summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3kernel/l3text-case.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/l3kernel/l3text-case.dtx')
-rw-r--r--macros/latex/contrib/l3kernel/l3text-case.dtx1421
1 files changed, 1421 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3kernel/l3text-case.dtx b/macros/latex/contrib/l3kernel/l3text-case.dtx
new file mode 100644
index 0000000000..3c608eacb9
--- /dev/null
+++ b/macros/latex/contrib/l3kernel/l3text-case.dtx
@@ -0,0 +1,1421 @@
+% \iffalse meta-comment
+%
+%% File: l3text-case.dtx
+%
+% Copyright (C) 2020 The LaTeX3 Project
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version. The latest version
+% of this license is in the file
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3kernel bundle" (The Work in LPPL)
+% and all files in that bundle must be distributed together.
+%
+% -----------------------------------------------------------------------
+%
+% The development version of the bundle can be found at
+%
+% https://github.com/latex3/latex3
+%
+% for those people who are interested.
+%
+%<*driver>
+\documentclass[full,kernel]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \textsf{l3text-case} package: text processing (case changing)^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2020-01-12}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3text-case} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=text>
+% \end{macrocode}
+%
+% \subsection{Case changing}
+%
+% \begin{macro}[EXP]
+% {
+% \text_lowercase:n,
+% \text_uppercase:n,
+% \text_titlecase:n,
+% \text_titlecase_first:n
+% }
+% \begin{macro}[EXP]
+% {
+% \text_lowercase:nn,
+% \text_uppercase:nn,
+% \text_titlecase:nn,
+% \text_titlecase_first:nn
+% }
+% The user level functions here are all wrappers around the internal
+% functions for case changing.
+% \begin{macrocode}
+\cs_new:Npn \text_lowercase:n #1
+ { \@@_change_case:nnn { lower } { } {#1} }
+\cs_new:Npn \text_uppercase:n #1
+ { \@@_change_case:nnn { upper } { } {#1} }
+\cs_new:Npn \text_titlecase:n #1
+ { \@@_change_case:nnn { title } { } {#1} }
+\cs_new:Npn \text_titlecase_first:n #1
+ { \@@_change_case:nnn { titleonly } { } {#1} }
+\cs_new:Npn \text_lowercase:nn #1#2
+ { \@@_change_case:nnn { lower } {#1} {#2} }
+\cs_new:Npn \text_uppercase:nn #1#2
+ { \@@_change_case:nnn { upper } {#1} {#2} }
+\cs_new:Npn \text_titlecase:nn #1#2
+ { \@@_change_case:nnn { title } {#1} {#2} }
+\cs_new:Npn \text_titlecase_first:nn #1#2
+ { \@@_change_case:nnn { titleonly } {#1} {#2} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case:nnn, \@@_change_case_aux:nnn}
+% \begin{macro}[EXP]{\@@_change_case_store:n}
+% \begin{macro}[EXP]{\@@_change_case_store:nw}
+% \begin{macro}[EXP]{\@@_change_case_result:n}
+% \begin{macro}[EXP]{\@@_change_case_end:w}
+% \begin{macro}[EXP]{\@@_change_case_loop:nnw}
+% \begin{macro}[EXP]{\@@_change_case_break:w}
+% \begin{macro}[EXP]
+% {
+% \@@_change_case_group_lower:nnn ,
+% \@@_change_case_group_upper:nnn ,
+% \@@_change_case_group_title:nnn ,
+% \@@_change_case_group_titleonly:nnn
+% }
+% \begin{macro}[EXP]{\@@_change_case_space:nnw}
+% \begin{macro}[EXP]{\@@_change_case_N_type:nnN, \@@_change_case_N_type_aux:nnN}
+% \begin{macro}[EXP]{\@@_change_case_N_type:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_math_search:nnNNN}
+% \begin{macro}[EXP]{\@@_change_case_math_loop:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_math_N_type:nnNN}
+% \begin{macro}[EXP]{\@@_change_case_math_group:nnNn}
+% \begin{macro}[EXP]{\@@_change_case_math_space:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_cs_check:nnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnNN}
+% \begin{macro}[EXP]{\@@_change_case_exclude:nnNn}
+% \begin{macro}[EXP]
+% {
+% \@@_change_case_letterlike_lower:nnN ,
+% \@@_change_case_letterlike_upper:nnN ,
+% \@@_change_case_letterlike_title:nnN ,
+% \@@_change_case_letterlike_titleonly:nnN
+% }
+% \begin{macro}[EXP]{\@@_change_case_letterlike:nnnnN}
+% \begin{macro}[EXP]
+% {\@@_change_case_char_lower:nnN, \@@_change_case_char_upper:nnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_lower_sigma:NnnN}
+% \begin{macro}[EXP]
+% {\@@_change_case_char_title:nnN, \@@_change_case_char_titleonly:nnN}
+% \begin{macro}[EXP]
+% {\@@_change_case_char_title:nN, \@@_change_case_char_titleonly:nN}
+% \begin{macro}[EXP]{\@@_change_case_char_title:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_char_char:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNNNN}
+% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnn}
+% \begin{macro}[EXP]
+% {
+% \@@_change_case_char_next_lower:nn ,
+% \@@_change_case_char_next_upper:nn ,
+% \@@_change_case_char_next_title:nn ,
+% \@@_change_case_char_next_titleonly:nn
+% }
+% \begin{macro}[EXP]{\@@_change_case_char_next_end:nn}
+% As for the expansion code, the business end of case changing is the
+% handling of \texttt{N}-type tokens. First, we expand the input fully
+% (so the loops here don't need to worry about awkward look-aheads and the
+% like). Then we split into the different paths.
+%
+% The code here needs to be \texttt{f}-type expandable to deal with the
+% situation where case changing is applied in running text. There, we
+% might have case changing as a document command and the text containing
+% other non-expandable document commands.
+% \begin{verbatim}
+% \cs_set_eq:NN \MakeLowercase \text_lowercase
+% ...
+% \MakeLowercase{\enquote*{A} text}
+% \end{verbatim}
+% If we use an \texttt{e}-type expansion and wrap each token in
+% \cs{exp_not:n}, that would explode: the document command grabs
+% \cs{exp_not:n} as an argument, and things go badly wrong. So we have to
+% wrap the entire result in exactly one \cs{exp_not:n}, or rather in the
+% kernel version.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case:nnn #1#2#3
+ {
+ \__kernel_exp_not:w \exp_after:wN
+ {
+ \exp:w
+ \exp_args:Ne \@@_change_case_aux:nnn
+ { \text_expand:n {#3} }
+ {#1} {#2}
+ }
+ }
+\cs_new:Npn \@@_change_case_aux:nnn #1#2#3
+ {
+ \group_align_safe_begin:
+ \@@_change_case_loop:nnw {#2} {#3} #1
+ \q_recursion_tail \q_recursion_stop
+ \@@_change_case_result:n { }
+ }
+% \end{macrocode}
+% As for expansion, collect up the tokens for future use.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_store:n #1
+ { \@@_change_case_store:nw {#1} }
+\cs_generate_variant:Nn \@@_change_case_store:n { o , e , V , v }
+\cs_new:Npn \@@_change_case_store:nw #1#2 \@@_change_case_result:n #3
+ { #2 \@@_change_case_result:n { #3 #1 } }
+\cs_new:Npn \@@_change_case_end:w #1 \@@_change_case_result:n #2
+ {
+ \group_align_safe_end:
+ \exp_end:
+ #2
+ }
+% \end{macrocode}
+% The main loop is the standard \texttt{tl action} type.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_loop:nnw #1#2#3 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#3}
+ { \@@_change_case_N_type:nnN }
+ {
+ \tl_if_head_is_group:nTF {#3}
+ { \use:c { @@_change_case_group_ #1 :nnn } }
+ { \@@_change_case_space:nnw }
+ }
+ {#1} {#2} #3 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_break:w #1 \q_recursion_tail \q_recursion_stop
+ {
+ \@@_change_case_store:n {#1}
+ \@@_change_case_end:w
+ }
+% \end{macrocode}
+% For a group, we \emph{could} worry about whether this contains a character
+% or not. However, that would make life very complex for little gain: exactly
+% what a first character is is rather weakly-defined anyway. So if there is
+% a group, we simply assume that a character has been seen, and for title
+% case we switch to the \enquote{rest of the tokens} situation. To avoid
+% having too much testing, we use a two-step process here to allow the
+% titlecase functions to be separate.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_group_lower:nnn #1#2#3
+ {
+ \@@_change_case_store:o
+ {
+ \exp_after:wN
+ {
+ \exp:w
+ \@@_change_case_aux:nnn {#3} {#1} {#2}
+ }
+ }
+ \@@_change_case_loop:nnw {#1} {#2}
+ }
+\cs_new_eq:NN \@@_change_case_group_upper:nnn
+ \@@_change_case_group_lower:nnn
+\cs_new:Npn \@@_change_case_group_title:nnn #1#2#3
+ {
+ \@@_change_case_store:o
+ {
+ \exp_after:wN
+ {
+ \exp:w
+ \@@_change_case_aux:nnn {#3} {#1} {#2}
+ }
+ }
+ \@@_change_case_loop:nnw { lower } {#2}
+ }
+\cs_new:Npn \@@_change_case_group_titleonly:nnn #1#2#3
+ {
+ \@@_change_case_store:o
+ {
+ \exp_after:wN
+ {
+ \exp:w
+ \@@_change_case_aux:nnn {#3} {#1} {#2}
+ }
+ }
+ \@@_change_case_break:w
+ }
+\use:x
+ {
+ \cs_new:Npn \exp_not:N \@@_change_case_space:nnw ##1##2 \c_space_tl
+ }
+ {
+ \@@_change_case_store:n { ~ }
+ \@@_change_case_loop:nnw {#1} {#2}
+ }
+% \end{macrocode}
+% The first step of handling \texttt{N}-type tokens is to filter out the
+% end-of-loop. That has to be done separately from the first real step
+% as otherwise we pick up the wrong delimiter. The loop here is the same
+% as the \texttt{expand} one, just passing the additional data long. If no
+% close-math token is found then the final clean-up is forced
+% (i.e.~there is no assumption of \enquote{well-behaved} input in terms of
+% math mode).
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_N_type:nnN #1#2#3
+ {
+ \quark_if_recursion_tail_stop_do:Nn #3
+ { \@@_change_case_end:w }
+ \@@_change_case_N_type_aux:nnN {#1} {#2} #3
+ }
+\cs_new:Npn \@@_change_case_N_type_aux:nnN #1#2#3
+ {
+ \exp_args:NV \@@_change_case_N_type:nnnN
+ \l_text_math_delims_tl {#1} {#2} #3
+ }
+\cs_new:Npn \@@_change_case_N_type:nnnN #1#2#3#4
+ {
+ \@@_change_case_math_search:nnNNN {#2} {#3} #4 #1
+ \q_recursion_tail \q_recursion_tail
+ \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_math_search:nnNNN #1#2#3#4#5
+ {
+ \quark_if_recursion_tail_stop_do:Nn #4
+ { \@@_change_case_cs_check:nnN {#1} {#2} #3 }
+ \token_if_eq_meaning:NNTF #3 #4
+ {
+ \use_i_delimit_by_q_recursion_stop:nw
+ {
+ \@@_change_case_store:n {#3}
+ \@@_change_case_math_loop:nnNw {#1} {#2} #5
+ }
+ }
+ { \@@_change_case_math_search:nnNNN {#1} {#2} #3 }
+ }
+\cs_new:Npn \@@_change_case_math_loop:nnNw #1#2#3#4 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#4}
+ { \@@_change_case_math_N_type:nnNN }
+ {
+ \tl_if_head_is_group:nTF {#4}
+ { \@@_change_case_math_group:nnNn }
+ { \@@_change_case_math_space:nnNw }
+ }
+ {#1} {#2} #3 #4 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_math_N_type:nnNN #1#2#3#4
+ {
+ \quark_if_recursion_tail_stop_do:Nn #4
+ { \@@_change_case_end:w }
+ \@@_change_case_store:n {#4}
+ \token_if_eq_meaning:NNTF #4 #3
+ { \@@_change_case_loop:nnw {#1} {#2} }
+ { \@@_change_case_math_loop:nnNw {#1} {#2} #3 }
+ }
+\cs_new:Npn \@@_change_case_math_group:nnNn #1#2#3#4
+ {
+ \@@_change_case_store:n { {#4} }
+ \@@_change_case_math_loop:nnNw {#1} {#2} #3
+ }
+\use:x
+ {
+ \cs_new:Npn \exp_not:N \@@_change_case_math_space:nnNw ##1##2##3
+ \c_space_tl
+ }
+ {
+ \@@_change_case_store:n { ~ }
+ \@@_change_case_math_loop:nnNw {#1} {#2} #3
+ }
+% \end{macrocode}
+% Once potential math-mode cases are filtered out the next stage is to
+% test if the token grabbed is a control sequence: the two routes the code
+% may take are then very different.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_cs_check:nnN #1#2#3
+ {
+ \token_if_cs:NTF #3
+ { \@@_change_case_exclude:nnN }
+ { \use:c { @@_change_case_char_ #1 :nnN } }
+ {#1} {#2} #3
+ }
+% \end{macrocode}
+% To deal with a control sequence there is first a need to test if it is
+% on the list which indicate that case changing should be skipped. That's
+% done using a loop as for the other special cases. If a hit is found then
+% the argument is grabbed and passed through as-is.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_exclude:nnN #1#2#3
+ {
+ \exp_args:Ne \@@_change_case_exclude:nnnN
+ {
+ \exp_not:V \l_text_math_arg_tl
+ \exp_not:V \l_text_case_exclude_arg_tl
+ }
+ {#1} {#2} #3
+ }
+\cs_new:Npn \@@_change_case_exclude:nnnN #1#2#3#4
+ {
+ \@@_change_case_exclude:nnNN {#2} {#3} #4 #1
+ \q_recursion_tail \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_exclude:nnNN #1#2#3#4
+ {
+ \quark_if_recursion_tail_stop_do:Nn #4
+ { \use:c { @@_change_case_letterlike_ #1 :nnN } {#1} {#2} #3 }
+ \cs_if_eq:NNTF #3 #4
+ {
+ \use_i_delimit_by_q_recursion_stop:nw
+ { \@@_change_case_exclude:nnNn {#1} {#2} #3 }
+ }
+ { \@@_change_case_exclude:nnNN {#1} {#2} #3 }
+ }
+\cs_new:Npn \@@_change_case_exclude:nnNn #1#2#3#4
+ {
+ \@@_change_case_store:n { #3 {#4} }
+ \@@_change_case_loop:nnw {#1} {#2}
+ }
+% \end{macrocode}
+% Letter-like commands may still be present: they are set up using a simple
+% lookup approach, so can easily be handled with no loop. If there is no
+% hit, we are at the end of the process: we loop around. Letter-like chars
+% are all available only in upper- and lowercase, so titlecasing maps to the
+% uppercase version.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_letterlike_lower:nnN #1#2#3
+ { \@@_change_case_letterlike:nnnnN {#1} {#1} {#1} {#2} #3 }
+\cs_new_eq:NN \@@_change_case_letterlike_upper:nnN
+ \@@_change_case_letterlike_lower:nnN
+\cs_new:Npn \@@_change_case_letterlike_title:nnN #1#2#3
+ { \@@_change_case_letterlike:nnnnN { upper } { lower } {#1} {#2} #3 }
+\cs_new:Npn \@@_change_case_letterlike_titleonly:nnN #1#2#3
+ { \@@_change_case_letterlike:nnnnN { upper } { end } {#1} {#2} #3 }
+\cs_new:Npn \@@_change_case_letterlike:nnnnN #1#2#3#4#5
+ {
+ \cs_if_exist:cTF { c_@@_ #1 case_ \token_to_str:N #5 _tl }
+ {
+ \@@_change_case_store:v
+ { c_@@_ #1 case_ \token_to_str:N #5 _tl }
+ \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#4}
+ }
+ {
+ \@@_change_case_store:n {#5}
+ \cs_if_exist:cTF
+ {
+ c_@@_
+ \str_if_eq:nnTF {#1} { lower } { upper } { lower }
+ case_ \token_to_str:N #5 _tl
+ }
+ { \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#4} }
+ { \@@_change_case_loop:nnw {#3} {#4} }
+ }
+ }
+% \end{macrocode}
+% For upper- and lowercase changes, once we get to this stage there are only
+% a couple of questions remaining: is there a language-specific mapping and
+% is there the special case of a terminal sigma. If not, then we pass to
+% a simple character mapping.
+% \begin{macrocode}
+\cs_new:Npx \@@_change_case_char_lower:nnN #1#2#3
+ {
+ \exp_not:N \cs_if_exist_use:cF { @@_change_case_lower_ #2 :nnnN }
+ {
+ \bool_lazy_or:nnTF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ { \exp_not:N \@@_change_case_lower_sigma:nnnN }
+ { \exp_not:N \@@_change_case_char:nnnN }
+ }
+ {#1} {#1} {#2} #3
+ }
+\cs_new:Npn \@@_change_case_char_upper:nnN #1#2#3
+ {
+ \cs_if_exist_use:cF { @@_change_case_upper_ #2 :nnnN }
+ { \@@_change_case_char:nnnN }
+ {#1} {#1} {#2} #3
+ }
+% \end{macrocode}
+% If the current character is an uppercase sigma, the a check is made on the
+% next item in the input. If it is \texttt{N}-type and not a control sequence
+% then there is a look-ahead phase: the logic here is simply based on letters.
+% The one exception is Dutch: see below.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_lower_sigma:nnnN #1#2#3#4
+ {
+ \int_compare:nNnTF { `#4 } = { "03A3 }
+ { \@@_change_case_lower_sigma:nnNw {#2} {#3} #4 }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+ \cs_new:Npn \@@_change_case_lower_sigma:nnNw #1#2#3#4 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#4}
+ { \@@_change_case_lower_sigma:NnnN #3 }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "03C2 } { \@@_char_catcode:N #3 } }
+ \@@_change_case_loop:nnw
+ }
+ {#1} {#2} #4 \q_recursion_stop
+ }
+ \cs_new:Npn \@@_change_case_lower_sigma:NnnN #1#2#3#4
+ {
+ \@@_change_case_store:e
+ {
+ \token_if_letter:NTF #4
+ { \char_generate:nn { "03C3 } { \@@_char_catcode:N #1 } }
+ { \char_generate:nn { "03C2 } { \@@_char_catcode:N #1 } }
+ }
+ \@@_change_case_loop:nnw {#2} {#3} #4
+ }
+ }
+% \end{macrocode}
+% For titlecasing, we need to fully expand the new character to see if it
+% is a letter (or active) But that means looking ahead in the $8$-bit case, so
+% we have to grab the required tokens up-front. Life is a lot easier for Unicode
+% \TeX{}'s, where we just have one token to worry about. The one wrinkle here
+% is that for look-ahead we'd get into trouble: luckily, only Dutch has that
+% issue.
+% \begin{macrocode}
+\cs_new:Npx \@@_change_case_char_title:nnN #1#2#3
+ {
+ \bool_lazy_or:nnTF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ { \exp_not:N \token_if_letter:NTF #3 }
+ {
+ \exp_not:N \bool_lazy_or:nnTF
+ { \exp_not:N \token_if_letter_p:N #3 }
+ { \exp_not:N \token_if_active_p:N #3 }
+ }
+ { \exp_not:N \use:c { @@_change_case_char_ #1 :nN } }
+ { \exp_not:N \@@_change_case_char_title:nnnN { title } {#1} }
+ {#2} #3
+ }
+\cs_new_eq:NN \@@_change_case_char_titleonly:nnN
+ \@@_change_case_char_title:nnN
+\cs_new:Npn \@@_change_case_char_title:nN #1#2
+ { \@@_change_case_char_title:nnnN { title } { lower } {#1} #2 }
+\cs_new:Npn \@@_change_case_char_titleonly:nN #1#2
+ { \@@_change_case_char_title:nnnN { title } { end } {#1} #2 }
+\cs_new:Npn \@@_change_case_char_title:nnnN #1#2#3#4
+ {
+ \cs_if_exist_use:cF { @@_change_case_title_ #3 :nnnN }
+ {
+ \cs_if_exist_use:cF { @@_change_case_upper_ #3 :nnnN }
+ { \@@_change_case_char:nnnN }
+ }
+ {#1} {#2} {#3} #4
+ }
+% \end{macrocode}
+% For Unicode engines we can handle all characters directly. However, for
+% the $8$-bit engines the aim is to deal with (a subset of) Unicode (UTF-8)
+% input. They deal with that by making the upper half of the range active,
+% so we look for that and if found work out how many UTF-8 octets there
+% are to deal with. Those can then be grabbed to reconstruct the full
+% Unicode character, which is then used in a lookup. (As will become
+% obvious below, there is no intention here of covering all of Unicode.)
+% \begin{macrocode}
+\bool_lazy_or:nnTF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_char:nnnN #1#2#3#4
+ {
+ \@@_change_case_store:e
+ { \use:c { char_ #1 case :N } #4 }
+ \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
+ }
+ }
+ {
+ \cs_new:Npn \@@_change_case_char:nnnN #1#2#3#4
+ {
+ \int_compare:nNnTF { `#4 } > { "80 }
+ {
+ \int_compare:nNnTF { `#4 } < { "E0 }
+ { \@@_change_case_char_UTFviii:nnnNN }
+ {
+ \int_compare:nNnTF { `#4 } < { "F0 }
+ { \@@_change_case_char_UTFviii:nnnNNN }
+ { \@@_change_case_char_UTFviii:nnnNNNN }
+ }
+ {#1} {#2} {#3} #4
+ }
+ {
+ \@@_change_case_store:e{ \use:c { char_ #1 case :N } #4 }
+ \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
+ }
+ }
+ \cs_new:Npn \@@_change_case_char_UTFviii:nnnNN #1#2#3#4#5
+ { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5} }
+ \cs_new:Npn \@@_change_case_char_UTFviii:nnnNNN #1#2#3#4#5#6
+ { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5#6} }
+ \cs_new:Npn \@@_change_case_char_UTFviii:nnnNNNNN #1#2#3#4#5#6#7
+ { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5#6#7} }
+ \cs_new:Npn \@@_change_case_char_UTFviii:nnnn #1#2#3#4
+ {
+ \cs_if_exist:cTF { c_@@_ #1 case_ \tl_to_str:n {#4} _tl }
+ {
+ \@@_change_case_store:v
+ { c_@@_ #1 case_ \tl_to_str:n {#4} _tl }
+ }
+ { \@@_change_case_store:n {#4} }
+ \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
+ }
+ }
+\cs_new:Npn \@@_change_case_char_next_lower:nn #1#2
+ { \@@_change_case_loop:nnw {#1} {#2} }
+\cs_new_eq:NN \@@_change_case_char_next_upper:nn
+ \@@_change_case_char_next_lower:nn
+\cs_new_eq:NN \@@_change_case_char_next_title:nn
+ \@@_change_case_char_next_lower:nn
+\cs_new_eq:NN \@@_change_case_char_next_titleonly:nn
+ \@@_change_case_char_next_lower:nn
+\cs_new:Npn \@@_change_case_char_next_end:nn #1#2
+ { \@@_change_case_break:w }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case_upper_de-alt:nnnN}
+% A simple alternative version for German.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:cpn { @@_change_case_upper_de-alt:nnnN } #1#2#3#4
+ {
+ \int_compare:nNnTF { `#4 } = { "00DF }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "1E9E } { \@@_char_catcode:N #4 } }
+ \use:c { @@_change_case_char_next_ #2 :nn }
+ {#2} {#3}
+ }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnn}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_aux:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_loop:nnw}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnN}
+% \begin{macro}[EXP]{\@@_change_case_if_greek:nTF}
+% For Greek uppercasing, we need to know if characters \emph{in the Greek
+% range} have accents. That means doing a \textsc{nfd} conversion first, then
+% starting a search. As described by the Unicode \textsc{cldr}, Greek accents
+% need to be found \emph{after} any U+0308 (diaeresis) and are done in two
+% groups to allow for the canonical ordering.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_upper_el:nnnN #1#2#3#4
+ {
+ \@@_change_case_if_greek:nTF { `#4 }
+ {
+ \exp_args:Ne \@@_change_case_upper_el:nnnn
+ { \char_to_nfd:N #4 } {#1} {#2} {#3}
+ }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+ \cs_new:Npn \@@_change_case_upper_el:nnnn #1#2#3#4
+ { \@@_change_case_upper_el_aux:nnnN {#2} {#3} {#4} #1 }
+ \cs_new:Npn \@@_change_case_upper_el_aux:nnnN #1#2#3#4
+ {
+ \@@_change_case_store:e { \use:c { char_ #1 case:N } #4 }
+ \@@_change_case_upper_el_loop:nnw {#2} {#3}
+ }
+ \cs_new:Npn \@@_change_case_upper_el_loop:nnw
+ #1#2#3 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#3}
+ { \@@_change_case_upper_el:nnN }
+ { \@@_change_case_loop:nnw }
+ {#1} {#2} #3 \q_recursion_stop
+ }
+% \end{macrocode}
+% In addition to the Greek accents, we list three cases here where an
+% accent outside the Greek range has a \text{nfd} that would make it
+% equivalent. That includes U+0344, which has to insert U+0308.
+% \begin{macrocode}
+ \cs_new:Npn \@@_change_case_upper_el:nnN #1#2#3
+ {
+ \token_if_cs:NTF #3
+ { \@@_change_case_loop:nnw {#1} {#2} #3 }
+ {
+ \int_compare:nNnTF { `#3 } = { "0308 }
+ {
+ \@@_change_case_store:n {#3}
+ \@@_change_case_upper_el_loop:nnw {#1} {#2}
+ }
+ {
+ \bool_lazy_any:nTF
+ {
+ { \int_compare_p:nNn { `#3 } = { "0300 } }
+ { \int_compare_p:nNn { `#3 } = { "0301 } }
+ { \int_compare_p:nNn { `#3 } = { "0304 } }
+ { \int_compare_p:nNn { `#3 } = { "0306 } }
+ { \int_compare_p:nNn { `#3 } = { "0308 } }
+ { \int_compare_p:nNn { `#3 } = { "0313 } }
+ { \int_compare_p:nNn { `#3 } = { "0314 } }
+ { \int_compare_p:nNn { `#3 } = { "0342 } }
+ { \int_compare_p:nNn { `#3 } = { "0340 } }
+ { \int_compare_p:nNn { `#3 } = { "0341 } }
+ { \int_compare_p:nNn { `#3 } = { "0343 } }
+ }
+ { \@@_change_case_upper_el_loop:nnw {#1} {#2} }
+ {
+ \int_compare:nNnTF { `#3 } = { "0344 }
+ {
+ \@@_change_case_store:e
+ {
+ \char_generate:nn { "0308 }
+ { \@@_char_catcode:N #3 }
+ }
+ \@@_change_case_upper_el_loop:nnw {#1} {#2}
+ }
+ {
+ \int_compare:nNnTF { `#3 } = { "0345 }
+ { \@@_change_case_loop:nnw {#1} {#2} }
+ { \@@_change_case_loop:nnw {#1} {#2} #3 }
+ }
+ }
+ }
+ }
+ }
+ \prg_new_conditional:Npnn \@@_change_case_if_greek:n #1 { TF }
+ {
+ \if_int_compare:w #1 < "0370 \exp_stop_f:
+ \prg_return_false:
+ \else:
+ \if_int_compare:w #1 > "03FF \exp_stop_f:
+ \if_int_compare:w #1 < "1F00 \exp_stop_f:
+ \prg_return_false:
+ \else:
+ \if_int_compare:w #1 > "1FFF \exp_stop_f:
+ \prg_return_false:
+ \else:
+ \prg_return_true:
+ \fi:
+ \fi:
+ \else:
+ \prg_return_true:
+ \fi:
+ \fi:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \begin{macro}[EXP]{\@@_change_case_title_el:nnnN}
+% Titlecasing retains accents, but to prevent the uppercasing code
+% from kicking in, there has to be an explicit function here.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_title_el:nnnN #1#2#3#4
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+% {
+% \@@_change_cases_lower_lt:nnnN ,
+% \@@_change_cases_lower_lt_auxi:nnnN ,
+% \@@_change_cases_lower_lt_auxii:nnnN
+% }
+% \begin{macro}[rEXP]{\@@_change_case_lower_lt:nnw}
+% \begin{macro}[rEXP]{\@@_change_case_lower_lt:nnN}
+% For Lithuanian, the issue to be dealt with is dots over lower case
+% letters: these should be present if there is another accent. The first step
+% is a simple match attempt: look for the three uppercase accented letters
+% which should gain a dot-above char in their lowercase form.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_lower_lt:nnnN #1#2#3#4
+ {
+ \exp_args:Ne \@@_change_case_lower_lt_auxi:nnnN
+ {
+ \int_case:nn { `#4 }
+ {
+ { "00CC } { "0300 }
+ { "00CD } { "0301 }
+ { "0128 } { "0303 }
+ }
+ }
+ {#2} {#3} #4
+ }
+% \end{macrocode}
+% If there was a hit, output the result with the dot-above and move on.
+% Otherwise, look for one of the three letters that can take a combining
+% accent: I, J and I-ogonek.
+% \begin{macrocode}
+ \cs_new:Npn \@@_change_case_lower_lt_auxi:nnnN #1#2#3#4
+ {
+ \tl_if_blank:nTF {#1}
+ {
+ \exp_args:Ne \@@_change_case_lower_lt_auxii:nnnN
+ {
+ \int_case:nn { `#4 }
+ {
+ { "0049 } { "0069 }
+ { "004A } { "006A }
+ { "012E } { "012F }
+ }
+ }
+ {#2} {#3} #4
+ }
+ {
+ \@@_change_case_store:e
+ {
+ \char_generate:nn { "0069 } { \@@_char_catcode:N #4 }
+ \char_generate:nn { "0307 } { \@@_char_catcode:N #4 }
+ \char_generate:nn {#1} { \@@_char_catcode:N #4 }
+ }
+ \@@_change_case_loop:nnw {#2} {#3}
+ }
+ }
+% \end{macrocode}
+% Again, branch depending on a hit. If there is one, we output the character
+% then need to look for a combining accent: as usual, we need to be aware of
+% the loop situation.
+% \begin{macrocode}
+ \cs_new:Npn \@@_change_case_lower_lt_auxii:nnnN #1#2#3#4
+ {
+ \tl_if_blank:nTF {#1}
+ { \@@_change_case_lower_sigma:nnnN {#2} {#2} {#3} #4 }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn {#1} { \@@_char_catcode:N #4 } }
+ \@@_change_case_lower_lt:nnw {#2} {#3}
+ }
+ }
+ \cs_new:Npn \@@_change_case_lower_lt:nnw #1#2#3 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#3}
+ { \@@_change_case_lower_lt:nnN }
+ { \@@_change_case_loop:nnw }
+ {#1} {#2} #3 \q_recursion_stop
+ }
+ \cs_new:Npn \@@_change_case_lower_lt:nnN #1#2#3
+ {
+ \bool_lazy_and:nnT
+ { ! \token_if_cs_p:N #3 }
+ {
+ \bool_lazy_any_p:n
+ {
+ { \int_compare_p:nNn { `#3 } = { "0300 } }
+ { \int_compare_p:nNn { `#3 } = { "0301 } }
+ { \int_compare_p:nNn { `#3 } = { "0303 } }
+ }
+ }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0307 } { \@@_char_catcode:N #3 } }
+ }
+ \@@_change_case_loop:nnw {#1} {#2} #3
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \begin{macro}[EXP]
+% {
+% \@@_change_cases_upper_lt:nnnN ,
+% \@@_change_cases_upper_lt_aux:nnnN
+% }
+% \begin{macro}[rEXP]{\@@_change_case_upper_lt:nnw}
+% \begin{macro}[rEXP]{\@@_change_case_upper_lt:nnN}
+% The uppercasing version: first find i/j/i-ogonek, then look for the
+% combining char: drop it if present.
+% \begin{macrocode}
+\bool_lazy_or:nnT
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_upper_lt:nnnN #1#2#3#4
+ {
+ \exp_args:Ne \@@_change_case_upper_lt_aux:nnnN
+ {
+ \int_case:nn { `#4 }
+ {
+ { "0069 } { "0049 }
+ { "006A } { "004A }
+ { "012F } { "012E }
+ }
+ }
+ {#2} {#3} #4
+ }
+ \cs_new:Npn \@@_change_case_upper_lt_aux:nnnN #1#2#3#4
+ {
+ \tl_if_blank:nTF {#1}
+ { \@@_change_case_char:nnnN { upper } {#2} {#3} #4 }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn {#1} { \@@_char_catcode:N #4 } }
+ \@@_change_case_upper_lt:nnw {#2} {#3}
+ }
+ }
+ \cs_new:Npn \@@_change_case_upper_lt:nnw #1#2#3 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#3}
+ { \@@_change_case_upper_lt:nnN }
+ { \use:c { @@_change_case_char_next_ #1 :nn } }
+ {#1} {#2} #3 \q_recursion_stop
+ }
+ \cs_new:Npn \@@_change_case_upper_lt:nnN #1#2#3
+ {
+ \bool_lazy_and:nnTF
+ { ! \token_if_cs_p:N #3 }
+ { \int_compare_p:nNn { `#3 } = { "0307 } }
+ { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} }
+ { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} #3 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnw}
+% \begin{macro}[EXP]{\@@_change_case_title_nl:nnN}
+% For Dutch, there is a single look-ahead test for \texttt{ij} when
+% title casing. If the appropriate letters are found, produce \texttt{IJ}
+% and gobble the \texttt{j}/\texttt{J}.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_title_nl:nnnN #1#2#3#4
+ {
+ \bool_lazy_or:nnTF
+ { \int_compare_p:nNn { `#4 } = { "0049 } }
+ { \int_compare_p:nNn { `#4 } = { "0069 } }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0049 } { \@@_char_catcode:N #4 } }
+ \@@_change_case_title_nl:nnw {#2} {#3}
+ }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+\cs_new:Npn \@@_change_case_title_nl:nnw #1#2#3 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#3}
+ { \@@_change_case_title_nl:nnN }
+ { \use:c { @@_change_case_char_next_ #1 :nn } }
+ {#1} {#2} #3 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_title_nl:nnN #1#2#3
+ {
+ \bool_lazy_and:nnTF
+ { ! \token_if_cs_p:N #3 }
+ {
+ \bool_lazy_or_p:nn
+ { \int_compare_p:nNn { `#3 } = { "004A } }
+ { \int_compare_p:nNn { `#3 } = { "006A } }
+ }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "004A } { \@@_char_catcode:N #3 } }
+ \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2}
+ }
+ { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} #3 }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnNw}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnN}
+% \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnNN}
+% The Turkic languages need special treatment for dotted-i and dotless-i.
+% The lower casing rule can be expressed in terms of searching first for
+% either a dotless-I or a dotted-I. In the latter case the mapping is
+% easy, but in the former there is a second stage search.
+% \begin{macrocode}
+\bool_lazy_or:nnTF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_new:Npn \@@_change_case_lower_tr:nnnN #1#2#3#4
+ {
+ \int_compare:nNnTF { `#4 } = { "0049 }
+ { \@@_change_case_lower_tr:nnNw {#1} {#3} #4 }
+ {
+ \int_compare:nNnTF { `#4 } = { "0130 }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0069 } { \@@_char_catcode:N #4 } }
+ \@@_change_case_loop:nnw {#1} {#3}
+ }
+ { \@@_change_case_lower_sigma:nnnN {#1} {#2} {#3} #4 }
+ }
+ }
+% \end{macrocode}
+% After a dotless-I there may be a dot-above character. If there is then
+% a dotted-i should be produced, otherwise output a dotless-i. When the
+% combination is found both the dotless-I and the dot-above char have to
+% be removed from the input.
+% \begin{macrocode}
+ \cs_new:Npn \@@_change_case_lower_tr:nnNw #1#2#3#4 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#4}
+ { \@@_change_case_lower_tr:nnN }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } }
+ \@@_change_case_loop:nnw
+ }
+ {#1} {#2} #4 \q_recursion_stop
+ }
+ \cs_new:Npn \@@_change_case_lower_tr:nnN #1#2#3
+ {
+ \bool_lazy_or:nnTF
+ { \token_if_cs_p:N #3 }
+ { ! \int_compare_p:nNn { `#3 } = { "0307 } }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } }
+ \@@_change_case_loop:nnw {#1} {#2} #3
+ }
+ {
+ \@@_change_case_store:e
+ { \char_generate:nn { "0069 } { \@@_char_catcode:N #3 } }
+ \@@_change_case_loop:nnw {#1} {#2}
+ }
+ }
+ }
+% \end{macrocode}
+% For $8$-bit engines, dot-above is not available so there is a simple
+% test for an upper-case I. Then we can look for the UTF-8 representation of
+% an upper case dotted-I without the combining char. If it's not there,
+% preserve the UTF-8 sequence as-is. With $8$bit engines, we cannot
+% completely preserve category codes, so we have to make some assumptions:
+% output a \enquote{normal} \texttt{i} for the dotted case. As the original
+% character here is catcode-$13$, we have to make a choice about handling
+% of |i|: generate a \enquote{normal} one.
+% \begin{macrocode}
+ {
+ \cs_new:Npn \@@_change_case_lower_tr:nnnN #1#2#3#4
+ {
+ \int_compare:nNnTF { `#4 } = { "0049 }
+ {
+ \@@_change_case_store:V \c_@@_dotless_i_tl
+ \@@_change_case_loop:nnw {#1} {#3}
+ }
+ {
+ \int_compare:nNnTF { `#4 } = { "00C4 }
+ { \@@_change_case_lower_tr:nnnNN {#1} {#2} {#3} #4 }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+ }
+ \cs_new:Npn \@@_change_case_lower_tr:nnnNN #1#2#3#4#5
+ {
+ \int_compare:nNnTF { `#5 } = { "00B0 }
+ {
+ \@@_change_case_store:e
+ {
+ \char_generate:nn { "0069 }
+ { \char_value_catcode:n { "0069 } }
+ }
+ \@@_change_case_loop:nnw {#1} {#3}
+ }
+ { \@@_change_case_char:nnnN {#1} {#2} {#3} #4#5 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \begin{macro}[EXP]{\@@_change_case_upper_tr:nnnN}
+% Uppercasing is easier: just one exception with no context.
+% \begin{macrocode}
+\cs_new:Npx \@@_change_case_upper_tr:nnnN #1#2#3#4
+ {
+ \exp_not:N \int_compare:nNnTF { `#4 } = { "0069 }
+ {
+ \bool_lazy_or:nnTF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \exp_not:N \@@_change_case_store:e
+ {
+ \exp_not:N \char_generate:nn { "0130 }
+ { \exp_not:N \@@_char_catcode:N #4 }
+ }
+ }
+ {
+ \exp_not:N \@@_change_case_store:V
+ \exp_not:N \c_@@_dotted_I_tl
+ }
+ \exp_not:N \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
+ }
+ { \exp_not:N \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+% {\@@_change_case_lower_az:nnnN, \@@_change_case_upper_az:nnnN}
+% Straight copies.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_change_case_lower_az:nnnN
+ \@@_change_case_lower_tr:nnnN
+\cs_new_eq:NN \@@_change_case_upper_az:nnnN
+ \@@_change_case_upper_tr:nnnN
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Case changing data for $8$-bit engines}
+%
+% \begin{variable}
+% {
+% \c_@@_dotless_i_tl ,
+% \c_@@_dotted_I_tl ,
+% \c_@@_i_ogonek_tl ,
+% \c_@@_I_ogonek_tl
+% }
+% For cases where there is an $8$-bit option in the |T1| font set up,
+% a variant is provided in both cases.
+% \begin{macrocode}
+\group_begin:
+ \bool_lazy_or:nnF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_set_protected:Npn \@@_tmp:w #1#2
+ {
+ \group_begin:
+ \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4
+ {
+ \tl_const:Nx #1
+ {
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {##1} { 13 }
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {##2} { 13 }
+ }
+ }
+ \use:x
+ { \@@_tmp:w \char_to_utfviii_bytes:n { "#2 } }
+ \group_end:
+ }
+ \@@_tmp:w \c_@@_dotless_i_tl { 0131 }
+ \@@_tmp:w \c_@@_dotted_I_tl { 0130 }
+ \@@_tmp:w \c_@@_i_ogonek_tl { 012F }
+ \@@_tmp:w \c_@@_I_ogonek_tl { 012E }
+ }
+\group_end:
+% \end{macrocode}
+% \end{variable}
+%
+% For $8$-bit engines we now need to define the case-change data for
+% the multi-octet mappings. These need a list of what code points are
+% doable in |T1| so the list is hard coded (there's no saving in loading
+% the mappings dynamically). All of the straight-forward ones have two
+% octets, so that is taken as read.
+% \begin{macrocode}
+\group_begin:
+ \bool_lazy_or:nnF
+ { \sys_if_engine_luatex_p: }
+ { \sys_if_engine_xetex_p: }
+ {
+ \cs_set_protected:Npn \@@_loop:nn #1#2
+ {
+ \quark_if_recursion_tail_stop:n {#1}
+ \use:x
+ {
+ \@@_tmp:w
+ \char_to_utfviii_bytes:n { "#1 }
+ \char_to_utfviii_bytes:n { "#2 }
+ }
+ \@@_loop:nn
+ }
+ \cs_set_protected:Npn \@@_tmp:nnnn #1#2#3#4#5
+ {
+ \tl_const:cx
+ {
+ c_@@_ #1 case_
+ \char_generate:nn {#2} { 12 }
+ \char_generate:nn {#3} { 12 }
+ _tl
+ }
+ {
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {#4} { 13 }
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {#5} { 13 }
+ }
+ }
+ \cs_set_protected:Npn \@@_tmp:w #1#2#3#4#5#6#7#8
+ {
+ \tl_const:cx
+ {
+ c_@@_lowercase_
+ \char_generate:nn {#1} { 12 }
+ \char_generate:nn {#2} { 12 }
+ _tl
+ }
+ {
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {#5} { 13 }
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \exp_not:N \char_generate:nn {#6} { 13 }
+ }
+ \@@_tmp:nnnn { upper } {#5} {#6} {#1} {#2}
+ \@@_tmp:nnnn { title } {#5} {#6} {#1} {#2}
+ }
+ \@@_loop:nn
+ { 00C0 } { 00E0 }
+ { 00C1 } { 00E1 }
+ { 00C2 } { 00E2 }
+ { 00C3 } { 00E3 }
+ { 00C4 } { 00E4 }
+ { 00C5 } { 00E5 }
+ { 00C6 } { 00E6 }
+ { 00C7 } { 00E7 }
+ { 00C8 } { 00E8 }
+ { 00C9 } { 00E9 }
+ { 00CA } { 00EA }
+ { 00CB } { 00EB }
+ { 00CC } { 00EC }
+ { 00CD } { 00ED }
+ { 00CE } { 00EE }
+ { 00CF } { 00EF }
+ { 00D0 } { 00F0 }
+ { 00D1 } { 00F1 }
+ { 00D2 } { 00F2 }
+ { 00D3 } { 00F3 }
+ { 00D4 } { 00F4 }
+ { 00D5 } { 00F5 }
+ { 00D6 } { 00F6 }
+ { 00D8 } { 00F8 }
+ { 00D9 } { 00F9 }
+ { 00DA } { 00FA }
+ { 00DB } { 00FB }
+ { 00DC } { 00FC }
+ { 00DD } { 00FD }
+ { 00DE } { 00FE }
+ { 0100 } { 0101 }
+ { 0102 } { 0103 }
+ { 0104 } { 0105 }
+ { 0106 } { 0107 }
+ { 0108 } { 0109 }
+ { 010A } { 010B }
+ { 010C } { 010D }
+ { 010E } { 010F }
+ { 0110 } { 0111 }
+ { 0112 } { 0113 }
+ { 0114 } { 0115 }
+ { 0116 } { 0117 }
+ { 0118 } { 0119 }
+ { 011A } { 011B }
+ { 011C } { 011D }
+ { 011E } { 011F }
+ { 0120 } { 0121 }
+ { 0122 } { 0123 }
+ { 0124 } { 0125 }
+ { 0128 } { 0129 }
+ { 012A } { 012B }
+ { 012C } { 012D }
+ { 012E } { 012F }
+ { 0132 } { 0133 }
+ { 0134 } { 0135 }
+ { 0136 } { 0137 }
+ { 0139 } { 013A }
+ { 013B } { 013C }
+ { 013E } { 013F }
+ { 0141 } { 0142 }
+ { 0143 } { 0144 }
+ { 0145 } { 0146 }
+ { 0147 } { 0148 }
+ { 014A } { 014B }
+ { 014C } { 014D }
+ { 014E } { 014F }
+ { 0150 } { 0151 }
+ { 0152 } { 0153 }
+ { 0154 } { 0155 }
+ { 0156 } { 0157 }
+ { 0158 } { 0159 }
+ { 015A } { 015B }
+ { 015C } { 015D }
+ { 015E } { 015F }
+ { 0160 } { 0161 }
+ { 0162 } { 0163 }
+ { 0164 } { 0165 }
+ { 0168 } { 0169 }
+ { 016A } { 016B }
+ { 016C } { 016D }
+ { 016E } { 016F }
+ { 0170 } { 0171 }
+ { 0172 } { 0173 }
+ { 0174 } { 0175 }
+ { 0176 } { 0177 }
+ { 0178 } { 00FF }
+ { 0179 } { 017A }
+ { 017B } { 017C }
+ { 017D } { 017E }
+ { 01CD } { 01CE }
+ { 01CF } { 01D0 }
+ { 01D1 } { 01D2 }
+ { 01D3 } { 01D4 }
+ { 01E2 } { 01E3 }
+ { 01E6 } { 01E7 }
+ { 01E8 } { 01E9 }
+ { 01EA } { 01EB }
+ { 01F4 } { 01F5 }
+ { 0218 } { 0219 }
+ { 021A } { 021B }
+ \q_recursion_tail ?
+ \q_recursion_stop
+ \cs_set_protected:Npn \@@_tmp:w #1#2#3
+ {
+ \group_begin:
+ \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4
+ {
+ \tl_const:cx
+ {
+ c_@@_ #3 case_
+ \char_generate:nn {##1} { 12 }
+ \char_generate:nn {##2} { 12 }
+ _tl
+ }
+ {#2}
+ }
+ \use:x
+ { \@@_tmp:w \char_to_utfviii_bytes:n { "#1 } }
+ \group_end:
+ }
+ \@@_tmp:w { 00DF } { SS } { upper }
+ \@@_tmp:w { 00DF } { Ss } { title }
+ \@@_tmp:w { 0131 } { I } { upper }
+ }
+ \group_end:
+% \end{macrocode}
+%
+% The (fixed) look-up mappings for letter-like control sequences.
+% \begin{macrocode}
+\group_begin:
+ \cs_set_protected:Npn \@@_change_case_setup:NN #1#2
+ {
+ \quark_if_recursion_tail_stop:N #1
+ \tl_const:cn { c_@@_lowercase_ \token_to_str:N #1 _tl }
+ { #2 }
+ \tl_const:cn { c_@@_uppercase_ \token_to_str:N #2 _tl }
+ { #1 }
+ \@@_change_case_setup:NN
+ }
+ \@@_change_case_setup:NN
+ \AA \aa
+ \AE \ae
+ \DH \dh
+ \DJ \dj
+ \IJ \ij
+ \L \l
+ \NG \ng
+ \O \o
+ \OE \oe
+ \SS \ss
+ \TH \th
+ \q_recursion_tail ?
+ \q_recursion_stop
+ \tl_const:cn { c_@@_uppercase_ \token_to_str:N \i _tl } { I }
+ \tl_const:cn { c_@@_uppercase_ \token_to_str:N \j _tl } { J }
+\group_end:
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex