11

I have a command that formats a single character, let's call it \d{}. (In my case, \d{} adds a dot under a Chinese character, as this is the custom to emphasize Chinese texts).

I want to define a new command \ds{} such that it applies \d{} over every letter of the string.

For example \ds{abc} would be equivalent to \d{a}\d{b}\d{c}.

Thanks!

  • Do you expect just characters in the string? Can you give a hint about what \d should do? Is this for LaTeX or Plain? – egreg Jul 1 '15 at 16:16
  • Yes, there will just be characters in the string. Not equations. (But my \d{} uses Latex maths inside it, if that matters). – Yan King Yin Jul 1 '15 at 16:26
  • Can you give us the \d definition? – Gonzalo Medina Jul 1 '15 at 16:26
  • \renewcommand{\d}[1]{$\underaccent{\scalebox{0.5}{\textbullet}}{\textrm{#1}}$} – Yan King Yin Jul 1 '15 at 16:28
17

You can use \@tfor. I provide also a better redefinition of the dot under according to your wish:

\documentclass{article}
\usepackage{graphicx}

\let\d\relax
\DeclareRobustCommand{\d}[1]{%
  \oalign{#1\cr\hidewidth\scalebox{0.5}{\textbullet}\hidewidth\cr}%
}
\makeatletter
\newcommand{\ds}[1]{%
  \@tfor\next:=#1\do{\d{\next}}%
}
\makeatother

\begin{document}

x\d{d}\d{s}\d{a}x

x\ds{dsa}x

\end{document}

enter image description here

What does \@tfor do? Its syntax is

\@tfor<scratch macro>:=<tokens>\do{<code>}

The scratch macro is traditionally \next, but it can be anything. The <tokens> part is any (brace balanced) list of tokens. In the loop, LaTeX essentially does \def<scratch macro>{<next token>}, so

\@tfor\next:=dsa\do{\d{\next}}

will perform

\def\next{d}\d{\next}\def\next{s}\d{\next}\def\next{a}\d{\next}

However, with \@tfor\next:=d {sa}\do{\d{\next}} we will just obtain

\def\next{d}\d{\next}\def\next{sa}\d{\next}

Explicit space tokens are ignored and braced groups of tokens are treated as one.

The expl3 analog is \tl_map_inline:nn:

\documentclass{article}
\usepackage{xparse}
\usepackage{graphicx}

\let\d\relax
\DeclareRobustCommand{\d}[1]{%
  \oalign{#1\cr\hidewidth\scalebox{0.5}{\textbullet}\hidewidth\cr}%
}

\ExplSyntaxOn
\NewDocumentCommand{\ds}{m}
 {
  \tl_map_inline:n { #1 } { \d { ##1 } }
 }
\ExplSyntaxOff

\begin{document}

x\d{d}\d{s}\d{a}x

x\ds{dsa}x

x\ds{d sa{bc}}x

\end{document}

No scratch macro is used: the current item in the loop is denoted by #1 (which becomes ##1 in the body of a definition, as usual).

In this particular case where just a single command is applied with the current item as argument, one can use \tl_map_function:nN:

\NewDocumentCommand{\ds}{m}
 {
  \tl_map_function:n { #1 } \d
 }

which has the same effect and is shorter. It can also appear in a full expansion context (not for this particular case, because of \d).

enter image description here

  • 1
    It would be great to have an explanation of \@tfor, especially since it's not defined in plain TeX – jpaugh Apr 27 '17 at 2:53
5

This solution allows word wrap and handles spaces between words. In addition, the [w] option allows the task to be performed on each word, rather than each character.'

In the MWE, I demonstrate with variously defined tasks:

  1. overstrike each character (2 different settings)

  2. place a dot under each character

  3. place a semicolon under each word

  4. apply extra intercharacter space

  5. apply extra interword space.

The task is defined in \charop{} for characters and \wordop{} for words, while the \chariterate[]{} macro does the iteration. Here is the MWE:

% WHILE THIS EXAMPLE IS SET UP FOR BOLDING A CALLIGRAPHIC FONT
% ITS GENERAL USE IS TO DO SOMETHING ON EACH char OF ITS ARGUMENT, ALLOWING LINE WRAP
% THE [W] OPTION TO \chariterate DOES SOMETHING TO EACH WORD.
\documentclass[10pt,a4paper,BCOR10mm,DIV11,toc=listof,parskip=full, openany]{scrbook}
\usepackage{stackengine}
\newcommand\chariterate[2][c]{\if w#1\worditeratehelper#2 \relax\relax\else
  \chariteratehelpA#2 \relax\relax\fi}
\def\chariteratehelpA#1 #2\relax{%
  \chariteratehelpB#1\relax\relax%
  \ifx\relax#2\else\ \chariteratehelpA#2\relax\fi
}
\def\chariteratehelpB#1#2\relax{%
  \charop{#1}%
  \ifx\relax#2\else
    \chariteratehelpB#2\relax%
  \fi
}
\def\worditeratehelper#1 #2\relax{%
  \wordop{#1}%
  \ifx\relax#2\else\ \worditeratehelper#2\relax\fi
}
\def\charop#1{#1}
\def\wordop#1{#1}
% THIS EXAMPLE ARTIFICIALLY BOLDS EACH char WITH A SHIFTED OVERSTRIKE
\def\charopA{%
  %\def\useanchorwidth{T}%
  \def\stacktype{L}%
  \def\stackalignment{l}%
  \def\calup{.2pt}%
  \def\calover{.15pt}%
  \renewcommand\charop[1]{\stackon[\calup]{##1}{\kern\calover##1}}%
}
% HERE'S AN EXAMPLE PLACING DOT UNDER EACH char
\def\charopB{%
  \def\useanchorwidth{T}%
  \def\stacktype{L}%
  \def\stackalignment{c}%
  \renewcommand\charop[1]{\stackunder[3pt]{##1}{.}}%
}
% EXTRA INTER-CHARACTER SPACE
\def\charopC{\renewcommand\charop[1]{##1\,}}
% HERE'S AN EXAMPLE PLACING DOT UNDER EACH WORD
\def\wordopA{%
  \renewcommand\wordop[1]{\def\stackalignment{c}\stackunder[3pt]{##1}{;}}
}
% EXTRA INTER-WORD SPACE
\def\wordopB{\renewcommand\wordop[1]{##1\ \ }}
%
\newenvironment{calligraphic}%
{\fontencoding{T1}\fontfamily{pzc}\fontseries{m}\fontshape{it}\fontsize{12pt}{12pt}\selectfont}{}%
\renewcommand*{\sectfont}{\normalcolor\usefont{T1}{pzc}{m}{it}}
\begin{document}
\charopA
   \begin{calligraphic}
   Test \textbf{this is not bold}\par
   Test \chariterate{this is bold with .2pt up shift and .15pt right shift}\par
 \def\calup{.0pt}
 \def\calover{.2pt}
   Test \chariterate{this is bold with .0pt up shift and .2pt right shift}\par
   \chariterate{cvxc This is a test. This is a test. This is a test. This is a test. 
    This is a test. This is a test. This is a test. This is a test. This is a 
    test. This is a test. This is a test. This fdsfsd is a test. This is a test. 
    This is a test. This is a test. This is adfsdf  test. This is a test.}\par
   \end{calligraphic}\par
\charopB
\wordopA
   \chariterate{This is doing something to each character.}\par
   \chariterate[w]{This is doing something to each word.}\par
   \chariterate{Ich bin m{\"u}de}.  
   \chariterate[w]{Ich bin m{\"u}de}.\par
\charopC
\wordopB
   \chariterate{This is doing something to each character.}\par
   \chariterate[w]{This is doing something to each word.}\par
\end{document}

enter image description here

In the most simple incarnation, to do merely underdots on characters alone, the code can be greatly reduced:

\documentclass{article}
\usepackage{stackengine}
\newcommand\chariterate[2][c]{\if w#1\worditeratehelper#2 \relax\relax\else
  \chariteratehelpA#2 \relax\relax\fi}
\def\chariteratehelpA#1 #2\relax{%
  \chariteratehelpB#1\relax\relax%
  \ifx\relax#2\else\ \chariteratehelpA#2\relax\fi
}
\def\chariteratehelpB#1#2\relax{%
  \charop{#1}%
  \ifx\relax#2\else
    \chariteratehelpB#2\relax%
  \fi
}
\def\worditeratehelper#1 #2\relax{%
  \wordop{#1}%
  \ifx\relax#2\else\ \worditeratehelper#2\relax\fi
}
  \def\useanchorwidth{T}%
  \def\stacktype{L}%
  \def\stackalignment{c}%
  \newcommand\charop[1]{\stackunder[3pt]{#1}{.}}%
  \def\wordop#1{#1}
\begin{document}
\chariterate{This is doing something to each character.
This is doing something to each character.
This is doing something to each character.}\par
\end{document}
  • Thanks, that may be useful for Chinese fonts also, when they don't provide the bold fonts. – Yan King Yin Jul 6 '15 at 16:28
  • Extremely useful! To ensure that also bigger fontsizes like headings look good, I'd suggest to define the length for \stackunder in ex though. – Florian Oct 14 '17 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.