TransWikia.com

Deforming/projecting text in TikZ(-cd) along a curved surface

TeX - LaTeX Asked on November 1, 2021

This is a continuation of STeX Exchange 552946.

I’ve been trying to get some 3D effects on tikz-cd, and got an amazing answer by @ZhiyuanLck. The only remaining problem is getting text/arrows to be drawn along curved surfaces (or look that way). Is it possible to achieve such an effect?

For instance, how would we do that for the Rightarrows and their labels theta_f and theta_g in the diagram below?

enter image description here

(This diagram is the ice cream cone condition for lax slice bicategories, as in Section 7.1 of Johnson–Yau’s new book on bicategories.)

Compilable code for this diagram:

documentclass[english,11pt]{standalone}
RequirePackage{luatex85}
usepackage{tikz}
usepackage{tikz-cd}
usetikzlibrary{3d}
makeatletter
tikzset{
  plane/.code args={#1and#2}{
    tikz@scan@one@pointpgf@process#1
    edeftemp@a{(thepgf@x, thepgf@y)};
    tikz@scan@one@pointpgf@process#2
    edeftemp@b{(thepgf@x, thepgf@y)};
    pgfkeysalso{
      plane x={temp@a},
      plane y={temp@b},
      canvas is plane,
    }
  },
}
makeatother
usepackage{libertine}
usepackage{mathtools}
usepackage[libertine]{newtxmath}
tikzcdset{
    arrow style=tikz,
    %diagrams={>={Straight Barb[scale=1.5]}}
    diagrams={>={Stealth[round,length=4pt,width=4.95pt,inset=2.75pt]}}
}
begin{document}
newsavebox{BoxNodeOne}
savebox{BoxNodeOne}{
    begin{tikzcd}[row sep={4.5em,between origins}, column sep={4.5em,between origins}, ampersand replacement=&]
        {}
        arrow[r, "F(A)"{plane={(1,0) and (0,0.7)}},phantom]
        &
        {}
    end{tikzcd}
} 
newsavebox{BoxNodeTwo}
savebox{BoxNodeTwo}{
    begin{tikzcd}[row sep={4.5em,between origins}, column sep={4.5em,between origins}, ampersand replacement=&]
        {}
        arrow[r, "F(B)"{plane={(1,0) and (0,0.7)}},phantom]
        &
        {}
    end{tikzcd}
} 
newsavebox{BoxOne}
savebox{BoxOne}{
    begin{tikzcd}[row sep={4.5em,between origins}, column sep={4.5em,between origins}, ampersand replacement=&]
        {}
        \
        arrow[u, Rightarrow]
        {}
    end{tikzcd}
} 
newsavebox{BoxTwo}
savebox{BoxTwo}{
    begin{tikzcd}[row sep={3.6em,between origins}, column sep={3.6em,between origins}, ampersand replacement=&]
        {}
        &
        {}
        \
        {}
        arrow[ru, Rightarrow, bend right=35]
        &
        {}
    end{tikzcd}
} 
begin{tikzcd}[row sep={14.4em,between origins}, column sep={6.3em,between origins}, ampersand replacement=&]
    {hspace{+1.25em}usebox{BoxNodeOne}}
    arrow[rr, "F(g)"{name=3,description,plane={(1,0) and (0,0.7)}},bend left=30]
    arrow[rr, "F(f)"{name=2,description,plane={(1,0) and (0,0.7)}},bend right=30]
    arrow[rd, "phi_{A}"'{name=1},start anchor={[xshift=+0.5em]}]
    &
    &
    {hspace{-1.25em}usebox{BoxNodeTwo}}
    arrow[ld, "phi_{B}",start anchor={[xshift=-0.5em]}]
    \
    {}
    &
    X
    &
    {}
    % 2-Arrows
    arrow[from=1,to=1-3,"theta_{f}"{description,yshift=-0.2em},shorten=2.5em,Rightarrow,xshift=-1.0em,yshift=-1.0em,bend right=15]
    arrow[from=2,to=3,"usebox{BoxOne}"{plane={(1,0) and (0,0.6)}},shorten=0.5em,phantom]
    arrow[from=2,to=3,"scalebox{0.75}{$F(alpha)$}"{description,plane={(1,0) and (0,0.6)}},shorten=0.5em,phantom]
end{tikzcd}
=
begin{tikzcd}[row sep={14.4em,between origins}, column sep={6.3em,between origins}, ampersand replacement=&]
    {hspace{+1.25em}usebox{BoxNodeOne}}
    arrow[rr, "F(g)"{name=3,description,plane={(1,0) and (0,0.7)}},bend left=30]
    arrow[rd, "phi_{A}"'{name=1},start anchor={[xshift=+0.5em]}]
    &
    &
    {hspace{-1.25em}usebox{BoxNodeTwo}}
    arrow[ld, "phi_{B}",start anchor={[xshift=-0.5em]}]
    \
    &
    X
    &
    % 2-Arrows
    arrow[from=1,to=1-3,"theta_{g}"{description,yshift=+0.15em},shorten=1.5em,Rightarrow,bend left=15,xshift=+0.25em,yshift=0.5em]
end{tikzcd}
end{document}

One Answer

The main idea is to combine text along path of library decorations.text with canvas is plane of library 3d. In my last answer, I have shown how to place node on specified plane by set x unit vector and y unit vector. Decoration text along path put every character in a qboxsynced, which means before inserting the text, the current coordinate transformation matrix is applied to the current canvas transformation matrix.

I define a new decoration 3d text along path. Its main code is from decoration text along path, and I add some code that is from tikz@canvas@is@plane in tikzlibrary3d.code.tex

deftikz@canvas@is@plane{
  pgf@process{tikz@plane@x}%
  pgf@xa=pgf@x%
  pgf@ya=pgf@y%
  pgf@process{tikz@plane@y}%
  pgf@xb=pgf@x%
  pgf@yb=pgf@y%
  pgf@process{tikz@plane@origin}%
  edefpgf@marshal{noexpandtikz@addtransform{%
      noexpandpgftransformtriangle
      {noexpandpgfqpoint{thepgf@x}{thepgf@y}}
      {noexpandpgfqpoint{thepgf@xa}{thepgf@ya}}
      {noexpandpgfqpoint{thepgf@xb}{thepgf@yb}}
      noexpandpgftransformscale{0.035146}%
      noexpandpgfsetxvec{noexpandpgfpoint{1cm}{0cm}}%
      noexpandpgfsetyvec{noexpandpgfpoint{0cm}{1cm}}%
      noexpandpgfsetzvec{noexpandpgfpoint{0cm}{0cm}}%
    }}%
  pgf@marshal%
}%

Next thing is to applied correct canvas transformation before pgfqboxsyncedpgf@hbox . The code looks like

  pgftransformtriangle%
  {pgfpointxy{0}{0}}%
  {pgfpointxy{1}{0}}%
  {pgfpointpolarxy{<some angle>}{1}}%
  pgftransformscale{0.035146}%
  pgfsetxvec{pgfpointxy{1}{0}}%
  pgfsetyvec{pgfpointxy{0}{1}}%
  pgfsetzvec{pgfpointxy{0}{0}}%

It seems that almost nothing has been changed. Note that before this piece of code, all the transformation has been applied, which means pgfpoingxy{1}{0} points to the direction of the path.

The key point is the y unit vector, as is said above, if <some angle> is 90, then y unit vector is perpendicular to x unit vector, which is the default behavior.

By setting y unit vector, there are two ways to make text appear to be placed on a curved plane:

  • set y unit vector to a constant unit vector
  • set y unit vector to a vector that points from the segment to some point

First way is easy to implement, just set <some angle> to <angle of constant y unit vector> - pgfdecoratedangle (minus pgfdecoratedangle to invert the segement rotation).

Second way need calculate angle of different y unit vector before decoration segment is decorated. This work can be done in persistent precomputation of state typeset

  persistent precomputation={
     pgfmathanglebetweenpoints%
     {pgfpointlineatdistance{pgfdecoratedinputsegmentcompleteddistance}{pgf@decorate@inputsegment@first}{pgf@decorate@inputsegment@last}}%
     {pgf@decorate@rel@point}
     xdefpgf@decorate@yvec@angle{pgfmathresult}
  }

At last, define the user interface

pgfkeys{%
  /pgf/decoration/.cd,
  3d raise/.store in=tikz@lib@dec@te@threedimraisevar,
  3d raise=0pt,
  yvec/.code={tikz@handle@vec{tikz@lib@dec@te@yvec@point}{tikz@lib@dec@te@yvec@angle}#1relax}
  yvec/.initial=90,
}

tikzset{
  mytext/.style={
    postaction=decorate,
    decoration={
      3d text along path,
      3d raise=.8ex,
      text align={align=center},#1
    }
  }
}

Then you can use mytext={...} to specify a piece of text on curved plane, all original keys of /pgf/decoration is supported and two new keys are added, you can set y unit vector to a constant vector by yvec=<angle> or to a vector point to some point by yvec={<point>}. Also, use 3d raise=<dimen> to get a correct shift in y unit vector.

Here is some examples and complete code: enter image description here

main.tex

documentclass[tikz, border=1cm]{standalone}
usetikzlibrary{decorations,decorations.text}
input{text.tex}

tikzset{
  mytext/.style={
    postaction=decorate,
    decoration={
      3d text along path,
      3d raise=.8ex,
      text align={align=center},#1
    }
  }
}

begin{document}
begin{tikzpicture}
draw [mytext={text along path, text={Do you know $x + y = z$? You don't know, what a pity!}}] (0, 0) arc (90:90+360:4cm and 1cm);
draw [yshift=2.5cm, mytext={text={Do you know $x + y = z$? You don't know, what a pity!}, yvec={(0,12)}}] (0, 0) arc (90:90+360:4cm and 1cm);
draw [yshift=5cm, mytext={text={Do you know $x + y = z$? You don't know, what a pity!}, yvec=90}] (0, 0) arc (90:90+360:4cm and 1cm);
draw [yshift=6cm, mytext={text={Do you know $x + y = z$? You don't know, what a pity!}, yvec=120}] (0, 0) to[relative, out=80, in=-80, distance=7cm] (0, 4);
node at (0, -1) {ellipse plane};
node at (0, 1.5) {point to curve plane};
node at (0, 4) {shifted curve plane};
end{tikzpicture}
end{document}

text.tex

makeatletter
newififtikz@lib@dec@te@yvecispoint
lettikz@lib@dec@te@yvecendpoint=pgfutil@empty
deftikz@lib@dec@te@yvecangle{90}
lettikz@lib@dec@te@threedimraisevar=pgfutil@empty
% keys for 3d text
pgfkeys{%
  /pgf/decoration/.cd,
  3d raise/.store in=tikz@lib@dec@te@threedimraisevar,
  3d raise=0pt,
  yvec/.code={tikz@handle@vec{tikz@lib@dec@te@yvec@point}{tikz@lib@dec@te@yvec@angle}#1relax}
}

% Parse yvec
% If #1 is an angle, save the angle as the direction of yvec
% If #1 is a point, parse the point and save it in
% `tikz@lib@dec@te@yvecendpoint'
deftikz@lib@dec@te@yvec@angle#1{%
  tikz@lib@dec@te@yvecispointfalse%
  deftikz@lib@dec@te@yvecangle{#1}%
}%
deftikz@lib@dec@te@yvec@point#1{%
  tikz@lib@dec@te@yvecispointtrue%
  deftikz@lib@dec@te@yvecendpoint{#1}%
}%


% 3d text along path
pgfdeclaredecoration{3d text along path}{initial}{%
    state{initial}[
        width=+0pt, next state=left indent,
        persistent precomputation={%
            edefpgf@lib@dec@text@indent@left{pgfkeysvalueof{/pgf/decoration/text align/left indent}}%
            edefpgf@lib@dec@text@indent@right{pgfkeysvalueof{/pgf/decoration/text align/right indent}}%
            edefpgf@lib@dec@text@align{pgfkeysvalueof{/pgf/decoration/text align/align}}%
            pgfdecoratedremainingdistance=pgfdecoratedpathlength%
        advancepgfdecoratedremainingdistance by-pgf@lib@dec@text@indent@rightrelax%
        edefpgfdecoratedpathlength{thepgfdecoratedremainingdistance}%
        pgf@lib@dec@text@getwidth%
        pgf@x=pgf@lib@dec@text@widthrelax%
        pgf@y=pgfdecoratedremainingdistance%
        ifpgf@lib@dec@text@fit%
            advancepgf@y by-pgf@lib@dec@text@indent@leftrelax%
            advancepgf@y by-pgf@x%
            ifpgf@lib@dec@text@stretch@spaces%
                defpgf@lib@dec@text@character@shift{0pt}%
                dividepgf@y bypgf@lib@dec@space@countrelax%
                edefpgf@lib@dec@text@space@shift{thepgf@y}%
            else%
                c@pgf@counta=pgf@lib@dec@character@countrelax%
                advancec@pgf@counta by-1relax%
                dividepgf@y byc@pgf@countarelax%
                edefpgf@lib@dec@text@character@shift{thepgf@y}%
                defpgf@lib@dec@text@space@shift{0pt}%
            fi%
            ifdimpgf@y<0ptrelax%
                pgf@lib@dec@text@fitfalse%
                pgf@lib@dec@text@stretch@spacesfalse%
                defpgf@lib@dec@text@character@shift{0pt}%
                defpgf@lib@dec@text@space@shift{0pt}%
            fi%
        else%
            defpgf@lib@dec@text@character@shift{0pt}%
            defpgf@lib@dec@text@space@shift{0pt}%
                ifxpgf@lib@dec@text@alignpgf@lib@dec@text@left@text%
                else%
                    ifxpgf@lib@dec@text@alignpgf@lib@dec@text@right@text%
                        advancepgf@y by-pgf@x%
                        edefpgf@lib@dec@text@indent@left{thepgf@y}%
                    else%
                        advancepgf@y by-pgf@x%
                        advancepgf@y by-pgf@lib@dec@text@indent@leftrelax%
                        pgf@y=0.5pgf@y%
                        advancepgf@y bypgf@lib@dec@text@indent@leftrelax%
                        edefpgf@lib@dec@text@indent@left{thepgf@y}%
                    fi%
                fi%
        fi%
        letpgfdecorationrestoftext=pgfdecorationtext%
    }]{}%
state{left indent}[width=+pgf@lib@dec@text@indent@left, next state=scan]{}%
%
state{scan}[
    width=+0pt,
    next state=before typeset,
    persistent precomputation={
        pgf@lib@dec@text@scanchar%
        ifvoidpgf@lib@dec@text@box%
            setboxpgf@lib@dec@text@boxhbox{}%
            wdpgf@lib@dec@text@box16383ptrelax%
        fi%
}]{}%
%
state{before typeset}[width=+.5wdpgf@lib@dec@text@box, next state=typeset]{}%
%
state{typeset}[width=+0pt, next state=after typeset,
  persistent precomputation={
    iftikz@lib@dec@te@yvecispoint
      pgfmathanglebetweenpoints%
      {pgfpointlineatdistance{pgfdecoratedinputsegmentcompleteddistance}{pgf@decorate@inputsegment@first}{pgf@decorate@inputsegment@last}}%
      {tikz@lib@dec@te@yvecendpoint}%
      edeftikz@lib@dec@te@yvecangle{pgfmathresult}
    fi
  }
]
{%
  pgftransformxshift{+-.5wdpgf@lib@dec@text@box}%
  setboxpgf@hboxhbox{copypgf@lib@dec@text@box}%
  pgftransformtriangle%
  {pgfpointxy{0}{0}}%
  {pgfpointxy{1}{0}}%
  {pgfpointpolarxy{tikz@lib@dec@te@yvecangle-pgfdecoratedangle}{1}}%
%   {pgfpointpolarxy{tikz@lib@dec@te@yvecangle-pgfdecoratedangle}{1}}%
  pgftransformscale{0.035146}%
  pgfsetxvec{pgfpointxy{1}{0}}%
  pgfsetyvec{pgfpointxy{0}{1}}%
  pgfsetzvec{pgfpointxy{0}{0}}%
  pgftransformshift{pgfpoint{0pt}{tikz@lib@dec@te@threedimraisevar}}
  pgfqboxsyncedpgf@hbox%
}%
state{after typeset}[width=+.5wdpgf@lib@dec@text@box, next state=shift,
    persistent precomputation={%
    ifpgf@lib@dec@text@fit%
        ifpgf@lib@dec@text@stretch@spaces%
            ifpgf@lib@dec@text@scan@space%
                letpgf@lib@dec@text@shift=pgf@lib@dec@text@space@shift%
            else%
                defpgf@lib@dec@text@shift{0pt}%
            fi%
        else%
            letpgf@lib@dec@text@shift=pgf@lib@dec@text@character@shift%
          fi%
        else%
            defpgf@lib@dec@text@shift{0pt}%
      fi%
  }]{}%
state{shift}[width=+pgf@lib@dec@text@shift, next state=scan]{}%
state{final}{}%
}%
makeatother

Answered by ZhiyuanLck on November 1, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP