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
\def\tikz@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}%
\edef\pgf@marshal{\noexpand\tikz@addtransform{%
\noexpand\pgftransformtriangle
{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}
{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
\noexpand\pgftransformscale{0.035146}%
\noexpand\pgfsetxvec{\noexpand\pgfpoint{1cm}{0cm}}%
\noexpand\pgfsetyvec{\noexpand\pgfpoint{0cm}{1cm}}%
\noexpand\pgfsetzvec{\noexpand\pgfpoint{0cm}{0cm}}%
}}%
\pgf@marshal%
}%
Next thing is to applied correct canvas transformation before \pgfqboxsynced\pgf@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}
\xdef\pgf@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}#1\relax}
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:

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
\newif\iftikz@lib@dec@te@yvecispoint
\let\tikz@lib@dec@te@yvecendpoint=\pgfutil@empty
\def\tikz@lib@dec@te@yvecangle{90}
\let\tikz@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}#1\relax}
}
% 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'
\def\tikz@lib@dec@te@yvec@angle#1{%
\tikz@lib@dec@te@yvecispointfalse%
\def\tikz@lib@dec@te@yvecangle{#1}%
}%
\def\tikz@lib@dec@te@yvec@point#1{%
\tikz@lib@dec@te@yvecispointtrue%
\def\tikz@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={%
\edef\pgf@lib@dec@text@indent@left{\pgfkeysvalueof{/pgf/decoration/text align/left indent}}%
\edef\pgf@lib@dec@text@indent@right{\pgfkeysvalueof{/pgf/decoration/text align/right indent}}%
\edef\pgf@lib@dec@text@align{\pgfkeysvalueof{/pgf/decoration/text align/align}}%
\pgfdecoratedremainingdistance=\pgfdecoratedpathlength%
\advance\pgfdecoratedremainingdistance by-\pgf@lib@dec@text@indent@right\relax%
\edef\pgfdecoratedpathlength{\the\pgfdecoratedremainingdistance}%
\pgf@lib@dec@text@getwidth%
\pgf@x=\pgf@lib@dec@text@width\relax%
\pgf@y=\pgfdecoratedremainingdistance%
\ifpgf@lib@dec@text@fit%
\advance\pgf@y by-\pgf@lib@dec@text@indent@left\relax%
\advance\pgf@y by-\pgf@x%
\ifpgf@lib@dec@text@stretch@spaces%
\def\pgf@lib@dec@text@character@shift{0pt}%
\divide\pgf@y by\pgf@lib@dec@space@count\relax%
\edef\pgf@lib@dec@text@space@shift{\the\pgf@y}%
\else%
\c@pgf@counta=\pgf@lib@dec@character@count\relax%
\advance\c@pgf@counta by-1\relax%
\divide\pgf@y by\c@pgf@counta\relax%
\edef\pgf@lib@dec@text@character@shift{\the\pgf@y}%
\def\pgf@lib@dec@text@space@shift{0pt}%
\fi%
\ifdim\pgf@y<0pt\relax%
\pgf@lib@dec@text@fitfalse%
\pgf@lib@dec@text@stretch@spacesfalse%
\def\pgf@lib@dec@text@character@shift{0pt}%
\def\pgf@lib@dec@text@space@shift{0pt}%
\fi%
\else%
\def\pgf@lib@dec@text@character@shift{0pt}%
\def\pgf@lib@dec@text@space@shift{0pt}%
\ifx\pgf@lib@dec@text@align\pgf@lib@dec@text@left@text%
\else%
\ifx\pgf@lib@dec@text@align\pgf@lib@dec@text@right@text%
\advance\pgf@y by-\pgf@x%
\edef\pgf@lib@dec@text@indent@left{\the\pgf@y}%
\else%
\advance\pgf@y by-\pgf@x%
\advance\pgf@y by-\pgf@lib@dec@text@indent@left\relax%
\pgf@y=0.5\pgf@y%
\advance\pgf@y by\pgf@lib@dec@text@indent@left\relax%
\edef\pgf@lib@dec@text@indent@left{\the\pgf@y}%
\fi%
\fi%
\fi%
\let\pgfdecorationrestoftext=\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%
\ifvoid\pgf@lib@dec@text@box%
\setbox\pgf@lib@dec@text@box\hbox{}%
\wd\pgf@lib@dec@text@box16383pt\relax%
\fi%
}]{}%
%
\state{before typeset}[width=+.5\wd\pgf@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}%
\edef\tikz@lib@dec@te@yvecangle{\pgfmathresult}
\fi
}
]
{%
\pgftransformxshift{+-.5\wd\pgf@lib@dec@text@box}%
\setbox\pgf@hbox\hbox{\copy\pgf@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}}
\pgfqboxsynced\pgf@hbox%
}%
\state{after typeset}[width=+.5\wd\pgf@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%
\let\pgf@lib@dec@text@shift=\pgf@lib@dec@text@space@shift%
\else%
\def\pgf@lib@dec@text@shift{0pt}%
\fi%
\else%
\let\pgf@lib@dec@text@shift=\pgf@lib@dec@text@character@shift%
\fi%
\else%
\def\pgf@lib@dec@text@shift{0pt}%
\fi%
}]{}%
\state{shift}[width=+\pgf@lib@dec@text@shift, next state=scan]{}%
\state{final}{}%
}%
\makeatother
tikz-cd
look like they were drawn along a curved surface, such as the surface of the cone above, e.g. like the text in this cone.