12

I want to typeset a book with some \parshape command, for example:

\documentclass{article}
\usepackage{lipsum}

\begin{document}
\parshape=4
0.2\textwidth 0.8\textwidth
0.2\textwidth 0.8\textwidth
0.2\textwidth 0.8\textwidth
0pt \textwidth
\lipsum
\end{document}

But since the lines and width may changes frequently, so I want to define a command like this

\newcounter{foo}
\setcounter{foo}{0}
\newcommand{\MyParShape}[3]
{
\parshape=#1
\loop
#2\textwidth #3\textwidth 
\addtocounter{foo}{1}
\if\thefoo<#1-1\repeat
0pt \textwidth
}

So I can call it like

\MyParShape{5}{0.5}{0.5}

or

\MyParShape{10}{0.8}{0.2}

But this does not work, I'm not an expert of TeX primitives, can somebody help?

lockstep
  • 250,273
ocean
  • 981

5 Answers5

9

As Harald says, the key here is that you need everything expanded. You can do that in a temporary variable, as he shows. Alternatively, you can use something like an expandable 'repeat n times' approach. Here, I'm assuming e-TeX is available

\documentclass{article}
\usepackage{lipsum}
\makeatletter
\newcommand*\replicate[1]{%
  \expandafter\replicate@aux\romannumeral\number\numexpr #1\relax000Q{}
}
\newcommand*\replicate@aux[1]{\csname replicate@aux@#1\endcsname}
\newcommand\replicate@aux@m{}
\long\def\replicate@aux@m#1Q#2#3{\replicate@aux#1Q{#2#3}{#3}}
\newcommand\replicate@aux@Q[2]{#1}
\DeclareRobustCommand*\MyParShape[3]{%
    \parshape = #1
      \replicate{#1 -1}{#2\textwidth #3\textwidth}%
      0pt\textwidth
}
\newcommand*\my@parshape{}
\makeatother
\begin{document}
\MyParShape{3}{0.6}{0.4}
\lipsum
\end{document}
Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
5

If the paragraph shape you need is like that, it's simpler to obtain it with

\hangindent=.2\textwidth \hangafter=-3
egreg
  • 1,121,712
4

The reason your approach doesn't work is that it does not expand fully in TeX's mouth. Now building a fully expandable version might be rather involved. An easier approach is probably to first build up a command sequence containing your finished \parshape and the executing that. In the following complete example I use a token register:

\documentclass{article}
\usepackage{lipsum}

\newtoks\pstoks
\newcounter{pscnt}

\makeatletter
\newcommand{\parshapestart}[1]{\pstoks{\parshape=#1 }}
\newcommand{\parshapeappend}[1]{\pstoks\expandafter{\the\pstoks#1}}
\newcommand{\parshapeadd}[3]
  {
    \setcounter{pscnt}{0}
    \loop
    \parshapeappend{#2\textwidth #3\textwidth}
    \addtocounter{pscnt}{1}
    \ifnum\c@pscnt<#1\repeat
  }
\newcommand{\parshapeend}[1][0pt \textwidth]
  {\parshapeappend{#1}\the\pstoks}
\makeatother

\begin{document}

\parshapestart{6}
\parshapeadd{3}{0.2}{0.8}
\parshapeadd{2}{0.1}{0.9}
\parshapeend
\lipsum
\end{document}
  • You can avoid the counter by using an expandable repetition routine, as seen in http://tex.stackexchange.com/questions/16189/repeat-command-n-times. My first approach was to do that inside an \edef, but as you posted a similar answer I've gone for avoiding additional storage and using full expansion! – Joseph Wright May 13 '11 at 07:19
2

For LaTeX (and plainTeX) there is the shapepar package which provides a set of predefined shapes but also allows you to define your own. However, it doesn't solve the issue with the unexpandable loop for you.

The command \shapepar should be used at the beginning of a paragraph, and it applies to the entire paragraph. There is one optional length parameter: a fixed scale, <scale len>; and one required parameter: a description of the shape, <shape spec>.

\shapepar [<scale len>]{<shape spec>} Text of the paragraph
Martin Scharrer
  • 262,582
0
\documentclass{article}
\usepackage{lipsum}
\newcount\shapectr
\newcommand\myParShape[3]{%
  \shapectr=0
  \def\shapeLength{}
  \loop
    \edef\shapeLength{\shapeLength #2\textwidth #3\textwidth }
    \advance\shapectr by 1
    \ifnum\the\shapectr<#1\relax
  \repeat
  \expandafter\parshape\numexpr #1+1\relax  
  \shapeLength
  0pt \textwidth%
}

\begin{document}
\myParShape{3}{0.2}{0.8}
\lipsum
\end{document}