# How do I pad a string argument to a macro to a set length string

So I have a string being passed as an argument to a macro. This string could contain up to 10 characters, but I need latex to 'pad' the string out so it ALWAYS contains 10 characters (by putting spaces at the end). If anyone can help me I would be very grateful.

• "string" is vague terminology in (La)TeX. Please specify exactly what tokens might form the "string". E.g., what about { and } and spaces and control sequences ? Do you need this in TeX? Do you need this in LaTeX? – Ulrich Diez Sep 28 '19 at 22:37
• Since TeX uses proportional fonts for most text, except typewriter, ten ‘I’ characters take up more horizontal space than ten ‘M’ characters. – Herb Schulz Sep 28 '19 at 22:47
• By the way: Space-tokens in horizontal mode form horizontal glue which can stretch and shrink. The amount of glue depends on the font, the sfcodes of preceding characters, \spaceskip and \xspaceskip. Even with monospaced fonts/fixed-pitch fonts in non-frenchspacing a space-token behind punctuation marks like . or : or ! or ? (which are not preceded by an upper cased letter) yields larger horizontal space than a space-token behind a lower case letter. In case you need this for visible output, you may wish to insert control-space \  or space-characters of catcode 12(other). – Ulrich Diez Sep 29 '19 at 0:26
• @HerbSchulz I guess you intended to say: "[...], ten 'M' characters take up more horizontal space than ten 'I' characters." ;-) – Ulrich Diez Sep 30 '19 at 16:51
• Will the string that's to be padded with whitespace contain whitespace characters? Please advise. – Mico Oct 3 '19 at 14:20

\documentclass{article}

\makeatletter
\def\zzzz#1#2 {%
\ifx\\#2%
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
{\zzzz{#1#2\space}}{\zz#1\space\space\space\space\space\space\space\space\space\space\zzz}%
}
\def\zz#1#2#3#4#5#6#7#8#9{#1#2#3#4#5#6#7#8#9\zzz}
\def\zzz#1#2\zzz{#1}

\begin{document}

\end{document}


produces a log (the | | just to make the padding visible).

|a         |
|abcdef    |
|a cd f    |
|0123456789|

• Your method fails to recognize spaces in the input string! – Steven B. Segletes Sep 28 '19 at 23:20
• @StevenB.Segletes a few more zz applied, fixed thanks. – David Carlisle Sep 29 '19 at 0:00
• Very nice hack! – Steven B. Segletes Sep 29 '19 at 0:02

I assume that your “strings” are sequences of printable character tokens.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
{
#3\prg_replicate:nn { #1 - \tl_count_tokens:n { #3 } } { #2 }
}
\ExplSyntaxOff

\begin{document}

\end{document}


This will produce on the console and in the log file

Pad to 10 with spaces
|a         |
|abcdef    |
|a cd f    |
|0123456789|
|a!!!!!!!!!|
|abcdef!!!!|
|a cd f!!!!|
|0123456789|
|a     |
|abcdef|
|a cd f|
|012345|
|a!!!!!|
|abcdef|
|a cd f|
|012345|


An error will be raised if the string is longer than stated.

As long as you don't mind the spaces being implicit, here is a way.

\documentclass{article}
\usepackage[T1]{fontenc}
\newcounter{tencount}
\makeatletter
{\catcode\ =12 \gdef\xsp{ }}
\newcommand\maketen{\begingroup\catcode\ =12 \maketenauxa}
\newcommand\maketenauxa[1]{%
\setcounter{tencount}{0}\def\tmp{}\maketenauxb#1\@sptoken\@sptoken
\@sptoken\@sptoken\@sptoken\@sptoken\@sptoken\@sptoken\@sptoken
\@sptoken\relax}
\def\maketenauxb#1#2\relax{%
\stepcounter{tencount}%
\ifnum\thetencount=10\relax\tmp\expandafter\endgroup\else
\maketenauxb#2\relax\fi
}
\makeatother
\begin{document}
*\maketen{Hi mom}*

\detokenize\expandafter{\tmp}*
\end{document}


If the spaces need to be explicit, here is a slight variation (only works with characters in the input stream, but not expandable macros).

\documentclass{article}
\usepackage[T1]{fontenc}
\newcounter{tencount}
\makeatletter
{\catcode\ =12 \gdef\xsp{ }}
\newcommand\maketen{\begingroup\catcode\ =12 \maketenauxa}
\newcommand\maketenauxa[1]{%
\setcounter{tencount}{0}\def\tmp{}\maketenauxb#1\space\space
\space\space\space\space\space\space\space
\space\relax}
\def\maketenauxb#1#2\relax{%
\expandafter\ifx\xsp#1\XYZ{\space}\else
\XYZ{#1}\fi%
\stepcounter{tencount}%
\ifnum\thetencount=10\relax\tmp\expandafter\endgroup\else
\maketenauxb#2\relax\fi
}
\makeatother
\begin{document}
*\maketen{Hi mom}*

*\detokenize\expandafter{\tmp}*
\end{document}


Usually curly opening braces { and curly closing braces } have a special meaning in (La)TeX:
The opening curly brace usually has category code 1(beginning of group).
The closing curly brace usually has category code 2(ending of group).

Under normal circumstances explicit beginning-of-group-character-tokens and explicit ending-of-group-character-tokens do not yield visible output within the output-file/within the .pdf-file.

But they yield visible output, e.g., when printed on screen, or written into external text files, or written to the .log-file, or when passed to \detokenize.

Besides this they can, despite their category code, occur in filenames that are gathered by TeX primitives like \openin, \openout or \input (which is renamed to \@@input in LaTeX 2ε).

The hash character (#) usually also has a special meaning in (La)TeX.
The hash character usually has category code 6(parameter).
Usually it cannot occur in normal text. In normal text you need to write \# or the like instead.
The hash character can occur in filenames that are gathered by TeX primitives like \openin, \openout or \input (which is renamed to \@@input in LaTeX 2ε).
Explicit character-tokens of category-code 6(parameter), i.e., hashes (#), will be duplicated when written to file or screen or when passed to \detokenize or \scantokens.

You did not reveal what you need this padding mechanism for.

Therefore it is hard to guess whether taking such hashes and such curly braces into account when calculating the amount of space characters that needs to be appended makes sense.

I decided to deliver a routine

\PadToLengthWithCharacter{⟨flags⟩}%
{⟨desired string length⟩}%
{⟨character to be used for padding⟩}%
{⟨string⟩}

⟨flags⟩ must have one of the values 00 / 01 / 10 / 11 .

If ⟨flags⟩=00, then an explicit character token of category code 6(parameter) will count as single character (i.e., hash-doubling is not taken into account) and a pair of matching explicit character tokens of category code 1(beginning of group) respective 2(ending of group) will not count as a character at all.

If ⟨flags⟩=01, then an explicit character token of category code 6(parameter) will count as single character (i.e., hash-doubling is not taken into account) and a pair of matching explicit character tokens of category code 1(beginning of group) respective 2(ending of group) will count as two characters.

If ⟨flags⟩=10, then an explicit character token of category code 6(parameter) will count as two characters (i.e., hash-doubling is taken into account) and a pair of matching explicit character tokens of category code 1(beginning of group) respective 2(ending of group) will not count as a character at all.

If ⟨flags⟩=11, then an explicit character token of category code 6(parameter) will count as two characters (i.e., hash-doubling is taken into account) and a pair of matching explicit character tokens of category code 1(beginning of group) respective 2(ending of group) will count as two characters.

The routine requires \detokenize from the ε-TeX extensions for detecting hashes. (\string# yields a single hash of category code 12(other). \detokenize{#} yields two hashes of category code 12(other).)

If the ⟨string⟩ has less characters than ⟨desired string length⟩, then ⟨character to be used for padding⟩ is used for padding. Otherwise the ⟨string⟩ is left as is.

The routine is expandable, i.e., can be used in full-expansion-contexts. Due to \romannumeral0-expansion the result is delivered after two expansion steps.

The routine does not expand ⟨string⟩ for detecting the ⟨string⟩'s length. Actually, for detecting the ⟨string⟩'s length, the routine counts tokens, not just characters. And the routine assumes that the ⟨character to be used for padding⟩-argument yields a single character. There is no error-checking implemented on that.

E.g., if ⟨string⟩=\textbf{#123}, then

• with ⟨flags⟩=00, ⟨string⟩'s length=5 is assumed:
• \textbf is taken for one character of the string.
• { is not taken for a character of the string.
• # is taken for one character of the string.
• 1 is taken for one character of the string.
• 2 is taken for one character of the string.
• 3 is taken for one character of the string.
• } is not taken for a character of the string.
• with ⟨flags⟩=01, ⟨string⟩'s length=7 is assumed:
• \textbf is taken for one character of the string.
• { is taken for one character of the string.
• # is taken for one character of the string.
• 1 is taken for one character of the string.
• 2 is taken for one character of the string.
• 3 is taken for one character of the string.
• } is taken for one character of the string.
• with ⟨flags⟩=10, ⟨string⟩'s length=6 is assumed:
• \textbf is taken for one character of the string.
• { is not taken for a character of the string.
• # is taken for two characters of the string.
• 1 is taken for one character of the string.
• 2 is taken for one character of the string.
• 3 is taken for one character of the string.
• } is not taken for a character of the string.
• with ⟨flags⟩=11, ⟨string⟩'s length=8 is assumed:
• \textbf is taken for one character of the string.
• { is taken for one character of the string.
• # is taken for two characters of the string.
• 1 is taken for one character of the string.
• 2 is taken for one character of the string.
• 3 is taken for one character of the string.
• } is taken for one character of the string.

Example containing the routine:

\documentclass[landscape]{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@Exchange, \UD@PassFirstBehindThirdToSecond
%%    \UD@removespace, \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstBehindThirdToSecond[3]{#2{#3}{#1}}%
\@ifdefinable\UD@removespace{\UD@firstoftwo{\def\UD@removespace}{} {}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument's first token is an explicit
%% space token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument (which mut be a single token!!!!) is an
%% explicit character token of category code 6(parameter)
%%.............................................................................
%% \UD@CheckWhetherHash{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case <argument which is to
%%                       be checked> is an explicit character token of category
%%                       code 6>}%
%%                     {<Tokens to be delivered in case <argument which is to
%%                       be checked> is not an explicit character token of
%%                       category code 6>}%
\newcommand\UD@CheckWhetherHash[1]{%
\romannumeral0%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\detokenize{#1}}{%
% space of whatsoever catcode
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}{% space of catcode 6
\UD@firstoftwo\expandafter{} \UD@firstoftwo
}%
}{% space of whatsoever catcode
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}%
}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\detokenize{#1}}{%
% no hash
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}{% hash
\UD@firstoftwo\expandafter{} \UD@firstoftwo
}%
}{% no hash
\UD@firstoftwo\expandafter{} \UD@secondoftwo
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% Fork whether argument is the token "1" or is not token "1":
%%.............................................................................
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
\@ifdefinable\UD@OneSelect{\long\def\UD@OneSelect#1!0!1!#2#3!!{#2}}%
\newcommand\UD@OneFork[1]{%
\romannumeral0%
\UD@CheckWhetherNoExclam{#1}{%
\UD@OneSelect
!#1!1!{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
!0!#1!{\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
!0!1!{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
!!%
}{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
%%=============================================================================
% #1 = <flags>
% #2 = <desired string length>
% #3 = <character to be used for padding>
% #4 = <string>
\romannumeral0%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\romannumeral\number\number#2 000}%
{\UD@secondoftwo{}}%
}%
%%=============================================================================
% #1 = <flags>
% #2 = <character to be used for padding>
% #3 = <string where things get removed>
% #4 = <amount of letters m denoting (remaining) desired string length>
% #5 = <string where things get added>
\UD@CheckWhetherNull{#4}{ #5}{%
\UD@CheckWhetherNull{#3}{%
\UD@PassFirstBehindThirdToSecond{#5#2}{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@firstoftwo{}#4}{%
\UD@PassFirstBehindThirdToSecond{}{\UD@secondoftwo{}}%
}%
}%
}{%
\UD@PassFirstBehindThirdToSecond{#5}{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@firstoftwo{}#4}{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@removespace#3}%
{\UD@secondoftwo{}}%
}%
}{%
\UD@CheckWhetherBrace{#3}{%
\expandafter\UD@OneFork\expandafter{\UD@secondoftwo#1}{%
\expandafter\UD@CheckWhetherNull
\expandafter{\UD@firstoftwo{}#4}%
{\UD@PassFirstBehindThirdToSecond{}}{%
\expandafter\expandafter
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter\expandafter
\expandafter{\UD@firstoftwo{\UD@firstoftwo{}}#4}%
}%
}{\UD@PassFirstBehindThirdToSecond{#4}}%
{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@secondoftwo{}#3}{\UD@secondoftwo{}}%
}%
}{%
\expandafter\UD@CheckWhetherHash
\romannumeral0\UD@ExtractFirstArgLoop{#3\UD@SelDOm}{%
\expandafter\UD@OneFork\expandafter{\UD@firstoftwo#1}{%
\UD@firstoftwo{%
\expandafter\UD@CheckWhetherNull
\expandafter{\UD@firstoftwo{}#4}%
{\UD@PassFirstBehindThirdToSecond{}}{%
\expandafter\expandafter
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter\expandafter
\expandafter{\UD@firstoftwo{\UD@firstoftwo{}}#4}%
}%
}%
}{\UD@secondoftwo{}}%
}{\UD@secondoftwo{}}%
{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@firstoftwo{}#4}}{%
\expandafter\UD@PassFirstBehindThirdToSecond
\expandafter{\UD@firstoftwo{}#3}%
{\UD@secondoftwo{}}%
}%
}%
}%
}%
}%
}%
}%
%%=============================================================================
\makeatother

\makeatletter
%%==== This section of code is only for decorating the text with a grid: ======
%% Draw quads for characters of \ttfamily-font:
%%-----------------------------------------------------------------------------
\usepackage{color}
\newbox\mybox
\newskip\UD@GridruleThickness
\UD@GridruleThickness=0.4pt\relax
\setbox\mybox=\vbox{%
{%
\normalfont\ttfamily\selectfont
\kern\dp\strutbox
\fboxsep=-\UD@GridruleThickness
\fboxrule=\UD@GridruleThickness % <-width of frame around the whole grid
\kern-\fboxrule
\kern-\fboxsep
\noindent
\kern-.5\UD@GridruleThickness
\kern-\fboxsep
\kern-\fboxrule
\@tempdima=#2\baselineskip
\edef\tempa{\the\@tempdima}%
\settowidth\@tempdima{X}%
\@tempdima=#1\@tempdima
\edef\tempb{\the\@tempdima}%
\@ifundefined{color}{}{\color#3}%
\fbox{%
\fboxsep=0pt
\fboxrule=0pt
\@ifundefined{colorbox}{\mbox}{\colorbox#4}%
{%
\parbox[b][\tempa]{\tempb}{%
\@ifundefined{color}{}{\color#3}%
\@tempdima=\baselineskip
\edef\tempa{\the\@tempdima}%
\offinterlineskip
\leavevmode\vskip0pt%
\noindent\hrule height \UD@GridruleThickness depth 0pt%
\noindent\hbox to\hsize{%
\vrule height\tempa depth 0pt width \UD@GridruleThickness
\settowidth\@tempdima{X}%
\kern\@tempdima\relax
}\hfill\vrule height\tempa depth 0pt width \UD@GridruleThickness
}%
}\vfill
\hrule height \UD@GridruleThickness depth 0pt%
}%
}%
}%
}%
}%
\ht\mybox=0pt
\dp\mybox=0pt
\wd\mybox=0pt
\box\mybox
}%
%%=============================================================================
\makeatother

\csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}%
\csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\textwidth=\paperwidth
\oddsidemargin=2cm
\marginparsep=.125\oddsidemargin
\marginparwidth=\oddsidemargin
\evensidemargin=\oddsidemargin
\textheight=\paperheight
\topmargin=2cm
\footskip=.5\topmargin
\pagestyle{plain}

\parskip=0ex
\parindent=0ex

\begin{document}
\ttfamily\frenchspacing\sloppy

% Let's draw some quads for making counting amount of characters more easy:
\Quadpaperphantom{129}{38}{[cmyk]{0, 0, 0, 0.4}}{[rgb]{1, 1, 1}}%

% Let's write some numbers for making counting amount of characters more easy:
\textcolor{red}{123456789012345678901234567890123456789012345678901234567890%
123456789012345678901234567890123456789012345678901234567890123456789}\\%

Under normal circumstances curly braces are not visible and \verb|#| cannot be
used---therefore flags=00 is sufficient:\\

\\

\\

\\

\\

\\

\\

% Be aware that horizontal glue coming from spces will be removed at
% beginnings of lines, thus the \null-box:
\\

When things are written to screen, curly braces are visible and \verb|#| gets doubled. Same with \verb|\detokenize|.\\
Therefore in such situations flags=11 might be useful:\\

\\