34
$\begingroup$

If I have:

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

I unfortunately get:
hi
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

$\endgroup$
2
  • $\begingroup$ there was a question on Stack Overflow strongly related to this one. $\endgroup$ Commented Jul 13, 2012 at 20:02
  • 2
    $\begingroup$ 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. $\endgroup$
    – VF1
    Commented Jul 13, 2012 at 20:04

5 Answers 5

35
$\begingroup$

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}}]

enter image description here

3D text effect:

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

enter image description here

$\endgroup$
9
  • $\begingroup$ Vitaliy, very nice! $\endgroup$ Commented Jul 13, 2012 at 20:36
  • 3
    $\begingroup$ 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... $\endgroup$
    – VF1
    Commented Jul 13, 2012 at 20:55
  • 1
    $\begingroup$ @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. $\endgroup$ Commented Jul 13, 2012 at 21:57
  • 5
    $\begingroup$ 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. :-/ $\endgroup$
    – Mr.Wizard
    Commented Jul 14, 2012 at 1:10
  • 3
    $\begingroup$ This is now outdated, now text = First[First[ImportString[ExportString[Style["Hi", Italic, FontSize -> 24, FontFamily -> "Times"],"PDF"], {"PDF", "PageGraphics"},"TextMode" -> "Outlines"]]]; should be used as per the documentation. $\endgroup$
    – bRost03
    Commented Jul 30, 2021 at 22:50
14
$\begingroup$

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;

rule = Dispatch[Thread[ascii -> asciiGraphics]];


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}]

manipulate

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.

$\endgroup$
3
  • $\begingroup$ Intresting. But what is the role of Dispatch? $\endgroup$
    – VF1
    Commented Jul 14, 2012 at 2:44
  • 1
    $\begingroup$ 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. $\endgroup$
    – Jens
    Commented Jul 14, 2012 at 4:08
  • $\begingroup$ @Vlad Speed is the biggest hurdle when doing anything interactive in Mathematica - I hope you'll be able to overcome that for your game... $\endgroup$
    – Jens
    Commented Jul 14, 2012 at 4:09
12
$\begingroup$

In version 10, we can bypass exporting to PDF and re-importing and can outline text directly using region discretization functions. This is shown in the documentation of BoundaryDiscretizeGraphics.

reg = BoundaryDiscretizeGraphics[Text[Style["R", FontFamily -> "Cambria"]], _Text]

Mathematica graphics

The region can then be converted back to graphics, and the FilledCurve extracted.

WARNING: I just realized that the following line will often crash the kernel with 10.1.0 on OS X! Proceed with caution!

Cases[Normal@Show[reg], _FilledCurve, Infinity]

Normal is for getting rid of GraphicsComplex inside the resulting Graphics object.

If there is a direct way to go from a BoundaryMeshRegion to a FilledCurve, without having to use Show, please let me know!

$\endgroup$
5
  • $\begingroup$ weird, RegionBoundary[reg]//FullForm shows the polygon data, but there is no evident way to programmatically extract it. Apply doesn't work.. on a MeshRegion $\endgroup$
    – george2079
    Commented Jul 16, 2015 at 19:31
  • $\begingroup$ I'm amazing you can discretize a word,and how do you find the pattern of "_Text"??Do you help me discretize a Tube?Such as "Graphics3D[Tube[{{0, 0, 0}, {1, 1, 1}}, .1]]"? $\endgroup$
    – yode
    Commented Sep 25, 2015 at 18:35
  • $\begingroup$ @yode It's in the documentation I linked to, almost this very example. I really don't know more than what's there. $\endgroup$
    – Szabolcs
    Commented Sep 25, 2015 at 20:45
  • $\begingroup$ If you provide character i, it would generate Polygon instead of FilledCurve. $\endgroup$
    – kh40tika
    Commented Dec 20, 2016 at 11:07
  • $\begingroup$ @user1950580 I've done this now where I search for either of them. It suffices to get all the cases I've tried. $\endgroup$
    – b3m2a1
    Commented Mar 17, 2017 at 5:11
3
$\begingroup$

You can do

Graphics[{Haloing[White], Text[Style["Hi", 40, Red]]}, 
 Background -> Black]

enter image description here Haloing was introduced in 13.3

$\endgroup$
2
$\begingroup$

A quick and dirty way to bypass exporting to PDF and re-importing is to put the text on a background of the thickened text, and the text thickening can be achieved by the trick of translating the text around by a small amount.

For example, we have a text to outline:

text = Text[Style["Hello!", Italic, FontSize -> 24]];
Graphics[{White, text}, Background -> Black, PlotRange -> 1, ImageSize -> 100]

enter image description here

We can use Translate to move the text around and CirclePoints to generate a set of uniformly distributed translation vectors along different directions. By dragging the text around, we effectively thicken the text.

Graphics[{{White, Translate[text, 0.02 #] & /@ CirclePoints[32]}}, 
 Background -> Black, PlotRange -> 1, ImageSize -> 100]

enter image description here

Now we can put the original text on top of this thickened background, which effectively creates an outline effect for the text.

Graphics[{{White, Translate[text, 0.02 #] & /@ CirclePoints[32]}, text}, 
 Background -> Black, PlotRange -> 1, ImageSize -> 100]

enter image description here

One may also adjust the thickness of the outline by changing the amount of translation for the background text.

Graphics[{{White, Translate[text, 0.05 #] & /@ CirclePoints[32]}, text}, 
 Background -> Black, PlotRange -> 1, ImageSize -> 100]

enter image description here

This method is much faster than the PDF export-import and the BoundaryDiscretizeGraphics methods. It applies to any text that can be displayed by the Text method without worrying about breaking text into characters and patching them together again (for example, preserving the spacing between characters in a row and between multiple lines can be painful).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.