# Creating a circled operator which expands into a lozenge

I have a LaTeX document which has a binary operator called "composition". Normally, this is written as a circled letter, where the letter is passed as a parameter: \comp{x}. I can achieve good results for this using techniques similar to How do I put a circle around an operator?.

However, sometimes the content of the operator get big, e.g. some calculation that renders as a few letters long: \comp{x + y + z}. In this instance, I don't want a giant circle with a diameter large enough to contain the text. I'd like to have the operator extend into a lozenge, the length of which depends on the length of the content. So, if the content is just one letter, it's just a letter in a circle. If it's several letters, it expands the circle horizontally into capsule that contains the content.

I have complete control of the build environment and package choice. The document will be quite long (200+ pages), so build time is a concern. However, the machine is quite punchy, so "heavy" solutions are possibly OK. The operator is used in math mode, and occasionally as a super/subscript, but should never get really tiny. How can I achieve this goal?

-

This answer takes care of TeX/LaTeX's math typesetting stuff. Starting point is the solution of JLDiaz, that is based on Andrew Swann's answer.

• \comp should be a binary operator (\mathbin).

• The contents of \comp is set as math.

• Math is automatically resized if used in subscripts, inside fractions, … The tikz solution switches to text mode and back to math. There we need to know, which style is active (\displaystyle, \textstyle, \scriptstyle, \scriptscriptstyle). LaTeX's \mathpalette helps in setting this style (It uses TeX's \mathchoice).

• Math can be surrounded by additional space, controlled by \mathsurround. When we switch intermediately from text to math, these space must not be added. LaTeX's \m@th helps in this case, because it sets \mathsurround to 0pt.

• I switched from ex to mu to get a better scaling behaviour and use a dimen register to set tikz's parameters including inner sep and line width.

• In the script styles TeX does not set additional space around binary operators. But IMHO it looks a little odd, if the adjacent symbols are more close to the box than its contents. Therefore a half thin space is added in script styles. And a quarter thin space in display and text styles. \nonscript ignores the following math glue in script styles.

• Also minimum width and an invisible rule are added to improve the behaviour for tiny or empty contents.

• \comp has an optional argument that pass options to the node settings.

The file:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes}
\usepackage{color}

\makeatletter
\newcommand*\comp[2][]{%
\ensuremath{%
\mathbin{%
\mathpalette{\comp@aux{#1}}{#2}%
}%
}%
}
\newdimen\comp@unit
\newcommand*{\comp@aux}[3]{%
#2%
\mskip.5\thinmuskip\nonscript\mskip-.25\thinmuskip
\begingroup
\sbox0{%
$% \m@th % \mathsurround=0pt #2% \displaystyle, \textstyle, ... \mkern9mu %$%
}%
\edef\x{\endgroup
\comp@unit=\the\wd0 %
}\x
\tikz[baseline=(char.base)]{%
\node[
rectangle,
draw,
minimum height=2\comp@unit,
minimum width=2\comp@unit,
rounded corners=\comp@unit,
inner sep=.33\comp@unit,
line width=.05\comp@unit,
#1%
] (char) {%
$% \m@th % \mathsurround=0pt #2% \displaystyle, \textstyle, ... \rule{0pt}{\comp@unit}% #3%$%
};%
}%
\mskip.5\thinmuskip\nonscript\mskip-.25\thinmuskip
}
\makeatother

\begin{document}

$a \oplus b \ominus c$

$a \comp{x} b \comp{y} c \comp{} d \comp{Z} e \comp{\cdot} f$

$a \comp{x+y+z} b \comp{\displaystyle\int_0^\infty \frac{x}{z}\,\mathrm{d}x} c$

$a \comp{x} b^{c \comp{y} d} \frac{e \comp{z} f}{g \comp{2x} h_{i \comp{2y} j}}$

$a \comp[red]{r} b \comp[green]{g} c \comp[blue]{b} d$

\end{document}


-
All the answers are wonderful, but I think this pips it for completeness. The only mild annoyance is that the baselines are not lining up identically on otherwise "similarly" sized items (viz. r g b on the bottom line). Thanks muchly! – Adam Wright Aug 20 '12 at 10:07
In the last line of the example the base lines for all letters arbgcbd are the same exactly. Of course, the circles can be centered at the math axis; just add \vcenter{\hbox{$ in the first line of the definition of \comp@aux and $}} in the last line. – Heiko Oberdiek Aug 20 '12 at 11:15

Using tikz as in Andrew Swann's answer, but using a rectangle with rounded corners, instead of an ellipse, and carefully setting the corner radius and the rectangle height, you can get a nice approximation:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\thispagestyle{empty}

\usetikzlibrary{shapes}

\newcommand*\compel[1]{\tikz[baseline=(char.base)]{
\node[rectangle,draw,minimum height=3ex, minimum width=3ex, rounded corners=1.5ex] (char) {#1};}}

\compel{x} \compel{$$x+y+z$$}

\end{document}

-
Have been trying to get a more general solution than the hard coded 3ex with \settowidth and \settoheight (then taking the minimum) but the height doesn't seem to be calculated correctly, even with \ensuremath... – drfrogsplat Aug 15 '12 at 8:13

Adapting the answers to Good way to make \textcircled numbers? one could use an ellipse shape from tikz.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes}

\newcommand*\compel[1]{\tikz[baseline=(char.base)]{
\node[ellipse,draw,inner sep=0.5pt] (char) {#1};}}

\begin{document}
\compel{x} \compel{$$x+y+z$$}

\end{document}


The cylinder shape is annoyingly close to your wish

\newcommand*\compcy[1]{\tikz[baseline=(char.base)]{
\node[cylinder,draw,inner sep=0.5pt] (char) {#1}; }}

\compcy{x} \compcy{$$x+y+z$$}


\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes}

\makeatletter
% Keys for shape flatcircle
%
% /pgf/shape aspect              : Ratio between the x and y radii of the flatcircle end.
% /pgf/flatcircle uses custom fill : Use a custom fill for the flatcircle.
% /pgf/flatcircle end fill         : Custom color for the flatcircle end.
% /pgf/flatcircle body fill        : Custom color for the flatcirclebody.
%

\newif\ifpgfflatcircleusescustomfill
\pgfkeys{/pgf/.cd,
flatcircle uses custom fill/.is if=pgfflatcircleusescustomfill,
flatcircle end fill/.initial=white,
flatcircle body fill/.initial=white
}

\pgfdeclareshape{flatcircle}{%
\savedmacro\getflatcirclepoints{%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}%
\pgf@x\pgf@xc%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}%
\pgf@y\pgf@yc%
\ifpgfshapeborderusesincircle%
\pgfmathsetmacro\rotate{\pgfkeysvalueof{/pgf/shape border rotate}}%
\ifdim\pgf@x<\pgf@y%
\pgf@x\pgf@y%
\else%
\pgf@y\pgf@x%
\fi%
\pgf@x1.414213\pgf@x%
\pgf@y1.414213\pgf@y%
\else%
\pgfmathmod{\pgfkeysvalueof{/pgf/shape border rotate}}{360}%
\ifdim\pgfmathresult pt<0pt\relax%
\fi%
\pgfmathsetcount\c@pgf@counta{+\pgfmathresult}%
\divide\c@pgf@counta90\relax%
\multiply\c@pgf@counta90\relax%
\edef\rotate{\the\c@pgf@counta}%
\ifnum\c@pgf@counta=90\relax%
\pgf@xa\pgf@x%
\pgf@x\pgf@y%
\pgf@y\pgf@xa%
\pgf@yc\pgf@xc%
\else%
\ifnum\c@pgf@counta=270\relax%
\pgf@xa\pgf@x%
\pgf@x\pgf@y%
\pgf@y\pgf@xa%
\pgf@yc\pgf@xc%
\fi%
\fi%
\fi%
\pgf@xa\pgf@x%
\pgf@ya\pgf@y%
\pgfutil@tempdima\pgfshapeaspect\pgf@ya%
\pgfutil@tempdimb\pgf@ya%
%
%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/minimum width}}%
\ifdim\pgfutil@tempdimb<.5\pgf@xc\relax%
\pgfutil@tempdimb.5\pgf@xc%
\pgf@ya\pgfutil@tempdimb%
\fi%
%
% Calculate how far the node contents can extend into the flatcircle bottom.
%
\pgf@yb\pgfutil@tempdimb%
\pgfmathdivide@{\pgfmath@tonumber{\pgf@yb}}{\pgfmath@tonumber{\pgfutil@tempdimb}}%
\pgfmathasin@{\pgfmathresult}%
\pgfmathcos@{\pgfmathresult}%
\let\angle\pgfmathresult%
\pgf@xb\pgfmathresult\pgfutil@tempdima%
%
%
\pgf@x.5\pgflinewidth%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/minimum height}}%
\ifdim\pgf@x<\pgf@xc%
\fi%
%
%
\pgf@x\pgfutil@tempdima\relax%
\pgf@y\pgfutil@tempdimb\relax%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/outer ysep}}%
\ifdim\pgf@xc>\pgf@yc%
\edef\outersep{\the\pgf@xc}%
\else%
\edef\outersep{\the\pgf@yc}%
\fi%
%
\pgfextract@process\flatcirclecenter{%
\pgf@x\pgfutil@tempdima%
\pgf@x.5\pgf@x%
\pgf@y0pt%
}%
%
\pgfextract@process\beforetop{%
\pgf@x\pgf@xa%
\pgf@y\pgf@ya%
}%
\pgfextract@process\afterbottom{%
\pgf@x-\pgf@xa%
\pgf@y\pgf@ya%
}%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/outer ysep}}%
\pgfextract@process\beforetopanchor{%
\beforetop%
}%
\pgfextract@process\afterbottomanchor{%
\afterbottom%
}%
%
\beforetopanchor%
\ifdim\pgf@x>\pgf@y%
\else%
\fi%
}
\savedanchor\centerpoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgf@y.5\ht\pgfnodeparttextbox%
}%
\savedanchor\midpoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgfmathsetlength\pgf@y{+0.5ex}%
}%
\savedanchor\basepoint{%
\pgf@x.5\wd\pgfnodeparttextbox%
\pgf@y0pt%
}%
\anchor{center}{\centerpoint}
\anchor{shape center}{%
\getflatcirclepoints%
{\centerpoint}{\rotate}%
}%
\anchor{mid}{\midpoint}%
\anchor{mid east}{%
\getflatcirclepoints%
\let\pgf@flatcircle@referencepoint\midpoint%
}%
\anchor{mid west}{%
\getflatcirclepoints%
\let\pgf@flatcircle@referencepoint\midpoint%
}%
\anchor{base}{\basepoint}%
\anchor{base east}{%
\getflatcirclepoints%
\let\pgf@flatcircle@referencepoint\basepoint%
}%
\anchor{base west}{%
\getflatcirclepoints%
\let\pgf@flatcircle@referencepoint\basepoint%
}%
\anchor{north}{%
\getflatcirclepoints%
}%
\anchor{south}{%
\getflatcirclepoints%
}%
\anchor{east}{%
\getflatcirclepoints%
}%
\anchor{west}{%
\getflatcirclepoints%
}%
\anchor{north east}{%
\getflatcirclepoints%
}%
\anchor{south west}{%
\getflatcirclepoints%
}%
\anchor{south east}{%
\getflatcirclepoints%
}%
\anchor{north west}{%
\getflatcirclepoints%
}%
\anchor{before top}{%
\getflatcirclepoints%
}
\anchor{top}{%
\getflatcirclepoints%
\pgfmathrotatepointaround{%
\beforetop%
\pgf@y0pt\relax%
}{\centerpoint}}{\centerpoint}{\rotate}%
}
\anchor{after top}{%
\getflatcirclepoints%
}
\anchor{before bottom}{%
\getflatcirclepoints%
}
\anchor{bottom}{%
\getflatcirclepoints%
\pgfmathrotatepointaround{%
\afterbottom%
\pgf@y0pt\relax%
}{\centerpoint}}{\centerpoint}{\rotate}%
}
\anchor{after bottom}{%
\getflatcirclepoints%
}
\backgroundpath{%
\getflatcirclepoints%
{%
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\pgfpathmoveto{\afterbottom}%
\pgfpathlineto{\beforetop\pgf@y-\pgf@y}%
\pgfpathclose%
%           \pgfpathmoveto{\beforetop}%
}%
}%
\behindbackgroundpath{%
\ifpgfflatcircleusescustomfill%
\getflatcirclepoints%
{%
\pgftransformshift{\centerpoint}%
\pgftransformrotate{\rotate}%
\pgfpathmoveto{\afterbottom}%
\pgfpathlineto{\beforetop\pgf@y-\pgf@y}%
\pgfpathclose%
\expandafter\pgfsetfillcolor\expandafter{\pgfkeysvalueof{/pgf/flatcircle body fill}}%
\pgfusepath{fill}%
%
\pgfpathmoveto{\beforetop}%
\pgfpathclose
\expandafter\pgfsetfillcolor\expandafter{\pgfkeysvalueof{/pgf/flatcircle end fill}}%
\pgfusepath{fill}%
}%
\fi%
}%
\anchorborder{%
\pgfextract@process\externalpoint{}%
\getflatcirclepoints%
\pgfutil@ifundefined{pgf@flatcircle@referencepoint}{\let\referencepoint\centerpoint}{%
\let\referencepoint\pgf@flatcircle@referencepoint}%
\pgfextract@process\externalpoint{%
\externalpoint%
\pgf@xa\pgf@x%
\pgf@ya\pgf@y%
\referencepoint%
}%
\pgfmathanglebetweenpoints{\centerpoint}{\externalpoint}%
\pgfmathsubtract@{\pgfmathresult}{\rotate}%
\ifdim\pgfmathresult pt<0pt\relax%
\fi%
\let\externalangle\pgfmathresult%
%
\ifdim\externalangle pt<\pgfmathresult pt\relax%
\ifdim\externalangle pt<\pgfmathresult pt\relax%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\beforetop%
\pgf@xa\pgf@x%
\centerpoint%
}%
}{\centerpoint}{\rotate}%
\else%
\pgfpointintersectionoflines{%
{\centerpoint}{\rotate}}{%
{\centerpoint}{\rotate}}%
{\referencepoint}{\externalpoint}%
\fi%
\else%
\ifdim\externalangle pt>\pgfmathresult pt\relax%
\ifdim\externalangle pt>\pgfmathresult pt\relax%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\beforetop%
\pgf@xa\pgf@x%
\centerpoint
}%
}{\centerpoint}{\rotate}%
\else%
\pgfpointintersectionoflines{%
{\centerpoint}{\rotate}}{%
{\centerpoint}{\rotate}}%
{\referencepoint}{\externalpoint}%
\fi%
\else%
\pgfmathrotatepointaround{%
\pgfmathpointintersectionoflineandarc%
{\pgfmathrotatepointaround{\externalpoint}{\centerpoint}{-\rotate}}%
{\pgfmathrotatepointaround{\referencepoint}{\centerpoint}{-\rotate}}%
{%
\afterbottom%
\pgf@xa\pgf@x%
\centerpoint
}%
}{\centerpoint}{\rotate}%
\fi%
\fi%
}
}
\makeatother

\newcommand*\comp[1]{\tikz[baseline=(char.base)]{
\node[flatcircle,draw,inner ysep=1pt,inner xsep=0pt] (char) {#1};}}

\begin{document}
\comp{x} \comp{$$x+y+z$$} \comp{y} \comp{$$\int$$}

\end{document}


This is just the code from pgflibraryshapes.geometric.code.tex for the cylinder shape, with a few lines commented out. I have replaced cylinder by flatcircle throughout, commented out the lines that draw the leftward point half circle on the right of the cylinder, and in \pgfextract@process\beforetop commented out the shifting commands that moved the rightside of the cylinder to the right, and flipped the sign of a correction to \pgf@x by \pgf@xb. Finally, in calling this code setting the inner separations separately in the x and y directions, results in a round circle in the first case. The code is long, because it includes all the pgf material to set anchors, allow rotations etc.

-

For a first approximation see the code below

\documentclass{article}
\usepackage{etoolbox}
\usepackage{tikz}

\newlength{\tempA}
\newlength{\tempB}

\newcommand{\comp}[1]{%
\settowidth{\tempA}{M}%
\settowidth{\tempB}{\ensuremath{#1}}%
\ifdimcomp{\tempA}{<}{\tempB}{\inlozenge{#1}}{\incircle{#1}}%
}

\newcommand{\incircle}[1]{\tikz[baseline]\draw[anchor=base] (0,0) node[draw,circle]{\ensuremath{#1}};}
\newcommand{\inlozenge}[1]{\tikz[baseline]\draw[anchor=base] (0,0) node[draw,rounded corners]{\ensuremath{#1}};}

\begin{document}
$\comp{m}_{\comp{x+x}}+\comp{A=B}$

\end{document}

-