# TikZ: Text alignment and line breaking

I have some troubles with text alignment along path in tikz. I made custom shape and wrote text along path inside it in the following way:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.text}
\begin{document}
\begin{tikzpicture}

% shape
\draw (70:3) arc (70:110:3)
-- (110:5)  arc (110:70:5)
-- cycle;

\path[
postaction={
decorate,
decoration={
text along path,
text align = center,
text={Gershon Daly Bonifacy Yaw}
}
}
]
(105:4.5) arc (105:75:4.5)
(105:4.0) arc (105:75:4.0)
(105:3.5) arc (105:75:3.5);

\end{tikzpicture}
\end{document}


Result is pretty fine, but there are small issues. This is output without central alignment:

and this is with central alignment:

My issue is that I want a correct line breaking (don't break lines in the middle of the word) and alignment relative to the center line of the shape, not the path.

I know that when rendering text along path every letter is wrapping in different \hbox and turn to the correct degree separately.

Any suggestions?

UPDATE:

In this answer path is decorated only in case if text fits the path. May be there are similar approach to decorate path only with fitted text and left rest of the text to the other paths?

UPDATE 2:

I can hide text if the text don't fit path redefining left indent state as stated in mentioned answer above:

\makeatletter
\pgfkeys{
/pgf/decoration/omit long text/.code={%
\expandafter\def\csname pgf@decorate@@text along path@left indent@options\expandafter\expandafter\expandafter\endcsname\expandafter\expandafter\expandafter{\csname pgf@decorate@@text along path@left indent@options\endcsname,switch if less than=\pgf@lib@dec@text@width to final}%
},
}
\makeatother


My idea is to not only skip typesetting text (switch to final state), but also set some flag that mean that text is not typeset, so I can decrease text length and call decoration again.

• I had an idea, but I lack the LuaTeX expertise required to implement it. The idea is to use Lua for: 1) compute the length of each arc (simple geometry math). 2) prepare a parshape in which each line has the appropiate length and use TeX to "cast" the text into that paragraph. 3) use lua again to inspect the resulting box and determining where the line breaks happened. 4) From lua generate tikz code which draws separate paths for each arc, and put in each one each single line found in 3) – JLDiaz Dec 23 '13 at 11:12

Following my idea (in a comment to the OP's question), I did some experiments with LuaTeX. These are the preliminary results.

# The latex example

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.text}
\directlua{dofile("testing.lua")}
\begin{document}
\sffamily

% Before entering tikzpicture
\directlua{PrepareText({{105,75,4.5}, {105,75,4}, {105,75,3.5}},
"Gershon Daly Bonifacy Yaw")}
\begin{tikzpicture}
\draw (70:3) arc (70:110:3)
-- (110:5)  arc (110:70:5)
-- cycle;
\directlua{TypeInArcs()}
\end{tikzpicture}
%
% A second example
\directlua{PrepareText({{105,75,4.5}, {105,75,4}, {105,75,3.5}},
"This is another longer test, which does not Fit")}
\begin{tikzpicture}
\draw (70:3) arc (70:110:3)
-- (110:5)  arc (110:70:5)
-- cycle;
\directlua{TypeInArcs()}
\end{tikzpicture}
%
% Third example
\directlua{PrepareText({{105,75,4.5}, {105,75,4}, {105,75,3.5}, {105,75,3}},
"This is another longer test, which now does Fit")}
\begin{tikzpicture}
\draw (70:2.5) arc (70:110:2.5)
-- (110:5)  arc (110:70:5)
-- cycle;
\directlua{TypeInArcs()}
\end{tikzpicture}

\end{document}


# The result

After compiling with lualatex

# The lua code

Save it in file testing.lua:

function GetLinesFromBox()
local lines,i,j,l,d,list,words
lines = {}; j = 1;
d=node.types();
node.unprotect_glyphs(list)
for l in node.traverse(list) do
if (d[l.id]=="hlist") then
words = {}; i = 1
if (d[l.id]=="glyph") then
words[i] = string.char(l.char)
i=i+1
end
if (d[l.id]=="glue") then
words[i] = " "
i=i+1
end
end
lines[j] = table.concat(words,"")
j=j+1
end
end
return lines
end

end

global_arcs = {}
global_text = ""

function PrepareText(arcs, text)
local j,l
global_arcs = arcs
global_text = text
tex.print("\\setbox0=\\vbox{\\centering\\parshape ", #arcs)
for j=1,#arcs do
l = ComputeLengthArc(arcs[j][1], arcs[j][2], arcs[j][3] )
tex.print("0cm "..l.."cm ")
end
tex.print("\\noindent ".. text .. "}")
end

function TypeInArcs()
local lines,j
lines = GetLinesFromBox()
for j=1,#global_arcs do
if lines[j]~=nil then
tex.sprint("\\path[ postaction = { decorate, decoration = {")
tex.sprint("  text along path,")
tex.sprint("  text align = center,")
tex.sprint("  text={"..lines[j].."}")
tex.sprint("} } ]")
tex.sprint("("..global_arcs[j][1]..":"..global_arcs[j][3]..
") arc ("..global_arcs[j][1]..":"..global_arcs[j][2]..":"..global_arcs[j][3]..");")
end
end
end


# Explanations and Caveats

This is only a proof-of-concept. The algorithm is very naive. I typeset the text in TeX's box 0, but with a parshape with the appropiate lengths for each line. Then I examine the resulting nodes from lua, assuming each hbox to be a line, each glyph inside that box to be a character, and each glue to be an space. This way I "reconstruct" the string to be used for each line later, when drawing the tikz picture.

There are some issues I didn't solve (I don't know how):

1. Ligatures in the text break the algorithm, because produce glyphs that do not have ascii equivalent. This is why I wrote "Fit" instead of "fit", to avoid the "fi" ligature.
2. For the same reason, probably no accented chars are allowed (not tested)
3. Apparently tikz interferes with TeX boxing mechanism, because if I try to create the box inside tikz environment, no box is produced. This is why I have to "prepare" the box before entering tikz.