# Is there a way to outline text?

If I have:

Graphics[
{Red, EdgeForm[Directive[White, Thick]],
Inset[Style[Text@"Hi!", 44], {0, 0}]}, Background -> Black
]


I unfortunately get:

Which as you can see does not have a thick white outline. Is there a way to get around this since EdgeForm clearly does not work?

By the way, I would rather NOT delve into making a larger, white "Hi" and then putting the red one on top. That's just not elegant.

MMA 8.0.1 for students
OS Windows 7 64-bit

-
there was a question on Stack Overflow strongly related to this one. –  belisarius Jul 13 '12 at 20:02
Oh, I did not know. I sort of gave up googling for MMA help since there usually isn't anything except for stuff here. Also, I can't find it. –  VF1 Jul 13 '12 at 20:04

Import text as a FilledCurve in graphics, using PDF as an intermediate format. Below are modified examples from Documentation Center:

text = First[First[ImportString[ExportString[Style["Hi", Italic, FontSize -> 24,
FontFamily -> "Times"], "PDF"], "PDF", "TextMode" -> "Outlines"]]];


Outline fonts using different edge and face forms:

Graphics[{EdgeForm[Directive[White, Thick]], Red, text},
Background -> Black, PlotRange -> {{-5, 25}, {-0, 20}}]


3D text effect:

Graphics[{EdgeForm[Opacity[0.5]], Table[{ColorData["TemperatureMap"][t],
Translate[text, 4 {-t, t}]}, {t, 0, 1, 1/10}]}, ImageSize -> Medium]


-
Vitaliy, very nice! –  alancalvitti Jul 13 '12 at 20:36
Cool. But is this really the only way? It's a bit slow to convert it to a curve and I need to display the graphics quickly in my game. The text changes based on the game's statistics, so precomputing's not an option... –  VF1 Jul 13 '12 at 20:55
@vlad you can do the curve conversion on a letter basis in advance. You'd need a kerning table as well, but that shouldn't be too difficult to generate. –  Sjoerd C. de Vries Jul 13 '12 at 21:57
@vlad, the idea by SjoerdC.deVries is indeed a good solution. Otherwise you're risking to end up "delving into making a larger, white "Hi" and then putting the red one on top" ;-) –  Vitaliy Kaurov Jul 13 '12 at 22:02
While clever it strikes me as really quite atrocious to have to do an Export->Import to get the outlines. I keep hoping someone will find or share an internal function that does this. :-/ –  Mr.Wizard Jul 14 '12 at 1:10

An important issue in the question seems to be that of speed. So as Sjoerd suggested, I wrote a solution that pre-outlines all the characters in a reasonable range of ASCII characters, and then does the replacements on an arbitrary string. The characters are stored in a table ascii, and their graphic replacements in asciiGraphics. I then define the replacement rule (rule) which is part of the function makeText:

ascii = CharacterRange[" ", "z"]

(*
==> {" ", "!", "\"", "#", "\$", "%", "&", "'", "(", ")", "*", \
"+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", \
"9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", \
"G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", \
"U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "", "a", \
"b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", \
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
*)

asciiGraphics =
First@ImportString[
ExportString[Style[#, FontFamily -> "Times", FontSize -> 44],
"PDF"], "TextOutlines" -> True] & /@ ascii;

Clear[makeText];
Options[makeText] = {"OutlineThickness" -> 1, "OutlineColor" -> White,
"Color" -> Red};
makeText[string_, OptionsPattern[]] :=
DisplayForm[
Row[Characters[string] /. rule] /.
FilledCurve[
x__] :> {EdgeForm[{AbsoluteThickness[
OptionValue["OutlineThickness"]],
OptionValue["OutlineColor"]}],
OptionValue["Color"],
FilledCurve[x]
}
]


The argument to makeText is the string to be rendered as outlined text. The thickness of the outlines is specified by the option "OutlineThickness". The other two options are "OutlineColor" and "Color" (of the filled areas).

The following is a demo - you win the game by sliding the slider to the right...

Manipulate[
Style[Pane[
makeText["Player 1 has " <> ToString[Floor[p]] <> " points",
"OutlineThickness" -> .7], ImageSize -> 360],
Background -> Black,
Magnification -> 3
], {p, 0, 1000}]


Edit

In response to the question in the comment:

By using Dispatch you can eke out a faster timing, and my focus was on getting fast execution. It won't matter much if you're dealing only with a short string. But if you're trying to translate a long text into outlined characters, the replacement is sped up when you first apply Dispatch to the rule.

-
Intresting. But what is the role of Dispatch? –  VF1 Jul 14 '12 at 2:44
By using Dispatch you can eke out a faster timing, and my focus was on getting fast execution. It won't matter much if you're dealing only with a short string. But if you're trying to translate a long text into outlined characters, the replacement is sped up when you first apply Dispatch` to the rule. –  Jens Jul 14 '12 at 4:08
@Vlad Speed is the biggest hurdle when doing anything interactive in Mathematica - I hope you'll be able to overcome that for your game... –  Jens Jul 14 '12 at 4:09