I think I actually might have got it... Pretty sure that some optimisations are possible; eg. I am using a set of hardcoded S numbers -> TEX sizing strings. There might be an inbuilt method to do that for you, but I couldn't immediately see it. I don't know if these numbers are subject to change either - do different base scales change the value of S, for example? But it works. You can set the size of the text, the ornamentation of the text (using Therion's inbuilt string->TEX functionality), and the alignment of the text. Some ornamentation styles do not respect rotation - that is a bug in Therion's text processing. Perhaps someone will now point out a glaring problem (like strings containing \foo or } which I think will break the TEX, but I also think this happens elsewhere in Therion...). But hopefully this gives a nice base for other people who needed this functionality to work from. I don't quite know what the "\framed { text here }" part does, since it seems to have no effect, but Therion uses it for all of the ornamented labels but not for basic labels. vardef create_styled_label (expr plaintext,P,R,S,A,defaultstyle)= save textsize, style; string textsize; if S = 0.5: textsize:="\thtinysize"; elseif S = 0.7: textsize:="\thsmallsize"; elseif S = 1.4: textsize:="\thlargesize"; elseif S = 2: textsize:="\thhugesize"; else: % normal is 1 textsize:="\thnormalsize"; fi; if known ATTR_labelstyle: style:=scantokens(ATTR_labelstyle); else: style:=defaultstyle; fi; picture thetext; thetext:=thTEX("\thframed {" & textsize & plaintext & "}"); if A = (-1,1): p_label.ulft(thetext,P,R,style); elseif A = (0,1): p_label.top(thetext,P,R,style); elseif A = (1,1): p_label.urt(thetext,P,R,style); elseif A = (-1,0): p_label.lft(thetext,P,R,style); elseif A = (1,0): p_label.rt(thetext,P,R,style); elseif A = (-1,-1): p_label.llft(thetext,P,R,style); elseif A = (0,-1): p_label.bot(thetext,P,R,style); elseif A = (1,-1): p_label.lrt(thetext,P,R,style); else: p_label(thetext,P,R,style); fi; enddef; def p_u_ropelength (expr P,R,S,A)= T:=identity shifted P; U:=(0,0); if known ATTR_text: % approximate size (varies with proportional font) U:=(S*u*(length ATTR_text)/4,S*u*(length ATTR_text)/4); create_styled_label(ATTR_text,P,R,S,A,p_label_mode_passageheight); fi; enddef; initsymbol("p_u_ropelength"); You can see that all it does is to produce the right TEX string (so it respects scaling), and then calls the right version of p_label (so it respects alignment). It relies on p_label to support rotation. If the symbol has "-attr labelstyle 5" it will use that label style. There is then an example point symbol that uses it with a string stored in "-attr text", and sets its T and U accordingly. However, there is no requirement for it to be a dedicated point symbol - you could just as easily call it to put a label anywhere you choose, such as beside some existing symbol (so you could have a warning triangle symbol, with some text in a box beside it, for example). Aside; One other issue with replacing the p_label macro like Stacho suggested is that you end up using a small chunk of the existing Therion code. This in turn means that you need to license all of yours under GPL (because it is GPL not LGPL). If your survey is intended to be released under some other license, you can't make a derivative work from a chunk of Therion code.