To have a self-contained answer, I am repeating some of what the other answers have mentioned. For the TLDR folks, I've included all of the relevant code at the end of this answer.
TeXForm design
The way TeXForm works is as follows:
- Apply any
TeXForm formatting rules that match the entire expression. If one does, then return the result. Otherwise:
- Convert the resulting expression into
TraditionalForm boxes.
- Recursively transform the resulting
TraditionalForm boxes into a TeXForm string.
Format issue
The heart of the problem then is that during step 1, the TeXForm definitions defined using Format are only applied once, and to the entire expression. This behavior is different from other Format definitions, which apply definitions recursively, because of the two-step process that TeXForm uses. To fix this, there are two possibilities:
- Use a
ReplaceRepeated strategy on the expression, that replaces parts at all levels (improve step 1).
- Convert the
TeXForm format rules into MakeBoxes rules (improve step 2).
Possibility 1 is used by @jkuczm in his TeXUtilities package. I prefer possibility 2, so I will provide code to use this possibility.
Why I don't like possibility 1
The reason I don't like using possibility 1 is because the code needs to search the expression to figure out which Format rules should be used when doing the expression replacement. And, since a Format rule can include a symbol on the RHS that also needs to be converted, the conversion of the expression really needs to be done again, including the search for all symbols that might need replacement in the new expression. Here is an example. Suppose we have the following Format rules:
Format[foo[x_], TeXForm] := bar[x, foo2[x]]
Format[foo2[x_], TeXForm] := bar2[x]
and we want to convert the expression:
foo[x] + Sin[x]
into TeXForm. In order to know which Format rules to use, we need to examine the expression, upon which we figure out that only foo needs to be replaced. So, if we replace it, we end up with:
bar[x, foo2[x]] + Sin[x]
Notice that we now have an expression with foo2. So, we need to repeat the above process, and we figure out that now only foo2 needs to be replaced. So, we again replace, ending up with:
bar[x, bar2[x]] + Sin[x]
At this point, there are no more Format rules to apply, so we can go ahead and create the boxes corresponding to the above expression.
Now, with possibility 2, all we need to do is to define the MakeBoxes rules corresponding to the Format statements, and then applying MakeBoxes to the expression will automatically use these new MakeBoxes rules as needed. No need to repeatedly search the expression for symbols and perform replacements.
Converting Format -> MakeBoxes
One issue with converting Format rules into MakeBoxes rules is that TeXForm uses TraditionalForm, and we don't want the TeXForm specific MakeBoxes rules to be used during normal TraditionalForm formatting. To avoid this, I will introduce a global variable (I've given it the name $TeX), and set this variable to True when the expression is converted to TraditionalForm boxes. Then, the code to convert Format rules into MakeBoxes rules can include a $TeX condition in the MakeBoxes rule, so that it only fires when the TeXForm string is being created.
Modifying built-in symbols
I will be modifying built-in symbols, and you should be aware that there are risks associated with doing this.
Since I will be changing DownValues/UpValues of built-in symbols, it will be very convenient to make use of the Initial wrapper that I introduced in my answer to How can one manually change the rule ordering:
Initial /: Verbatim[TagSetDelayed][Initial[sym_], lhs_, rhs_] := With[
{
new = Block[{sym},
TagSetDelayed[sym, lhs, rhs];
First @ Language`ExtendedDefinition[sym]
],
protect = Unprotect[sym]
},
sym;
Replace[new,
Rule[values_, n:Except[{}]] :> (
values[sym] = DeleteDuplicates@Join[n, values[sym]]
),
{2}
];
Protect@protect;
]
The basic idea is that using something like:
Initial[FresnelC] /:
MakeBoxes[FresnelC[x_], TraditionalForm] :=
MakeBoxes[Defer[FresnelC][x], TraditionalForm]
will not only create the new FormatValues, it will try to put this new format value at the beginning, so that it fires before any built - in format rules for the input:
FormatValues[FresnelC] //InputForm
{HoldPattern[MakeBoxes[FresnelC[x_], TraditionalForm]] :> MakeBoxes[Defer[FresnelC][x], TraditionalForm],
HoldPattern[MakeBoxes[FresnelC[BoxForm`a\$_], TraditionalForm] /; BoxForm`sufficientVersionQ[6.1]] :>
TemplateBox[{MakeBoxes[BoxForm`a\$, TraditionalForm]}, "FresnelC"]}
Notice how the new MakeBoxes rule is first.
Convert`TeX`ExpressionToTeX
The first change is to modify the function Convert`TeX`ExpressionToTeX so that it sets the global variable $TeX to True before creating TraditionalForm boxes:
Initial[Convert`TeX`ExpressionToTeX] /:
Convert`TeX`ExpressionToTeX[e__] /; !TrueQ@$TeX := Block[
{$TeX = True},
Convert`TeX`ExpressionToTeX[e]
]
By design, $TeX will only get a value when it is blocked. This means that !TrueQ@$TeX will initially be True, and then after $TeX is blocked True, the condition will be false. The normal Convert`TeX`ExpressionToTeX code converts the expression into boxes using MakeBoxes[.., TraditionalForm].
Modifying Format
TagSetDelayed Format
The next step is to modify Format so that it creates a $TeX conditioned TraditionalForm MakeBoxes rule instead of a TeXForm Format rule. Before I do that, let's see what kind of FormatValues TeXForm typically creates:
foo /: Format[foo, TeXForm] := bar
FormatValues[foo] //FullForm
List[RuleDelayed[HoldPattern[Format[foo,TeXForm]],bar]]
Instead of the above, I want to create a TraditionalForm MakeBoxes rules. The following code does this:
Initial[Format] /: TagSetDelayed[sym_Symbol, Verbatim[Format][x_, TeXForm], rhs_] := With[
{fmt = TraditionalForm},
Initial[sym] /: MakeBoxes[x, fmt] /; $TeX := MakeBoxes[rhs, fmt]
]
Let's try the above Format again:
Clear[foo];
foo /: Format[foo, TeXForm] := bar
FormatValues[foo]
{HoldPattern[MakeBoxes[foo, TraditionalForm] /; $TeX] :>
MakeBoxes[bar, TraditionalForm]}
Notice that there is no TeXForm rule any more, only a $TeX conditioned MakeBoxes rule. Also, the FormatValues is attached to foo, and not to MakeBoxes. This is exactly what we want. Modifying Format to do this is easy when TagSetDelayed is used, since the relevant tag symbol (needed for the MakeBoxes rule) is given in the input.
SetDelayed Format
A typical Format statement doesn't bother including the tag, so it would be nice to have this version work as well. Here is one way to do that:
Initial[Format] /: SetDelayed[Verbatim[Format][x_, TeXForm], rhs_] := With[
{s = getTagSymbol[Format[x, TeXForm]], fmt = TraditionalForm},
Replace[s,
HoldForm[tag_] :> (
Initial[tag] /: MakeBoxes[x, fmt] /; $TeX := MakeBoxes[rhs, fmt]
)
]
]
SetAttributes[getTagSymbol, HoldFirst]
getTagSymbol[Format[x_, TeXForm]] := Module[{dummy, t},
extractTag[Hold[Message[Format::tag, HoldForm@Format, _, tag_], False]] := t = tag;
Internal`HandlerBlock[{Message, extractTag},
Quiet[dummy[1] /: Format[x, TeXForm] := 1]
];
t
]
The above code uses getTagSymbol to figure out what the correct tag is for a given Format LHS. It does this by using a dummy Module variable as the tag, so that the Format rule will issue an error message telling us what the tag symbol should be. For instance:
dummy /: Format[x, InputForm] := 2
Format::tag: Rule for Format of x can only be attached to x.
$Failed
The Internal`HandlerBlock code intercepts this Message to figure out what the tag symbol is, and a Quiet is used because we are intentionally creating an error. A couple examples of getTagSymbol in action:
getTagSymbol[Format[x, TeXForm]]
getTagSymbol[Format[s_String, TeXForm]]
getTagSymbol[Format[foo[bar_], TeXForm]]
HoldForm[x]
HoldForm[String]
HoldForm[foo]
Finally, here is an example of an ordinary Format statement:
Format[foo2, TeXForm] := bar2
FormatValues[foo2]
{HoldPattern[MakeBoxes[foo2, TraditionalForm] /; $TeX] :>
MakeBoxes[bar2, TraditionalForm]}
Again, there is no Format rule, and the MakeBoxes rule has the $TeX condition as desired. With the above rules, we now get:
ToString[foo + foo2, TeXForm]
"\text{bar}+\text{bar2}"
The above code is sufficient for effectively making the TeXForm Format rules act recursively.
Examples
Example 1
My first example will be similar to the OP example, except that I won't use a string RHS. Instead, I will define:
Format[x, TeXForm] := Style["x", FontWeight->"Bold"]
Then, the TeXForm for a few examples are (I use CellPrint so that all examples print, to avoid the issue discussed in (138916)):
CellPrint@TextCell[ToString[#, TeXForm], "Output"]& /@ {x, x+1, Sin[x^2+1]};
\pmb{\text{x}}
\pmb{\text{x}}+1
\sin \left(\pmb{\text{x}}^2+1\right)
And this is what they look like when using MathJax:
$\pmb{\text{x}}$
$\pmb{\text{x}}+1$
$\sin \left(\pmb{\text{x}}^2+1\right)$
Example 2
This example comes from question (153876):
Format[FresnelC[x_], TeXForm] := Defer[FresnelC][x]
Format[CosIntegral[x_], TeXForm] := Defer[CosIntegral][x]
Then, nested functions do the right thing:
HoldForm[1+FresnelC[1+FresnelC[CosIntegral[z]]]] //TeXForm
1+\text{FresnelC}(1+\text{FresnelC}(\text{CosIntegral}(z)))
OP Example
Finally, let's look at the OP Example:
Format[x, TeXForm] := "{\\bf x}"
x //TeXForm
\text{$\{\backslash \backslash $bf x$\}$}
What happened here? Without my Format changes, the Format statement will match x, and then return "{\b x}", short-circuiting the MakeBoxes/BoxesToTeX code. On the other hand, my Format changes mean that there are no Format statements to match, they have all been turned into MakeBoxes rules. So, there is no short-circuit, and the BoxesToTeX code applies its rules to the boxes to produce the output you see. It is the same thing as doing:
"{\\bf x}" //TeXForm
\text{$\{\backslash \backslash $bf x$\}$}
I think this treatment of strings is rather suboptimal.
I think the way to proceed here is to change how the BoxesToTeX code deals with strings. MakeBoxes produces 2 kinds of strings. For example:
MakeBoxes[x] //FullForm
MakeBoxes["x"] //FullForm
"x"
"\"x\""
Basically, strings get converted into "quoted" strings, and everything else gets converted into "unquoted" strings. Currently, the BoxesToTeX code treats both kinds of strings equivalently. For example:
Convert`TeX`BoxesToTeX["\"{\\\\bf x}\""]
Convert`TeX`BoxesToTeX["{\\\\bf x}"]
"\text{\$\{\backslash \backslash \$bf x\$\}\$}"
"\text{\$\{\backslash \backslash \$bf x\$\}\$}"
So, one idea is to leave the conversions of "quoted strings" as they are, but to change the conversions of "unquoted strings". This way anybody who relies on the current behavior of TeXForm[string] will see that behavior maintained (although I suspect that nobody relies on the current behavior, and the best thing to do is to eliminate this string conversion behavior entirely).
The following code only changes the conversion of unquoted strings that are syntactically valid TeX:
System`Convert`TeXFormDump`maketex[s_String] /; !StringMatchQ[s, "\""~~___~~"\""] && SyntaxQ[s, TeXForm] := Replace[
s,
{
n_ /; StringMatchQ[n, NumberString] :> n,
w_?wordQ :> "\\operatorname{"<>w<>"}"
}
]
wordQ[s_String] := Length @ StringSplit[s, WordBoundary] == 1
Note that numbers are left alone, single word strings (corresponding to operators) are wrapped in \operatorname, and other strings are just left alone (since they pass the TeXForm SyntaxQ check).
Finally, the Format rule needs to be changed slightly because we want the TraditionalForm boxes to not have quotes:
Format[x, TeXForm] := "{\\bf x}"
Block[{$TeX=True}, MakeBoxes[x, TraditionalForm]] //InputForm
"\"{\\bf x}\""
Since the RHS of the Format statement is a string, the corresponding boxes are a quoted string. To make the corresponding boxes an unquoted string (so that the new string TeX code is used), we need to use RawBoxes:
Format[x, TeXForm] := RawBoxes @ "{\\bf x}"
Block[{$TeX=True}, MakeBoxes[x, TraditionalForm]] //InputForm
"{\bf x}"
Now, the OP example should work:
x^2+1 //TeXForm
{\bf x}^2+1
Code
Here is the relevant code in a single code block:
Initial /: Verbatim[TagSetDelayed][Initial[sym_], lhs_, rhs_] := With[
{
new = Block[{sym},
TagSetDelayed[sym, lhs, rhs];
First @ Language`ExtendedDefinition[sym]
],
protect = Unprotect[sym]
},
sym;
Replace[new,
Rule[values_, n:Except[{}]] :> (
values[sym] = DeleteDuplicates@Join[n, values[sym]]
),
{2}
];
Protect@protect;
]
Initial[Convert`TeX`ExpressionToTeX] /:
Convert`TeX`ExpressionToTeX[e__] /; !TrueQ@$TeX := Block[
{$TeX = True},
Convert`TeX`ExpressionToTeX[e]
]
Initial[Format] /: TagSetDelayed[sym_Symbol, Verbatim[Format][x_, TeXForm], rhs_] := With[
{fmt = TraditionalForm},
Initial[sym] /: MakeBoxes[x, fmt] /; $TeX := MakeBoxes[rhs, fmt]
]
Initial[Format] /: SetDelayed[Verbatim[Format][x_, TeXForm], rhs_] := With[
{s = getTagSymbol[Format[x, TeXForm]], fmt = TraditionalForm},
Replace[s,
HoldForm[tag_] :> (
Initial[tag] /: MakeBoxes[x, fmt] /; $TeX := MakeBoxes[rhs, fmt]
)
]
]
SetAttributes[getTagSymbol, HoldFirst]
getTagSymbol[Format[x_, TeXForm]] := Module[{dummy, t},
extractTag[Hold[Message[Format::tag, HoldForm@Format, _, tag_], False]] := t = tag;
Internal`HandlerBlock[{Message, extractTag},
Quiet[dummy[1] /: Format[x, TeXForm] := 1]
];
t
]
System`Convert`TeXFormDump`maketex[s_String] /; !StringMatchQ[s, "\""~~___~~"\""] && SyntaxQ[s, TeXForm] := Replace[
s,
{
n_ /; StringMatchQ[n, NumberString] :> n,
w_?wordQ :> "\\operatorname{"<>w<>"}"
}
]
wordQ[s_String] := Length @ StringSplit[s, WordBoundary] == 1
And, here again is the OP example:
(* OP example *)
Format[x, TeXForm] := RawBoxes @ "{\\bf x}"
x^2 + 1 //TeXForm
{\bf x}^2+1
Format[s]:=rhs defines a symbol s to print like rhs.– Grzegorz Rut May 07 '14 at 09:41Formatever worked (reliably) as advertised? Not in my experience. Just useMakeBoxesinstead; forget aboutFormat. :) – Oleksandr R. May 07 '14 at 12:31$BoxForms, which areStandardFormandTraditionalForm. These are called byTeXFormon its argument, and the latter then displays as the $\TeX$ corresponding to the resulting boxes. It isn't the most elegant approach, I admit, but without knowledge of how theTeXFormdisplay is obtained internally (probably the the FE sees it and produces some special boxes?), this hijacking seems to be the best we can do. – Oleksandr R. May 07 '14 at 13:00TeXFormwithInputForm, suggesting that the problem is mainly associated withTeXFormrather thanFormat. Please keep us updated on the response to the bug report. – cyberSingularity May 09 '14 at 12:25Format[bin[x_, y_], TeXForm] := MatrixForm[{{x}, {y}}]in theTeXFormreference does work... – cyberSingularity May 09 '14 at 12:37Formatrule is applied inTeXForm[x]. It is possible that a format is applied inTeXFormonly when it matches the whole expression. – Michael E2 May 09 '14 at 12:37