6

This is a complement of my last question Apply command on nth character of a word, I need to color a range of characters like this \ColorRgChar{2-5}{examination} which color from second to the fifth character of the word.

If the first number is greater then the length of word no coloring is done.

Coloring of characters should be done at one time with a single \textcolor command not with \textcolor for every char. @David Carlisle

\documentclass{article}
\usepackage{xcolor}

\begin{document}

\makeatletter
\def\ColorRgChar#1#2{\xColorRgChar{#1}#2\@empty}
\def\xColorRgChar#1#2{\ifnum\ifx\@empty#21\else#1\fi=1 \textcolor{red}{#2}\expandafter\@gobbletwo
    \else#2\fi\xColorRgChar{\numexpr#1-1\relax}}
\makeatother

\ColorRgChar{2}{examination} 

%\ColorRgChar{2-5}{examination}  % desired macro 

\end{document}
2
  • 1
    Why the aversion to coloring a character at a time? Commented Feb 22, 2020 at 20:56
  • 1
    I want to use macro on Arabic word and coloring each character alone break font shaping inside the range of letters.
    – Salim Bou
    Commented Feb 22, 2020 at 21:08

4 Answers 4

5

The bulk of the code is managing the input. The second argument can be

  1. a single number, meaning just one character to color;
  2. -, meaning color everything;
  3. m-, meaning color from the m-th character to the end;
  4. -n, meaning color from the start up to the n-th character;
  5. m-n, meaning color from the m-th up to the n-th character.
\documentclass{article}
\usepackage{xparse,xcolor}

\ExplSyntaxOn

\NewDocumentCommand{\colorrange}{>{\SplitArgument{1}{-}}mm}
 {
  % #1 is passed as two braced arguments
  \salim_colorrange:nnn #1 { #2 }
 }

\cs_new_protected:Nn \salim_colorrange:nnn
 {
  % let's analyze the first two args
  \tl_if_novalue:nTF { #2 }
   {% no hyphen in the argument
    \__salim_colorrange:nnn { #1 } { #1 } { #3 }
   }
   {
    \bool_lazy_or:nnTF { \tl_if_blank_p:n { #1 } } { \tl_if_blank_p:n { #2 } }
     {% argument is -n or m- or -
      \tl_if_blank:nTF { #1 }
       {
        \tl_if_blank:nTF { #2 }
         {% argument is -
          \textcolor{red}{#3}
         }
         {% argument is -n
          \__salim_colorrange:nnn { 1 } { #2 } { #3 }
         }
       }
       {% argument is m-
        \__salim_colorrange:nnn { #1 } { -1 } { #3 }
       }
     }
     {% argument is m-n
      \__salim_colorrange:nnn { #1 } { #2 } { #3 }
     }
   }
 }

\cs_new_protected:Nn \__salim_colorrange:nnn
 {
  \int_compare:nTF { #1 > #2 > 0 }
   {
    #3
   }
   {
    \tl_range:nnn { #3 } { 1 } { #1 - 1 }
    \textcolor{red}{ \tl_range:nnn { #3 } { #1 } { #2 } }
    \tl_range:nnn { #3 } { #2 + 1 } { -1 }
   }
 }

\ExplSyntaxOff

\begin{document}

\colorrange{4}{examination} (4)

\colorrange{4-6}{examination} (4-6)

\colorrange{1-3}{examination} (1-3)

\colorrange{-3}{examination} (-3)

\colorrange{4-11}{examination} (4-11)

\colorrange{4-}{examination} (4-)

\colorrange{-}{examination} (-)

\colorrange{5-4}{examination} (5-4)

\colorrange{12-4}{examination} (12-4)

\colorrange{12-}{examination} (12-)

\colorrange{-15}{examination} (-15)

\end{document}

Wrong input is allowed. Changing the color with an optional argument can be easily added.

enter image description here

4
5
\documentclass{article}
\usepackage{xcolor}
\newcounter{pft}
\begin{document}

\makeatletter
\def\pft#1-#2;{\edef\lower@pft{\the\numexpr#1-1}\edef\upper@pft{\the\numexpr#2+1}}%
\def\ColorRgChar#1#2{\pft#1;%
\setcounter{pft}{0}%
\@tfor\next:=#2\do{\stepcounter{pft}%
\ifnum\value{pft}>\lower@pft
 \ifnum\value{pft}<\upper@pft
  \textcolor{red}{\next}%
 \else
  \next
 \fi
\else
 \next
\fi
}}%
\makeatother

\ColorRgChar{2-5}{examination} \ColorRgChar{4-7}{hibernation} \ColorRgChar{1-3}{catcode}  

\end{document}

enter image description here

Or without an explicit counter.

\documentclass{article}
\usepackage{xcolor}
\colorlet{SalimBouColor}{red}
\begin{document}

\makeatletter
\def\pft#1-#2;{\edef\lower@pft{\the\numexpr#1-1}\edef\upper@pft{\the\numexpr#2+1}}%
\def\ColorRgChar#1#2{\pft#1;%
\edef\n@pft{0}%
\@tfor\next:=#2\do{\edef\n@pft{\the\numexpr\n@pft+1}%
\ifnum\n@pft>\lower@pft
 \ifnum\n@pft<\upper@pft
  \textcolor{SalimBouColor}{\next}%
 \else
  \next
 \fi
\else
 \next
\fi
}}%
\makeatother

\ColorRgChar{2-5}{examination} \ColorRgChar{4-7}{hibernation} \ColorRgChar{1-3}{catcode}  

This includes, of course, your case of just coloring one character: \ColorRgChar{3-3}{examination}

\colorlet{SalimBouColor}{blue}%
\ColorRgChar{2-5}{examination}

\end{document}

enter image description here

Or without wrapping single characters into \textcolor.

\documentclass{article}
\usepackage{xcolor}
\colorlet{SalimBouColor}{red}
\begin{document}

\makeatletter
\def\pft#1-#2-#3;{\edef\lower@pft{#1}\edef\upper@pft{#2}}%
\def\ColorRgChar#1#2{\pft#1-0-0;%
\ifnum\upper@pft=0
\edef\upper@pft{\lower@pft}%
\fi
\colorlet{orig@color}{.}%
\edef\n@pft{0}%
\@tfor\next:=#2\do{\edef\n@pft{\the\numexpr\n@pft+1}%
\edef\tmp@pft{}%
\ifnum\n@pft=\lower@pft
\color{SalimBouColor}%
\fi
\next
\ifnum\n@pft=\upper@pft
\color{orig@color}%
\fi
}}%
\makeatother

\ColorRgChar{2-5}{examination} \ColorRgChar{4-7}{hibernation} \ColorRgChar{1-3}{catcode}  

This includes, of course, your case of just coloring one character: \ColorRgChar{3}{examination}

\colorlet{SalimBouColor}{blue}%
\ColorRgChar{2-5}{examination}

\end{document}

enter image description here

8
  • Thanks Schrödinger's the macro color every character alone what I need is a single \textcolor{red} command for all characters in the range like what we get with e\textcolor{red}{xami}nation
    – Salim Bou
    Commented Feb 22, 2020 at 18:21
  • @SalimBou I added a version for that, too.
    – user194703
    Commented Feb 22, 2020 at 18:42
  • How would the code have to be modified so that, say, \ColorRgChar{2-3}{öäüßÖÄÜ} would run without crashing under pdfLaTeX? (It runs fine under LuaLaTeX.)
    – Mico
    Commented Feb 22, 2020 at 18:52
  • 2
    @Mico I do not know. I never use these characters in my TeX files since you will always find something where they cause trouble, especially if you aim at writing a paper that can be submitted to the arXiv and/or a journal. My real life tex files are always designed in such a way that they can be uploaded to the arXiv, i.e. no lualatex, none of the newer (and oscillating) expl3, just well-established LaTeX code ingredients. Of course, \ColorRgChar{2-5}{ex{\"a}amination} works fine with the above code, and it will work on the arXiv.
    – user194703
    Commented Feb 22, 2020 at 19:06
  • 1
    @Mico BTW, \ColorRgChar{2-5}{ex{ä}amination} also works.
    – user194703
    Commented Feb 22, 2020 at 19:08
4

Here's a LuaLaTeX-based solution. It provides a Lua function that does the actual work and a LaTeX utility macro, called \ColorRangeChar, which passes its three arguments to the Lua function. The three arguments are (a) and (b) integers that indicate the positions of the first and last characters to be colored and (c) the word itself.

This solution satisfies the OP's requirement that "Coloring of characters should be done at one time with a single \textcolor command." The Lua function is set up to handle non-ASCII characters, such as öäüßÖÄÜ. Of course, no coloring is done if the first number exceeds the number of characters in the word.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{xcolor}
%% set up the Lua function that does the actual work:
\directlua{
function color_range_char ( m , n , s )
  local t
  t = unicode.utf8.sub(s,1,m-1) ..
      "\string\\textcolor{red}{" .. unicode.utf8.sub(s,m,n) .. "}" ..
      unicode.utf8.sub(s,n+1) 
  tex.sprint ( t )
end
}
%% set up a LaTeX macro that invokes the Lua function:
\newcommand\ColorRangeChar[3]{\directlua{color_range_char(#1,#2,"#3")}}  

\begin{document}
\ColorRangeChar{3}{5}{examination}

\ColorRangeChar{2}{4}{öäüßÖÄÜ}
\end{document}
3

Here is an extension of egreg's first code in this answer to support ranges of the form min-max, where min means “start” if not given, and max means “end” if not provided. My code uses l3regex to parse the range expression.

\documentclass{article}
\usepackage{xcolor}
\usepackage{xparse}

\ExplSyntaxOn

\msg_new:nnn { salim } { invalid-range-expression }
  { Invalid~range~expression:~'\exp_not:n {#1}'. }

\msg_new:nnn { salim } { zero-bound-in-range-expression }
  { Range~expression~contains~zero~bound:~'\exp_not:n {#1}'. }

\tl_new:N \l__salim_startexpr_tl
\tl_new:N \l__salim_endexpr_tl
\int_new:N \l__salim_start_int
\int_new:N \l__salim_end_int
\regex_const:Nn \l__salim_range_regex { \A (\d*) \- (\d*) \Z }

% Internal function that handles non-degenerate, ready-to-use input
\cs_new_protected:Npn \__salim_colorrange:nnn #1#2#3
  {
    % Deliver all characters before the chosen range
    \tl_range:nnn { #3 } { 1 } { \l__salim_start_int - 1 }
    % Deliver the characters in the chosen range with the desired color
    \textcolor {#1}
      { \tl_range:nnn { #3 } { \l__salim_start_int } { \l__salim_end_int } }
    % Deliver all characters after the chosen range
    \tl_range:nnn { #3 } { \l__salim_end_int + 1 } { -1 }
  }

\cs_new_protected:Npn \salim_colorrange:nnn #1#2#3
  {
    \regex_extract_once:NnNTF \l__salim_range_regex {#2} \l_tmpa_seq
      {
        \tl_set:Nx \l__salim_startexpr_tl { \seq_item:Nn \l_tmpa_seq { 2 } }
        \tl_set:Nx \l__salim_endexpr_tl { \seq_item:Nn \l_tmpa_seq { 3 } }

        \int_set:Nn \l__salim_start_int
          {
            \tl_if_empty:NTF \l__salim_startexpr_tl { 1 }
              { \l__salim_startexpr_tl }
          }
        \int_set:Nn \l__salim_end_int
          {
            \tl_if_empty:NTF \l__salim_endexpr_tl { -1 }
              { \l__salim_endexpr_tl }
          }

        \bool_lazy_any:nTF
          {
            { \int_compare_p:nNn { \l__salim_start_int } = { 0 } }
            { \int_compare_p:nNn { \l__salim_end_int } = { 0 } }
          }
          { \msg_error:nnn { salim } { zero-bound-in-range-expression } {#2} }
          {
            \bool_if:nTF
              {
                \int_compare_p:nNn
                  { \l__salim_start_int } > { \tl_count:n {#3} }
                ||
                ! \int_compare_p:nNn { \l__salim_end_int } = { -1 }
                && \int_compare_p:nNn { \l__salim_start_int } >
                                      { \l__salim_end_int }
              }
              {#3}
              { \__salim_colorrange:nnn {#1} {#2} {#3} }
          }
      }
      { \msg_error:nnn { salim } { invalid-range-expression } {#2} }
  }

\NewDocumentCommand \colorrange { O{red} m m }
  {% #1 = color to use, #2 = range, #3 = word
    \salim_colorrange:nnn {#1} {#2} {#3}
  }

\ExplSyntaxOff

\begin{document}

\colorrange{4-6}{examination}

\colorrange[blue]{1-3}{examination}

\colorrange[blue]{-3}{examination}

\colorrange[green!50!black]{4-11}{examination}

\colorrange[green!50!black]{4-}{examination}

\colorrange{-}{examination}

\bigskip

% Cases with degenerate input
\colorrange{5-4}{examination}

\colorrange{12-4}{examination}

\colorrange{12-}{examination}

\colorrange{-15}{examination}

\end{document}

enter image description here

6
  • The second and third arguments to \tl_range:nnn accept <integer> denotations, so you can directly pass them integer variables, no need for \tl_range:nVV.
    – egreg
    Commented Feb 22, 2020 at 21:13
  • @egreg Thanks for your comment! I know this works, but AFAICT, this is because of an undocumented feature of \tl_range:nnn: the fact that it accepts integer expressions for its second and third arguments. The doc. of \int_eval:n seems to give a definition of integer denotation which \l__salim_start_int does not satisfy. However, it is a valid integer expression and expands to an integer denotation. And yes, I know I used the undocumented feature in the other two \tl_range:nnn calls... because you did, so it must be correct. :-)
    – frougon
    Commented Feb 23, 2020 at 0:15
  • @egreg Rereading your comment, maybe you wanted to say that \tl_range:nnn reading integer denotations (according to its documentation), it will eventually cause TeX to read 〈number〉s, operation during which \countdef tokens are treated as 〈number〉s and macros are expanded until a 〈number〉 is finished. I can accept that. What I meant is that if one strictly interprets the \int_eval:n documentation where the term “integer denotation” appears to be defined, a token such as \l__salim_start_int doesn't qualify.
    – frougon
    Commented Feb 23, 2020 at 10:17
  • 1
    @egreg Right, I agree with that. Don't you think the doc should be slightly updated to mention integer expressions?
    – frougon
    Commented Feb 23, 2020 at 10:19
  • 1
    For people still wondering: commit f437b2d128f4.
    – frougon
    Commented Feb 23, 2020 at 17:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .