20

I'm trying to draw text in shapes (using scaling and path). I want text to show up like this:

Valley Text

I've tried a lot of different methods, but I'm having trouble doing so. I can get close to it using something like this:

\documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{decorations.text}

\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
\node (One) at (-15,0) {.}; 
\node (Two) at (15,0) {.};
\draw [decorate,decoration={text effects along path, 
                    text={TTTTTTTTTTTTTTTT},
                    text align=center,
                    text effects/.cd,
                    path from text,
                    character count=\i, character total=\n, 
                    characters= {text along path, scale=(2 + abs(\i - (\n / 2) - 0.5) * 1 )}}] (One) to (Two);

\end{tikzpicture}

\end{document}

Which results in :

Result

I want the top of the text to curve up like the example shown.

Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
  • If you have to do it piece-meal, my answer here, http://tex.stackexchange.com/questions/301897/star-wars-text-effect/302908#302908, may offer some insights. However, I'm thinking there may be a pgf way to make it easy. – Steven B. Segletes Jun 09 '16 at 13:02

2 Answers2

21

Update

Using triangular mesh, we can approximate any continuous transformation.

\documentclass[border=9,tikz]{standalone}
\begin{document}

\pgfmathdeclarefunction{fx}{2}{\pgfmathparse{#1*(3+cos(#2*20))/3}}
\pgfmathdeclarefunction{fy}{2}{\pgfmathparse{#2*(3-cos(#1*20))/3}}
\pgfmathdeclarefunction{fxx}{2}{\pgfmathparse{fx(#1+1,#2)-fx(#1,#2)}}
\pgfmathdeclarefunction{fxy}{2}{\pgfmathparse{fy(#1+1,#2)-fy(#1,#2)}}
\pgfmathdeclarefunction{fyx}{2}{\pgfmathparse{fx(#1,#2+1)-fx(#1,#2)}}
\pgfmathdeclarefunction{fyy}{2}{\pgfmathparse{fy(#1,#2+1)-fy(#1,#2)}}

\tikz{
    \path(-15,-15)(15,15);
    \foreach\i in{-10,...,9}{
        \foreach\j in{-10,...,9}{
            \pgfmathsetmacro\aa{fxx(\i,\j)}
            \pgfmathsetmacro\ab{fxy(\i,\j)}
            \pgfmathsetmacro\ba{fyx(\i,\j)}
            \pgfmathsetmacro\bb{fyy(\i,\j)}
            \pgfmathsetmacro\xx{fx (\i,\j)}
            \pgfmathsetmacro\yy{fy (\i,\j)}
            \pgflowlevelobj{
                \pgfsettransformentries{\aa}{\ab}{\ba}{\bb}{\xx cm}{\yy cm}
            }{
                \fill[black!10](1,0)--(0,0)--(0,1);
                \clip(1,0)--(0,0)--(0,1)--cycle;
                \tikzset{shift={(-\i,-\j)}}
                \path(0,0)node{\includegraphics[width=20cm]{lena.png}};
            }
            \pgfmathsetmacro\aa{fxx(\i  ,\j+1)}
            \pgfmathsetmacro\ab{fxy(\i  ,\j+1)}
            \pgfmathsetmacro\ba{fyx(\i+1,\j  )}
            \pgfmathsetmacro\bb{fyy(\i+1,\j  )}
            \pgfmathsetmacro\xx{fx (\i+1,\j+1)}
            \pgfmathsetmacro\yy{fy (\i+1,\j+1)}
            \pgflowlevelobj{
                \pgfsettransformentries{\aa}{\ab}{\ba}{\bb}{\xx cm}{\yy cm}
            }{
                \clip(0,0)--(-1,0)--(0,-1)--cycle;
                \tikzset{shift={(-\i-1,-\j-1)}}
                \path(0,0)node{\includegraphics[width=20cm]{lena.png}};
            }
        }
    }
}

\end{document}

Old Answer

Just copying @Steven B. Segletes's idea, only that the grid is 2D, and is done in PGF/TikZ.

% !TEX program = XeLaTeX
% !TEX encoding = UTF-8 Unicode

\documentclass[border=9,tikz]{standalone}
\usepackage{fontspec}\setmainfont{Arial Unicode MS}

\begin{document}

\pgfmathdeclarefunction{fxx}{1}{\pgfmathparse{#1}}
\pgfmathdeclarefunction{fxy}{1}{\pgfmathparse{sin(222+30*#1)}}
\pgfmathdeclarefunction{fyx}{1}{\pgfmathparse{sin(20*#1)}}
\pgfmathdeclarefunction{fyy}{1}{\pgfmathparse{#1}}
\pgfmathdeclarefunction{gxx}{1}{\pgfmathparse{fxx(#1+1)-fxx(#1)}}
\pgfmathdeclarefunction{gxy}{1}{\pgfmathparse{fxy(#1+1)-fxy(#1)}}
\pgfmathdeclarefunction{gyx}{1}{\pgfmathparse{fyx(#1+1)-fyx(#1)}}
\pgfmathdeclarefunction{gyy}{1}{\pgfmathparse{fyy(#1+1)-fyy(#1)}}

\tikz{
    \path(-2,-2)(103,23);
    \foreach\i in{0,...,100}{
        \foreach\j in{0,...,20}{
            {
                \pgfmathsetmacro\aa{gxx(\i)}
                \pgfmathsetmacro\ab{gxy(\i)}
                \pgfmathsetmacro\ba{gyx(\j)}
                \pgfmathsetmacro\bb{gyy(\j)}
                \pgfmathsetmacro\xx{fxx(\i)+fyx(\j)}
                \pgfmathsetmacro\yy{fxy(\i)+fyy(\j)}
                \pgflowlevelobj{
                    \pgfsettransformentries{\aa}{\ab}{\ba}{\bb}{\xx cm}{\yy cm}
                }{
                    \clip(0,0)--(1,0)--(1,1)--(0,1)--cycle;
                    \draw[gray](1,0)--(0,0)--(0,1);
                    \path(-\i,-\j)+(50,10)node{\fontsize{500pt}{0}\selectfont Valley Text};
                }
            }
        }
    }
}

\end{document}

Some Math

In my code a transformation is defined by

(x,y) |--> ( fxx(x)+fyx(y) , fxy(x)+fyy(y) )

where fxx, fyx, fxy, fyy are good functions. Such transformation will send a square to a parallelogram. Coincidently, PDF supports affine transformation, which also send a square to a parallelogram. Thus I can use piecewise affine transformation to approximate the original transformation. The result is that any curve will remain connected, although not differentiable.

Symbol 1
  • 36,855
20

I just threw in the \outline macro (requires pdflatex) for the fun of it.

The \outline stuff is in the preamble, whereas the necking code is in the main document. I originally stole the code that makes up \outline from Malipivo at TikZ: halo around text?

What I do is create a box (\mytext) with the text (padded a little so that none gets subsequently clipped), and then successively apply a \clipbox that vertically slices the text box \cuts times. On each cut, I apply a \scalebox to shrink the text as a function of the cut number and \dip, which represents the maximum necking fraction. The function I choose here is parabolic, though others can be developed as well.

\documentclass{article}
\usepackage{ifthen,trimclip,calc,fp,graphicx,xcolor}
%%%%% FOR TEXT OUTLINING
\input pdf-trans
\newbox\qbox
\def\usecolor#1{\csname\string\color@#1\endcsname\space}
\newcommand\bordercolor[1]{\colsplit{1}{#1}}
\newcommand\fillcolor[1]{\colsplit{0}{#1}}
\newcommand\outline[1]{\leavevmode%
  \def\maltext{#1}%
  \setbox\qbox=\hbox{\maltext}%
  \boxgs{Q q 2 Tr \thickness\space w \fillcol\space \bordercol\space}{}%
  \copy\qbox%
}
\newcommand\colsplit[2]{\colorlet{tmpcolor}{#2}\edef\tmp{\usecolor{tmpcolor}}%
  \def\tmpB{}\expandafter\colsplithelp\tmp\relax%
  \ifnum0=#1\relax\edef\fillcol{\tmpB}\else\edef\bordercol{\tmpC}\fi}
\def\colsplithelp#1#2 #3\relax{%
  \edef\tmpB{\tmpB#1#2 }%
  \ifnum `#1>`9\relax\def\tmpC{#3}\else\colsplithelp#3\relax\fi
}
\bordercolor{blue}
\fillcolor{yellow}
\def\thickness{.5}
%%%%%
\begin{document}
\Huge
\edef\dip{.5}% percent to depress the amplitude
\def\cuts{200}% Number of cuts
\newsavebox\mytext
\savebox{\mytext}{\kern.3pt\textbf{\textsf{\outline{Valley Text}}}\kern.3pt}% TEXT
\newlength\clipsize
\FPeval{\myprod}{1/cuts}
\clipsize=\myprod\wd\mytext\relax
\newcounter{mycount}
\whiledo{\value{mycount}<\cuts}{%
  \stepcounter{mycount}%
  \edef\NA{\themycount}%
  \edef\NB{\the\numexpr\cuts-\themycount\relax}%
  \FPeval{\myprod}{1 - \dip*\NA*\NB*4/\cuts/\cuts}%
  \clipbox{%
    \value{mycount}\clipsize\relax{} %
    -1pt %
    \wd\mytext-\value{mycount}\clipsize-\clipsize\relax{} %
    -1pt%
  }{\scalebox{1}[\myprod]{\usebox{\mytext}}}%
}
\end{document}

enter image description here

Without the \outline code, the code is more manageable. (EDITED) Here I convert it into a macro \parabtext[<mode>]{<lift>}{<neck>}{<cuts>}{<content>} with several additions from above:

<mode> is 0 for narrow middle, and 1 for narrow ends;

<lift> is the fractional max-lift of the baseline during the transformation

<neck> is the fractional reduction in total height at the neck;

<cuts> are the number of vertical slices to apply to the box (too small, and it will look stair-stepped)

<content> is the stuff to put in a box and transform.

\documentclass{article}
\usepackage{ifthen,trimclip,calc,fp,graphicx,xcolor}
\newsavebox\mytext
\newcounter{mycount}
\newlength\clipsize
\newcommand\parabtext[5][0]{%
  \edef\neck{#3}% percent to depress the amplitude
  \def\cuts{#4}% Number of cuts
  \savebox{\mytext}{\kern.2pt#5\kern.2pt}% TEXT
  \FPeval{\myprod}{1/cuts}%
  \clipsize=\myprod\wd\mytext\relax%
  \setcounter{mycount}{0}%
  \whiledo{\value{mycount}<\cuts}{%
    \stepcounter{mycount}%
    \edef\NA{\themycount}%
    \edef\NB{\the\numexpr\cuts-\themycount\relax}%
    \FPeval{\myprod}{\NA*\NB*4/\cuts/\cuts}%
    \ifnum0#1=0\relax%
      \FPeval{\myprod}{1 - \neck*(\myprod)}%
    \else%
      \FPeval{\myprod}{1 - \neck*(1-\myprod)}%
    \fi%
    \clipbox{%
      \value{mycount}\clipsize\relax{} %
      -1pt %
      \wd\mytext-\value{mycount}\clipsize-\clipsize\relax{} %
      -1pt%
    }{\raisebox{#2\dimexpr\ht\mytext-\myprod\ht\mytext}{%
        \scalebox{1}[\myprod]{\usebox{\mytext}}}}%
  }%
}
\begin{document}
\Huge\centering\def\X{\textbf{XXX}}%
\parabtext{0}{.7}{200}{\textbf{\textcolor{brown}{Valley Text}}}\par
\X\parabtext{0}{.7}{200}{\X}\parabtext[1]{0}{.7}{200}{\X}\X\par
\X\parabtext{1}{.7}{200}{\X}\parabtext[1]{1}{.7}{200}{\X}\X\par
\X\parabtext{.425}{.7}{200}{\X}\parabtext[1]{.425}{.7}{200}{\X}\X
\end{document}

enter image description here

The most recently EDITED option, <mode>, changes the shape function of the transformation. In both modes, a function is evaluated

\FPeval{\myprod}{\NA*\NB*4/\cuts/\cuts}%

That is a parabola that is at a value of 1 at both ends of the box and a value of 0 at the middle of the box. Then, if the mode is 0, the transformation is

\FPeval{\myprod}{1 - \neck*(\myprod)}%

whereas, if not zero, the transformation is

\FPeval{\myprod}{1 - \neck*(1-\myprod)}%

This simple difference will produce the necked and barrelled versions, respectively.


PROPOSED METHODOLOGY

The method is somewhat computationally expensive, but it is intended to be used sparingly for the Wow! effect.

First, I'll show a slightly modified code that takes it, in effect, from a forward difference to a central difference, though it may cost a little more in computation.

But what I think works best is to set the number of slices in a user defined variable to a low number, say \def\slices{5} and develop your document on that basis. On the final compilation, when all is set in the final layout, then re\def\slices to the desired number, say {200}, and recompile one last time.

\documentclass{article}
\usepackage{ifthen,trimclip,calc,fp,graphicx,xcolor}
\newsavebox\mytext
\newcounter{mycount}
\newlength\clipsize
\newcommand\parabtext[5][0]{%
  \edef\neck{#3}% percent to depress the amplitude
  \def\cuts{#4}% Number of cuts
  \savebox{\mytext}{\kern.2pt#5\kern.2pt}% TEXT
  \FPeval{\myprod}{1/cuts}%
  \clipsize=\myprod\wd\mytext\relax%
  \setcounter{mycount}{0}%
  \whiledo{\value{mycount}<\cuts}{%
    \stepcounter{mycount}%
    \edef\NA{\themycount}%
    \edef\NB{\the\numexpr\cuts-\themycount\relax}%
    \FPeval{\myprod}{(\NA-.5)*(\NB+.5)*4/\cuts/\cuts}%
    \ifnum0#1=0\relax%
      \FPeval{\myprod}{1 - \neck*(\myprod)}%
    \else%
      \FPeval{\myprod}{1 - \neck*(1-\myprod)}%
    \fi%
    \clipbox{%
      \value{mycount}\clipsize-\clipsize\relax{} %
      -1pt %
      \wd\mytext-\value{mycount}\clipsize\relax{} %
      -1pt%
    }{\raisebox{#2\dimexpr\ht\mytext-\myprod\ht\mytext}{%
        \scalebox{1}[\myprod]{\usebox{\mytext}}}}%
  }%
}
\begin{document}
\def\slices{200}
\Huge\centering\def\X{\textbf{XXX}}%
\parabtext{0}{.7}{\slices}{\textbf{\textcolor{brown}{Valley Text}}}\par
\X\parabtext{0}{.7}{\slices}{\X}\parabtext[1]{0}{.7}{\slices}{\X}\X\par
\X\parabtext{1}{.7}{\slices}{\X}\parabtext[1]{1}{.7}{\slices}{\X}\X\par
\X\parabtext{.425}{.7}{\slices}{\X}\parabtext[1]{.425}{.7}{\slices}{\X}\X
\end{document}

Here is, for example, the output with \slices set to 5 for the lo-res compilation:

enter image description here

NOTE TO XeLaTeX USERS

The OP noted that this approach failed to work in XeLaTeX. After some study, I boiled the problem down to a bug in the trimclip package (\clipbox of a \scalebox not working properly in xelatex). Joseph Wright was kind enough to diagnose the issue and provide a patch for the trimclip code, when running in XeLaTeX.

\makeatletter
\ifdefined\XeTeXversion
  \def\@cliptoboxdim#1{%
    \setbox #1=\hbox{%
      \Gin@defaultbp\WIDTH{\wd #1}%
      \Gin@defaultbp \DEPTH {\dp #1}%
      \@tempdima \ht #1%
      \advance\@tempdima\dp#1%
      \Gin@defaultbp \TOTALHEIGHT {\@tempdima }%
      \special{pdf:literal q}% 
      \special{pdf:literal 0 -\DEPTH \space \WIDTH \space \TOTALHEIGHT \space re W n }%
      \rlap{\copy #1}%
      \special {pdf:literal Q}%
      \hskip\wd#1%
    }%
  }
\fi
\makeatother
  • 1
    @HiemanshuSharma You are welcome. I just now added more functionality with an optional argument that gives a rise, so that both topline and baseline deviances are possible. Please see my revision. – Steven B. Segletes Jun 09 '16 at 15:36
  • 1
    @HiemanshuSharma I have edited again, so that both a barrelled and a necked version of the transformation are available from the macro. – Steven B. Segletes Jun 09 '16 at 19:58
  • Thank you! This looks really good. Now just to figure out how to optimize these to compile quickly on my hardware. Figure out the right balance between smoothness and compile time. – Hiemanshu Sharma Jun 10 '16 at 04:35
  • 1
    @HiemanshuSharma My recommendation would be to set the number of slices to a low number in a variable (\def\slices{10} and \parabtext[]{}{}{\slices}{}). Then, for the final compilation, reset \def\slices{200} for one last compile. – Steven B. Segletes Jun 10 '16 at 11:05
  • 2
    This is so nineties... – Andreï V. Kostyrka Jun 15 '16 at 00:14
  • 1
    @AndreïKostyrka And in the '90's, we would have said "this is so sixties." – Steven B. Segletes Jun 15 '16 at 00:25
  • @StevenB.Segletes I know the question was asked a while ago, but any idea why this doesn't work for xelatex, and what I can do to port this to xelatex? – Hiemanshu Sharma Sep 28 '16 at 15:46
  • @HiemanshuSharma Interesting. It compiles for me under Xelatex, but produces a vastly different (and incorrect) output. I am not sure which loaded package is having the difficulty. – Steven B. Segletes Sep 28 '16 at 17:14
  • @HiemanshuSharma I am thinking a bug (or an outdated version). I have asked a question here: http://tex.stackexchange.com/questions/331780/clipbox-of-a-scalebox-not-working-properly-in-xelatex – Steven B. Segletes Sep 28 '16 at 18:01
  • @StevenB.Segletes Ah interesting. Thank you! – Hiemanshu Sharma Sep 28 '16 at 18:34
  • @HiemanshuSharma Joseph has provided a fix at the cited question. Just paste the code from his\makeatletter to \makeatother (inclusive) into my code above, and it compiles as expected. – Steven B. Segletes Sep 28 '16 at 19:30
  • Thanks @StevenB.Segletes! I'll try that out and see if it works. – Hiemanshu Sharma Sep 28 '16 at 19:40
  • @StevenB.Segletes I've been running into more issues :D I've asked a follow up question here: https://tex.stackexchange.com/questions/334548/create-text-shapes-with-custom-fonts, was too big to be a comment. – Hiemanshu Sharma Oct 17 '16 at 21:35