# Thoughts on turning this DropCap/Lettrine code into a macro

I am aware there are some packages available for Dropped Capitals, esp. the lettrine.sty package for LaTeX. However, I'm learning TeX from fundamentals and decided to figure out how to do it myself. I've written an implementation that works and is flexible enough for my needs and would now like to turn it into a macro, but I'm not sure how best to do so. To start here is an example (I'm using csplain, so you need to run this MWE with pdftex --fmt=pdfcsplain):

\input opmac
\input cs-schola

{\dimen0 = 3.67\baselineskip
\divide \dimen0 by 65536
\setbox0 = \vbox{\hbox{\valign{%
#\vfil\cr
\hbox{\typobase\typoscale[\magstep1/]}\cr
\noalign{\hskip 1pt}
\hbox{\typobase\typosize[\number\dimen0/]W}\cr}}}
\dimen1 = .8\wd0 % base indent
\dimen0 = \hsize \advance\dimen0 by -\dimen1 % base line length
\hbox to \dimen1{\hss\box0}\par % shifts DropCap into left margin slightly
\vskip-3.0\baselineskip % pushes text up next to DropCap
\dimen2 = \dimen1 \advance\dimen2 by 1pt % line 1 indent
\dimen3 = \dimen0 \advance\dimen3 by -1pt % line 1 length
\dimen4 = \dimen1 \advance\dimen4 by -3pt % line 2 indent
\dimen5 = \dimen0 \advance\dimen5 by 3pt % line 2 length
\dimen6 = \dimen1 \advance\dimen6 by -7pt % line 3 indent
\dimen7 = \dimen0 \advance\dimen7 by 7pt % line 3 length

\parshape = 4 \dimen2 \dimen3 \dimen4 \dimen5 \dimen6 \dimen7 0pt \hsize
\noindent{\caps\rm e're here,''} Mother called out, waking me from my nap on the back
seat of the car. When you're a kid who can get carsick just being driven around the
block, you learn to sleep as much as possible on long motor trips. This one was
{\caps\rm 139} curvy miles. Our {\caps\rm 1928} black Buick sedan crunched up the
gravel driveway to the {\em cottage}. Well, that's what summer homes were called then,
even large ones like the Schuneman's. We were in {\em Eagles~Mere,} a Victorian village
in the Pocono Mountains of Pennsylvania $\ldots$\par}

\bye


This implementation is rather hard coded for a DropCap depth of 3 lines, but I'm ok with that limitation. Most of the code is "generic" to font and size, except for dimen2 through dimen7 which always need adjustments depending on the font. The lines

\divide \dimen0 by 65536
\hbox{\typobase\typosize[\number\dimen0/]W}


could be improved if I could figure out how to get OPmac to honor \ptunit but that is a side issue.

From what I can tell I need the following parameters:

1. optional character before the drop cap (a left double quote in this example)
2. optional hskip value between the character before the drop cap and the drop cap itself
3. the drop cap caracter
4. the "base indent" - this also determines how far into the margin the drop cap will protrude. Currently the value is a fraction of the drop caps width. (.8 in this example)
5. Line 1 "shift" value
6. Line 2 "shift" value
7. Line 3 "shift" value

If I'm doing something obviously wrong or bad, or if there is a simpler more obvious implementation, please chime in.

I'm NOT asking for someone to write me a macro (unless you want to share with us), but rather some ideas on actually doing so.

• related: tex.stackexchange.com/q/769/1410 – morbusg Jun 26 '14 at 8:50
• Ultimately you'd need a different \parshape for each and every letter in the alphabet(s). I think I remember seeing some talk about LuaTeX being able to figure out the shape of a letter, so I guess that could be used, too. – morbusg Jun 26 '14 at 8:59
• traditional drop caps were often "illuminated", with additional decoration resulting in a square or rectangular piece of type. doing anything else with metal would have been a nightmare, so this "workaround" is not surprising. @morbusg suggests that for a shaped drop cap, a different \parshape would be needed for each letter of the alphabet. it's worse than that -- a different \parshape would be needed for every letter times however many different depths (number of lines) are to be involved with the drop cap. i also wonder about using "L" as a "shaped" drop cap ... – barbara beeton Jun 26 '14 at 13:24

When I'm writing a macro, I'm first thinking about user interface. For your purpose, we suggest the macro \Capinsert which will be used before paragraph:

\Capinsert First letter is scaled, this means F in this case.


User may insert a optional left material. We can use \optdef mentioned in OPmac trick 0067 but the optional parameter is separated by brackets [] and user can write \thefontscale[...] here (for example) and the brackets [] are not able to be nested. Thus, user can be confused. This is the reason why I suggest to use {} as an optional parameter. For example:

\Capinsert {material llaped to the left} First letter is scaled ...


This decision needs little more macro programming:

\def\Capinsert{\def\leftCapmaterial{}\futurelet\next\CapinsertA}
\def\CapinsertA{\ifx\next\bgroup \expandafter\CapinsertB \else \expandafter\CapinsertC \fi}
\def\CapinsertB #1{\def\leftCapmaterial{#1}\CapinsertC}
\def\CapinsertC #1{...


\Capinsert checks by \futurelet if there is an open brace \bgroup. If this is true, the \CapinsertB scans the optional parameter and saves it to \leftCapmaterial macro. Else \CapinsertC is processed where the core of the macro is realised.

Now, we suggest the way of data declaration. It may looks like:

\newdimen\ptem     \ptem=.1em
\newdimen\Capsize  \Capsize=44\ptem
\newdimen\Capabove \Capabove=8\ptem

\declCap {default} {0;0,0,0}
\declCap A {1;6,2,-2}
...
\declCap W {3;0,4,6}
...


First, the \ptem is declared here. It is 1pt when 10pt font is used. But if user declares \typosise[12/14] before the data declaration (for example) and whole document is scaled, then the unit used for letter correction is scaled too. This is desired behavior. The \ptem means "pt unit dependent on em".

The \Capsize declares the size of the scaled letters. And \Capabove declares the amount of raising scaled letters above the first baseline of the paragraph. If it it zero then top of scaled letter is equal to the first baseline.

The data about letters are declared in the way:

\declCap <letter> {protrude left; first line left, second line left, etc.}


The {default} data is used when the letter hasn't its own declaration. The comma separated list can include arbitrary number of numbers. The corresponding number of lines in the paragraph will be shifted. The numbers can include decimal point, can be negative and they are expressed in \ptem units.

The macro code follows. The \declCap only saves data to the macro with the name \cap:=<letter>. The \sxdef from OPmac is used here:

\def\declCap #1#2{\sxdef{cap:=#1}{#2}}


The \CapinsertC reads the letter to be scaled as #1 and does following:

\def\CapinsertC #1{\par
\isdefined{cap:=#1}\iftrue \edef\tmp{\csname cap:=#1\endcsname}%
\else   \edef\tmp{\csname cap:=default\endcsname}\fi
\setbox0=\hbox{{\thefontsize[\expandafter\ignorept\the\Capsize]#1}}%
\expandafter \CapinsertD \tmp,,%
\noindent\kern-\firstlineindent
\rlap{\kern-\protrudeCap\ptem\llap{\leftCapmaterial}%
\vbox to0pt{\kern-\Capabove\box0\vss}}%
\kern\firstlineindent
}


The \tmp includes declared data or default. The \box0 includes the scaled letter. The \thefontsize parameter needs the dimen without pt. So, the \ignorept macro from OPmac removes the pt letters expanded by \the primitive. The \Capinsert is executed followed by data and by two commas. It prepares and executes \parshape primitive and saves the indentation of the first line to the \firstlineindent. The \noindent starts indented by \firslineindent. So, wee need to return back by negative kern, to do \rlap{scaled letter} and to return by \kern\firstindent. The scaled letter is realized by

\kern-\protrudeCap\ptem \llap{left material}\vbox{shifted \box0}


The last thing is to scan data prepared by the form

\CapinsertD protrude left; first line left, second line left, ... ,,%


This is done by:

\def\CapinsertD #1;{\tmpnum=1 \let\firstlineindent=\undefined
\def\parshapeparams{}\def\protrudeCap{#1}\CapinsertE}
\def\CapinsertE #1,{\ifx,#1,\parshape =\tmpnum \parshapeparams 0pt \hsize
\else
\edef\parshapeparams{\parshapeparams\the\tmpdim}%
\ifx\firstlineindent\undefined \let\firstlineindent\parshapeparams \fi
\edef\parshapeparams{\parshapeparams\the\tmpdim}%
\expandafter \CapinsertE \fi
}


First, \CapinsertD reads protrude left, save it to \protrudeCap. The initial values of \tmpnum, \firstlineindent, \parshapeparams are set here. The \CapinsertE reads line indentations in the loop. The loop ends when #1 is empty, i. e. between the last two commas appended to the data. The \tmpnum counts number of indented lines plus 1. The indentation is calculated by \wd0 minus data-item minus protrude. The result is added to the \parshapeparams. Next, the line width is calculated as \hsize minus indentation and added to the parshapeparams too. When the loop ends, we have \parshapeparams ready for \parshape primitive. Only \tmpnum is prepended and 0pt \hsize is appended (this declares normal line).

Finally, the working example is here:

\input opmac
\input cs-schola
%\typosize[12/15]

%% macros

\def\declCap #1#2{\sxdef{cap:=#1}{#2}}
\def\Capinsert{\def\leftCapmaterial{}\futurelet\next\CapinsertA}
\def\CapinsertA{\ifx\next\bgroup \expandafter\CapinsertB \else \expandafter\CapinsertC \fi}
\def\CapinsertB #1{\def\leftCapmaterial{#1}\CapinsertC}
\def\CapinsertC #1{\par
\isdefined{cap:=#1}\iftrue \edef\tmp{\csname cap:=#1\endcsname}%
\else   \edef\tmp{\csname cap:=default\endcsname}\fi
\setbox0=\hbox{{\thefontsize[\expandafter\ignorept\the\Capsize]\Capprefix#1}\kern\Capafter}%
\expandafter \CapinsertD \tmp,,%
\noindent\kern-\firstlineindent
\rlap{\kern-\protrudeCap\ptem\llap{\leftCapmaterial}%
\vbox to0pt{\kern-\Capabove\box0\vss}}%
\kern\firstlineindent
}
\def\CapinsertD #1;{\tmpnum=1 \let\firstlineindent=\undefined
\def\parshapeparams{}\def\protrudeCap{#1}\CapinsertE}
\def\CapinsertE #1,{\ifx,#1,\parshape =\tmpnum \parshapeparams 0pt \hsize
\else
\edef\parshapeparams{\parshapeparams\the\tmpdim}%
\ifx\firstlineindent\undefined \let\firstlineindent\parshapeparams \fi
\edef\parshapeparams{\parshapeparams\the\tmpdim}%
\expandafter \CapinsertE \fi
}
\def\hboxshift#1#2{\vbox to0pt{\vss\hbox{#2}\kern-#1}}

%% data declarations:

\newdimen\ptem     \ptem=.1em
\newdimen\Capsize  \Capsize=44\ptem
\newdimen\Capabove \Capabove=8\ptem
\newdimen\Capafter \Capafter=1\ptem
\def\Capprefix{\localcolor\Red}

\declCap {default} {0;0,0,0}
\declCap W {3;0,4,6}
\declCap A {1;6,2,-2}
\declCap L {0;9,0,0}
% \declCap B ... etc.

%% document:

\Capinsert {\thefontscale[\magstep3]\localcolor\Grey\hboxshift{4pt}{}\kern.1em}
W{\caps\rm e're here,''}
Mother called out, waking me from my nap on the back seat of the car. When
you're a kid who can get carsick just being driven around the block, you
learn to sleep as much as possible on long motor trips. This one was
{\caps\rm 139} curvy miles. Our {\caps\rm 1928} black Buick sedan crunched
up the gravel driveway to the {\em cottage}. Well, that's what summer homes
were called then, even large ones like the Schuneman's. We were in {\em
Eagles~Mere,} a Victorian village in the Pocono Mountains of Pennsylvania
$\ldots$

\Capinsert Another example is here.
Mother called out, waking me from my nap on the back seat of the car. When
you're a kid who can get carsick just being driven around the block, you
learn to sleep as much as possible on long motor trips. This one was
{\caps\rm 139} curvy miles. Our {\caps\rm 1928} black Buick sedan crunched
up the gravel driveway to the {\em cottage}. Well, that's what summer homes
were called then, even large ones like the Schuneman's. We were in {\em
Eagles~Mere,} a Victorian village in the Pocono Mountains of Pennsylvania
$\ldots$

\Capinsert Lettrine package does something similar.
Mother called out, waking me from my nap on the back seat of the car. When
you're a kid who can get carsick just being driven around the block, you
learn to sleep as much as possible on long motor trips. This one was
{\caps\rm 139} curvy miles. Our {\caps\rm 1928} black Buick sedan crunched
up the gravel driveway to the {\em cottage}. Well, that's what summer homes
were called then, even large ones like the Schuneman's. We were in {\em
Eagles~Mere,} a Victorian village in the Pocono Mountains of Pennsylvania
$\ldots$

\bye


Edit: I've added global parameters \Capafter, \Capprefix and the following picture:

• Wow! It is extremely kind of you to post such a detailed response. I'm eager to delve into this in detail later today. Thank you. – acarlow Jun 26 '14 at 18:27
• All macros from OPmac are documented by this level of detail. But, I am sorry, in somewhat unusable language :( – wipet Jun 26 '14 at 18:45
• Ok, I took my first look at this and it's beginning to make sense. I especially like the declCap idea. That is very useful for a long text where multiple chapters start with the same drop cap letters. Your documentation style is very "literate programing"-like which I'm sure is no accident. – acarlow Jun 26 '14 at 20:35
• Not only do I find this macro to be acceptable, it is also quite instructive. Many thanks. I do hope the other using plain TeX will take advantage of this as well! – acarlow Jun 29 '14 at 5:44
• I just thought I'd add one final comment about this code. It's actually pretty amazing. Not only does it solve the problem in very generic way, but it is (in my opinion) an excellent example of how to break down a problem programmatically in TeX, as well as how to do things like handling an arbitrary number of arguments, optional parameters, "hooks" for customization, the use of relative sizes, the use of delimiters so everything doesn't have to be bounded by '{' and '}', etc. I found it all to be inspiring and instructive. Thanks again, @wipet! I wonder if this is useful enough for ctan? – acarlow Jul 3 '14 at 3:31