Fitting text to a shape

A given text is fitted to specified paths. This code automatically uses the shapepar package with TikZ.

The shapeparnode macro uses six parameters:

  1. (optional - default: empty) a style for the node (font size, color...)
  2. the horizontal margin (a distance)
  3. the vertical margin (a distance)
  4. the left boundary (a continuous vertical path)
  5. the right boundary (a continuous vertical path)
  6. the text (a single paragraph without par or empty line)

The shapeparnodeaccuracy can be locally redefined and gives the accuracy to compute the shape (default: 2 lines per em).

The code was written by Paul Gaborit and posted on TeX.SE. Slight modification for using english blind text.


text-shape

Edit and compile if you like:

% Fitting text to a shape
% Author: Paul Gaborit
\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage[T1]{fontenc}
\usepackage{shapepar}
\usepackage{microtype}
\usepackage{tikz}
\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength\PreviewBorder{10pt}%
\usetikzlibrary{calc,fit,intersections}
\def\shapeparnodeaccuracy{2}
\newcommand\shapeparnode[6][]{
  % 6 parameters:
  % style for node (default:empty),
  % h margin, v margin, left path, right path, text (just one paragraph!)

  % name left and right paths and compute there bounding boxes
  \begin{scope}[local bounding box=leftbb]
    \path[name path global=left, xshift=#2] #4;
  \end{scope}
  \node[inner ysep=-#3, inner xsep=0pt, fit=(leftbb)](leftbb){};
  \begin{scope}[local bounding box=rightbb]
    \path[name path global=right, xshift=-#2] #5;
  \end{scope}
  \node[inner ysep=-#3, inner xsep=0pt, fit=(rightbb)](rightbb){};

  % global bounding box
  \path let
  \p1=(leftbb.north west), \p2=(leftbb.south west),
  \p3=(rightbb.north east), \p4=(rightbb.south east)
  in
  \pgfextra{
    \pgfmathsetmacro{\ymin}{(\y1 < \y3) ? \y1 : \y3}
    \pgfmathsetmacro{\ymax}{(\y2 > \y4) ? \y2 : \y4}
    \typeout{ymin \ymin}
    \typeout{ymax \ymax}
  } node[inner sep=0, fit={(\x1,\ymin pt)(\x3,\ymax pt)}](mybb){};

  % compute nb steps
  \path let \p1=(mybb.north), \p2=(mybb.south) in
  \pgfextra{
    \pgfmathsetmacro{\fnthght}{1em/\shapeparnodeaccuracy}
    \pgfmathtruncatemacro{\nbsteps}{(\y1-\y2)/\fnthght}
    \xdef\nbsteps{\nbsteps}
    \typeout{nb steps \nbsteps}
  };

  % horizontal references
  \path (mybb.north) -- (mybb.south)
  \foreach \cnt in {0, 1, ..., \nbsteps}{
    \pgfextra{\pgfmathsetmacro{\pos}{\cnt/\nbsteps}}
    coordinate[pos=\pos] (ref \cnt)
  };

  % left and right boundaries coordinates
  \foreach \cnt in {0, 1, ..., \nbsteps}{
    % an horizontal line from left to right
    \path[name path=ltor]
      (mybb.west |- ref \cnt) --  (mybb.east |- ref \cnt);
    % same line from right to left
    \path[name path=rtol]
      (mybb.east |- ref \cnt) -- (mybb.west |- ref \cnt);
    % left boundary
    \path[name intersections={of=rtol and left, by={l \cnt}, sort by=rtol}];
    % right boundary
    \path[name intersections={of=ltor and right, by={r \cnt}, sort by=ltor}];
  }
  % start point (and initial value of boundshape)
  \path let \p1=(l 0) in 
  \pgfextra{
    \pgfmathsetmacro{\xstart}{\x1}
    \xdef\boundshape{{0}{0}b{\xstart}}
    \xdef\xmin{\xstart}
    \xdef\xmax{\xstart}
  };

  % top and bottom
  \path let \p1=(l 0), \p2=(l \nbsteps) in
  \pgfextra{
    \pgfmathsetmacro{\ystart}{\y1}\xdef\ystart{\ystart}
    \pgfmathsetmacro{\yending}{\y2}\xdef\yending{\yending}
  };
  % incremental definition of boundshape
  \foreach \cnt in {0, 1, ..., \nbsteps}{
    \path let \p1=(l \cnt), \p2=(r \cnt) in
    \pgfextra{
      \pgfmathsetmacro{\start}{\x1}
      \pgfmathsetmacro{\len}{\x2-\x1}
      \pgfmathsetmacro{\ypos}{\cnt/\nbsteps*(\ystart - \yending)}
      {\let\\=\relax \xdef\boundshape{\boundshape\\{\ypos}t{\start}{\len}}}
      \pgfmathsetmacro{\xmin}{(\xmin < \start) ? \xmin : \start}
      \xdef\xmin{\xmin}
      \pgfmathsetmacro{\xmax}{(\xmax > \start + \len) ? \xmax : \start + \len}
      \xdef\xmax{\xmax}
    };
  }
  % draw the node with text in a shapepar
  \pgfmathsetmacro{\ymax}{\ystart - \yending}
  {\let\\=\relax \xdef\boundshape{\boundshape\\{\ymax}e{0}}}
  \node[#1, text width=\xmax pt - \xmin pt, align=flush left,
  anchor=north west, inner sep=0]
  at (mybb.north west -| \xmin pt, 0)
  {\Shapepar[1pt]{\boundshape}#6\par};
}

\begin{document}%
\begin{tikzpicture}
  \begin{scope}[yshift=-22cm] % third example
    \footnotesize
    \def\radius{3.1}
    \def\pathone{(3,0)
      arc(90:225:\radius)
      arc (45:-45:\radius/2.415)
      arc(135:270:\radius)}
    \def\pathtwo{(3,-4*\radius)
      arc(-90:0:\radius)
      arc(180:90:\radius)
      arc(270:180:\radius)
      arc(0:90:\radius)}
    \fill[top color=lime, bottom color=orange, middle color=yellow, draw=white]
    \pathone -- \pathtwo -- cycle;
    \shapeparnode[text=black, font=\footnotesize]
      {2em}{1em}{\pathone}{\pathtwo}{\blindtext[2]}%
    %\draw[orange] \pathone;
    %\draw[orange] \pathtwo;
  \end{scope}
\end{tikzpicture}
\end{document}




Click to download: text-shape.textext-shape.pdf
Open in Overleaf: text-shape.tex