[Tex/LaTex] ‘Poster’ fountain pen nib style text

calligraphyfontstikz-pgf

This question led to a new package:
spath3

(Technically, this question led to a subpackage of the spath3 package; the spath3 package provides some foundations on which a TikZ library calligraphy was built and the TikZ library is bundled with the spath3 library.)

While there are many fine fonts available, occasionally it would be nice to simulate the effects you can get 'by hand' with a poster nib (for a fountain pen). This would let you create a number of 'special effects' easily. Is there an easy way to do this (I guess featuring Tikz).

Best Answer

Update This is now available on CTAN (see above). Bugfixes and improvements will generally be available on github before being uploaded to CTAN. If getting the files from there, note that the relevant .dtx file is spath3.dtx. This generates all the necessary files.


Update: I've added this to the TeX-SX package which was announced at What are your favourite TikZ/PGF answers?. The dtx can be downloaded from http://bazaar.launchpad.net/+branch/tex-sx/files. Run pdflatex on it to produce the sty file and the documentation.


Hmm, I guess you're looking for something a bit like this:

calligraphic TeX

It may not be easy, but I'll have a go ...

\documentclass{standalone}
\usepackage{tikz}

%%% TODO:
%
% 1. Be able to define a number of different pens and use appropriately
% 2. Add an annotation style (arrows with numbers)
% 3. Check how this interacts with colours and so forth

\makeatletter
\long\def\ge@addto@macro#1#2{%
  \begingroup
  \toks@\expandafter\expandafter\expandafter{\expandafter#1#2}%
  \xdef#1{\the\toks@}%
  \endgroup}

\long\def\ge@addbefore@macro#1#2{%
  \begingroup
  \toks@\expandafter\expandafter\expandafter{\expandafter#2#1}%
  \xdef#1{\the\toks@}%
  \endgroup}

\long\def\ge@addbefore@macro#1#2{%
  \begingroup
  \toks@\expandafter\expandafter\expandafter{\expandafter#2#1}%
  \xdef#1{\the\toks@}%
  \endgroup}

\long\def\g@addbefore@macro#1#2{%
  \begingroup
    \toks@\expandafter{\expandafter#2#1}%
    \xdef#1{\the\toks@}%
  \endgroup}

\pgfkeys{/tikz/store path in/.code={\pgfsyssoftpath@getcurrentpath{\my@path}\global\let#1=\my@path}}

\pgfkeys{/tikz/define pen/.style={preaction={store path in=\penpath}}}

\def\pen{\path[define pen]}
\def\calligraphy{\path[calligraphy]}

\pgfkeys{/tikz/calligraphy/.style={preaction={store path in=\calligraphypath},preaction={stroke with calligraphy pen=\penpath}}}
\pgfkeys{/tikz/stroke with calligraphy pen/.code={\thickenpath{\calligraphypath}{#1}}}

\def\thickenpartialpath#1#2{%
  \def\thick@path{}%
  \def\thick@action##1##2##3{%
    \edef\this@action{\string##1}%
    \ifx\this@action\cal@moveto
    \ifx\thick@path\@empty
    \else
     \lengthofsoftpath{\thick@path}
     \ifnum\value{softpathlength}=1
      \thickenpartialpathwithstroke{#1}{\thick@path}
     \else
      \thickenpartialpathwithfill{#1}{\thick@path}
     \fi
     \def\thick@path{}%
    \fi
    \fi
    \def\to@add{##1{##2}{##3}}
  }
  \let\thick@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\thick@path\expandafter\thick@action\expandafter\thick@append#2\relax
  \lengthofsoftpath{\thick@path}
  \ifnum\value{softpathlength}=1
  \thickenpartialpathwithstroke{#1}{\thick@path}
  \else
  \thickenpartialpathwithfill{#1}{\thick@path}
  \fi
}

\def\thickenpartialpathwithfill#1#2{%
  \startsoftpath{#2}
  \let\pen@sx=\tr@xlen
  \let\pen@sy=\tr@ylen
  \endsoftpath{#2}
  \let\pen@ex=\tr@xlen
  \let\pen@ey=\tr@ylen
  \startsoftpath{#1}
  \let\path@sx=\tr@xlen
  \let\path@sy=\tr@ylen
  \endsoftpath{#1}
  \let\path@ex=\tr@xlen
  \let\path@ey=\tr@ylen
  \translatesoftpath{#1}{\pen@sx}{\pen@sy}%
  \let\lower@path=\trpath
  \translatesoftpath{#2}{\path@ex}{\path@ey}
  \let\right@path=\trpath
  \reversesoftpath{#1}
  \translatesoftpath{\revpath}{\pen@ex}{\pen@ey}
  \let\upper@path=\trpath
  \reversesoftpath{#2}
  \translatesoftpath{\revpath}{\path@sx}{\path@sy}
  \let\left@path=\trpath
  \catsoftpath{\lower@path}{\right@path}
  \catsoftpath{\catpath}{\upper@path}
  \catsoftpath{\catpath}{\left@path}
  \def\to@add{\pgfsyssoftpath@closepath{0pt}{0pt}}
  \ge@addto@macro\catpath\to@add
  \pgfsyssoftpath@setcurrentpath{\catpath}
  \pgfsyssoftpath@flushcurrentpath
  \pgfusepath{fill}
}

\def\thickenpartialpathwithstroke#1#2{%
  \startsoftpath{#2}
  \let\pen@sx=\tr@xlen
  \let\pen@sy=\tr@ylen
  \translatesoftpath{#1}{\pen@sx}{\pen@sy}%
  \pgfsyssoftpath@setcurrentpath{\trpath}
  \pgfsyssoftpath@flushcurrentpath
  \pgfusepath{stroke}
}

\def\catsoftpath#1#2{
  \let\catpath=#1
  \expandafter\trimfirst#2\relax
  \ge@addto@macro\catpath\trimed@path
}

\def\trimfirst#1#2#3#4\relax{%
  \edef\this@action{\string#1}%
  \ifx\this@action\cal@moveto
  \def\trimed@path{#4}%
  \else
  \def\trimed@path{#1{#2}{#3}#4}%
  \fi
}

\def\walksoftpath#1#2#3#4{
  \let\path@cmd=#4%
  \ifx\path@cmd\relax
  \let\next@action=\@gobblefour
  \else
  \let\next@action=\modifypath
  \fi
  \next@action{#1}{#2}{#3}{#4}%
}

\def\modifypath#1#2#3#4#5#6{%
  #2{#4}{#5}{#6}
  #3#1\to@add
  \walksoftpath{#1}{#2}{#3}}

\newcounter{softpathlength}
\def\lengthofsoftpath#1{%
  \def\len@path{}%
  \setcounter{softpathlength}{0}%
  \def\len@action##1##2##3{%
    \stepcounter{softpathlength}%
    \edef\to@add{}%
  }
  \let\len@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\len@path\expandafter\len@action\expandafter\len@append#1\relax
  
}

\def\translatesoftpath#1#2#3{%
  \def\tr@path{}%
  \def\tr@action##1##2##3{%
    \pgfmathsetmacro{\tr@xlen}{##2+#2}
    \pgfmathsetmacro{\tr@ylen}{##3+#3}
    \edef\to@add{\noexpand##1{\tr@xlen pt}{\tr@ylen pt}}
  }
  \let\tr@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\tr@path\expandafter\tr@action\expandafter\tr@append#1\relax
  \global\let\trpath=\tr@path
}

\def\trimlast#1\pgfsyssoftpath@movetotoken\relax{#1}

\foreach \cal@cpt in {
  moveto,
  lineto,
  curvetosupporta,
  curvetosupportb,
  curveto,
  rectcorner,
  rectsize%
} {
\expandafter\xdef\csname cal@\cal@cpt\endcsname{\expandafter\string\csname pgfsyssoftpath@\cal@cpt token\endcsname}
}
\edef\cal@closepath{\string\pgfsyssoftpath@closepath}

\def\reversesoftpath#1{%
  \def\re@path{}%
  \def\re@action##1##2##3{%
    \edef\this@re@action{\string##1}
    \ifx\this@re@action\cal@curvetosupporta
     \edef\to@add{\noexpand\pgfsyssoftpath@curvetosupportbtoken{##2}{##3}\noexpand\pgfsyssoftpath@curvetotoken}
    \else
     \ifx\this@re@action\cal@curvetosupportb
      \edef\to@add{\noexpand\pgfsyssoftpath@curvetosupportatoken{##2}{##3}}
     \else
      \ifx\this@re@action\cal@curveto
       \edef\to@add{{##2}{##3}}
      \else
       \edef\to@add{{##2}{##3}\noexpand##1}
      \fi
     \fi
    \fi
    }
  \let\re@append=\ge@addbefore@macro
  \expandafter\walksoftpath\expandafter\re@path\expandafter\re@action\expandafter\re@append#1\relax
  \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\re@path\expandafter\expandafter\expandafter{\expandafter\trimlast\re@path\relax}
  \g@addbefore@macro\re@path\pgfsyssoftpath@movetotoken
  \global\let\revpath=\re@path
}

\def\startsoftpath#1{%
  \def\st@path{}%
  \def\st@action##1##2##3{%
    \def\tr@xlen{##2}%
    \def\tr@ylen{##3}%
    \def\st@action####1####2####3{}
  }
  \let\st@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\st@path\expandafter\st@action\expandafter\st@append#1\relax
}

\def\endsoftpath#1{%
  \def\end@path{}%
  \def\end@action##1##2##3{%
    \def\tr@xlen{##2}%
    \def\tr@ylen{##3}%
  }
  \let\end@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\end@path\expandafter\end@action\expandafter\end@append#1\relax
}

\def\thickenpath#1#2{%
  \def\th@path{}%
  \def\th@action##1##2##3{%
    \edef\this@action{\string##1}
    \ifx\this@action\cal@moveto
     \ifx\th@path\@empty
    \else
      \thickenpartialpath\th@path{#2}
     \fi
    \def\th@path{}
    \fi
    \def\to@add{##1{##2}{##3}}
  }
  \let\th@append=\ge@addto@macro
  \expandafter\walksoftpath\expandafter\th@path\expandafter\th@action\expandafter\th@append#1\relax
  \thickenpartialpath\th@path{#2}
}

\makeatother

\begin{document}
\begin{tikzpicture}
\pen (-.25,-.125) -- (0,0) ++(.125,.0625) -- (.25,.125);
\calligraphy (0,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(2,0) (1,0) -- ++(0,-2) -- ++(-.125,-.125) .. controls +(.1,.1) and +(-.1,-.1) .. ++(.35,0);
\calligraphy (3,-1) .. controls +(-1.5,1) and +(-1.5,-1) .. ++(0,-2) ++(-1.1,1) -- ++(1,0) -- ++(-.25,-.25);
\calligraphy (3.25,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(1.5,-2) ++(0,2.5) .. controls +(-.75,.75) and +(.75,-.75) .. ++(-1.5,-3);
\calligraphy (0,-4) .. controls +(1,1) and +(-1,-1) .. ++(5,0);
\end{tikzpicture}
\end{document}

First, the history. I enjoy calligraphy, I like producing documents in TeX, and lately with the addition of TikZ I've started enjoying producing graphical stuff so the combination of calligraphy with TikZ has been at the back of my mind for a long time. There have been a few times when reading a question here that I've though "I could do that with a calligraphy pen.". Also, when I found that Inkscape had a calligraphy tool then I thought it would be great in combination with a graphics tablet, but I was never very happy with the result - they weren't elegant enough to replace a hand-drawn one, nor precise enough to have the benefits of being computer-rendered (though being able to make a screencast can be useful: see this page for such a screencast, also by looking at the source of the SVGs you can see that Inkscape's method for doing calligraphy is ... not elegant.).

Anyway, I was idly perusing the PGF manual when I came across the concept of soft paths and realised that this was the key to doing calligraphy. Basically, (see the manual for more details) when TikZ/PGF constructs a path then it does so in several layers and at each step one can interrupt the process to mess with the data so far before passing it on to the next. One step is a soft path. This is the path in basic form, so once all coordinates have been computed and all funny paths rendered into their more basic components (so circles are replaced by bezier curves and so forth), but it is not yet "baked hard" so can still be manipulated.

The above script steps in at this point and says, "That path you've just constructed, rather than render it as is, I'm going to 'thicken' it.". In detail, it takes the path together with another path (called the "pen") and forms a (cartesian) product of the two. This then produces a region which it fills. The effect is as if the pen had been dragged along the path. By using quite complicated pens, quite complicated effects can be achieved.

Most of the time, this filling method produces exactly the desired effect. Where it fails is where the path turns a corner tangential to the pen path. If one were truly dragging a pen along the path, this corner would be filled, but because this method works by translation, that doesn't happen (an example in the picture is the top of the "X" on the right). However, when doing calligraphy, one should never push the pen and drawing a stroke where the path turns tangential to the pen would involve pushing the pen (so the off-diagonal of the "X" should really be three strokes: the main path and the two ends).

Note that once I've packaged this into something (and one reason for prompting Joseph to ask this question was to justify including it in the proposed "TikZ from TeX-SX" package!), the usage will be something like:

\pen (-.25,-.125) -- (0,0) ++(.125,.0625) -- (.25,.125);
\calligraphy (0,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(2,0) (1,0) -- ++(0,-2) -- ++(-.125,-.125) .. controls +(.1,.1) and +(-.1,-.1) .. ++(.35,0);
\calligraphy (3,-1) .. controls +(-1.5,1) and +(-1.5,-1) .. ++(0,-2) ++(-1.1,1) -- ++(1,0) -- ++(-.25,-.25);
\calligraphy (3.25,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(1.5,-2) ++(0,2.5) .. controls +(-.75,.75) and +(.75,-.75) .. ++(-1.5,-3);
\calligraphy (0,-4) .. controls +(1,1) and +(-1,-1) .. ++(5,0);

which is really quite simple.

Apart from the fun of "Hey, look what I just managed to do!", I also wanted to put this somewhere public because I think that this ability to manipulate the soft path is quite useful for other effects. It's something like decorations, but not quite, and the concepts that I've identified here will be useful whenever there is a situation where one wants to say, "That path there, I'd like to use it, but in a weird way.". In particular, doing something more than once with the same path, but modifying the path slightly for each action.