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}
Salim Bou
  • 17,021
  • 2
  • 31
  • 76

4 Answers4

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

  • 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 Feb 22 '20 at 18:21
  • @SalimBou I added a version for that, too. –  Feb 22 '20 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 Feb 22 '20 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. –  Feb 22 '20 at 19:06
  • 1
    @Mico BTW, \ColorRgChar{2-5}{ex{ä}amination} also works. –  Feb 22 '20 at 19:08
  • Is there a possibility to change macro for a single character so it can be used like this \ColorRgChar{3}{examination} not like this \ColorRgChar{3-3}{examination}? – Salim Bou Feb 22 '20 at 19:50
  • @SalimBou Sure. I added it to the last code. –  Feb 22 '20 at 20:02
  • Schrödinger's cat the macro do not succed in RTL (right to left) context I think it will works if we use \textcolor rather then \color command. – Salim Bou Feb 22 '20 at 20:50
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

egreg
  • 1,121,712
  • Thanks it work like a charm, I just have to learn latex3 syntax :) – Salim Bou Feb 22 '20 at 21:27
  • Is there a way to make such macro (Format char and format chars) work with glyphs,accents? as sown in https://tex.stackexchange.com/questions/355666/textarabic-highlight-specifc-letters-or-elements-of-text – Silva Aug 15 '20 at 23:04
  • https://tex.stackexchange.com/questions/558813/formatting-multiple-characters-and-glyhs-accents-within-the-same-word – Silva Aug 16 '20 at 00:21
  • @Silva Not really, I’m afraid. Maybe with XeLaTeX. – egreg Aug 16 '20 at 07:58
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}
Mico
  • 506,678
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

frougon
  • 24,283
  • 1
  • 32
  • 55
  • 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 Feb 22 '20 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 Feb 23 '20 at 00: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 Feb 23 '20 at 10:17
  • Basically, the arguments to \tl_range:nnn are passed to (an internal version of) \int_eval:n, so an integer variable qualifies. – egreg Feb 23 '20 at 10:19
  • 1
    @egreg Right, I agree with that. Don't you think the doc should be slightly updated to mention integer expressions? – frougon Feb 23 '20 at 10:19
  • 1
    For people still wondering: commit f437b2d128f4. – frougon Feb 23 '20 at 17:08