Text and fonts

A tale of two APIs

There are two ways to draw text in Luxor. You can use either the so-called 'toy' API or the 'pro' API. Both have their advantages and disadvantages. Also, font selection and availability varies a lot across the three operating systems. You may have to experiment to find code patterns that work for you.

The Toy API


  • text(string, [position]) to place text at a position, otherwise at 0/0, and optionally specify the horizontal and vertical alignment
  • fontface(fontname) to specify the fontname
  • fontsize(fontsize) to specify the fontsize
text("Georgia: a serif typeface designed in 1993 by Matthew Carter.", halign=:center)

text placement

(If the specified font is unavailable on the current system configuration, the default, usually Times/Helvetica or DejaVu, is used.)

The label function also uses the Toy API.

The Pro API


  • setfont(fontname, fontsize) to specify the fontname and size
  • settext(text, [position]) to place the text at a position, and optionally specify horizontal and vertical alignment, rotation (in degrees counterclockwise!), and the presence of any pseudo-Pango-flavored markup.
setfont("Georgia Bold", 16)
settext("Georgia: a serif typeface designed in 1993 by Matthew Carter.", halign="center")

text placement

Specifying the font ("Toy" API)

Use fontface(fontname) to choose a font, and fontsize(n) to set the font size. get_fontsize finds the current font size.

Specifying the font ("Pro" API)

To select a font in the Pro text API, use setfont and supply both the font name and a size.

Placing text ("Toy" API)

Use text to place a string. The left edge is placed at the origin, by default. Or you can supply a position. Use halign and/or valign to align the string at the position.

pt1 = Point(-100, 0)
pt2 = Point(0, 0)
pt3 = Point(100, 0)
text("text 1",  pt1, halign=:left,   valign = :bottom)
text("text 2",  pt2, halign=:center, valign = :bottom)
text("text 3",  pt3, halign=:right,  valign = :bottom)
text("text 4",  pt1, halign=:left,   valign = :top)
text("text 5",  pt2, halign=:center, valign = :top)
text("text 6",  pt3, halign=:right,  valign = :top)
map(p -> circle(p, 5, :fill), [pt1, pt2, pt3])
sethue("black") # hide
[text(string(θ), Point(40cos(θ), 40sin(θ)), angle=θ) for θ in 0:π/12:47π/24]

text rotation

Placing text ("Pro" API)

Use settext to place text. You can include some pseudo-HTML markup with the keyword argument markup=true.

settext("<span font='26' background ='green' foreground='red'> Hey</span>
    <i>italic</i> <b>bold</b> <sup>superscript</sup>
    angle=10) # degrees counterclockwise!

pro text placement

Writing LaTeX

It's possible to write math equations in $\LaTeX$ by passing a LaTeXString to the text function. Luxor uses MathTeXEngine.jl to parse the LaTeXString. You should load MathTeXEngine.jl (using MathTeXEngine) before accessing this feature.


MathTeXEngine.jl is a package that renders many LaTeXStrings without requiring a $\LaTeX$ compiler. The package uses the fonts Computer Modern and New Computer Modern. They're included with the MathTeXEngine package, and you can find them in your julia folder in packages/MathTeXEngine/.../assets/fonts. You should make sure these have been copied to your system's font directories before running Luxor and writing $\LaTeX$ strings.

using Luxor
using MathTeXEngine
path_svg = "latexexample.svg"
Drawing(600, 400, path_svg)
t₁ = L"e^{i\pi} + 1 = 0"
t₂ = L"e^x = \sum^\infty_{n=0} \frac{x^n}{n!} = \lim_{n\to\infty}(1+\frac{x}{n})^n"
t₃ = L"\cos(\theta)"

text(t₁, Point(0, -100), halign=:center, valign=:baseline, angle=0)
text(t₂, Point(0, -20), halign=:center, valign=:top, angle=0)

line(Point(0, 132), Point(50, 132), action = :stroke)
line(Point(0, 132), Point(50cos(π/4), 132 - 50sin(π/4)), action = :stroke)

text(t₃, Point(0, 120), halign = :left, valign = :baseline, angle = -π/4, rotationfixed = false)



The string macros in the LaTeXStrings.jl package allow you to enter LaTeX equations without having to escape backslashes and dollar signs (and they'll add the dollar signs for you if you omit them).

Notes on fonts

Fonts are loaded when you first start using Luxor/Cairo in a Julia session. This partly explains why starting a Luxor/Cairo session can take a few seconds.

On macOS, the fontname required by the Toy API's fontface should be the PostScript name of a currently activated font. You can find this out using, for example, the FontBook application.

On macOS, a list of currently activated fonts can be found (after a while) with the shell command:

system_profiler SPFontsDataType

Fonts currently activated by a Font Manager can be found and used by the Toy API but not by the Pro API (at least on my macOS computer currently).

On macOS, you can obtain a list of fonts that fontconfig considers are installed and available for use (via the Pro Text API with setfont) using the shell command:

fc-list | cut -f 2 -d ":"

although typically this lists only those fonts in /System/Library/Fonts and /Library/Fonts, and not ~/Library/Fonts.

(There is a Julia interface to fontconfig at Fontconfig.jl. See also FreeTypeAbstraction.jl)

In the Pro API, the default font is Times Roman (on macOS). In the Toy API, the default font is Helvetica (on macOS).

One difference between settext and text (on macOS) is that many more missing Unicode glyphs are automatically substituted by other fonts when you use the former.

Cairo.jl (and hence Luxor.jl) doesn't support emoji currently. 😢

Text is rasterized (converted from outlines to pixels) only when you output to the PNG format. For SVG formats, text is converted to outlines (curves and lines). For PDF and EPS formats, the fonts you use are stored inside the output file ("embedded"), and the text is displayed using that font only when the file is viewed.

For PNG files, the appearance of fonts when output is controlled to some extent by the operating system. For example, on Windows, if ClearType is active, differently-colored pixels are used to display fonts, because of the use of subpixel rendering. These colored pixels will be visible in the PNG output.

Text to paths

textoutlines(string, position) converts the text into graphic path(s), places them starting at position, and applies the action.

textoutlines("&", O, :path, valign=:middle, halign=:center)

text outlines

textpath converts the text into graphic paths suitable for further manipulation.

textpath preserves the Bézier curves, whereas textoutlines flattens all curves and converts them to polygons.

Text and font dimensions ("Toy" API only)

The textextents(str) function returns the dimensions of the string str, given the current font. There has to be a current drawing before this function is called.

width and height are stored in elements 3 and 4. The first two elements are the offsets ("bearings") from the reference point (green) to the bounding box. The last two elements determine where the next ("advance") character should start (blue).



There is currently no equivalent of this function for the "Pro" API.


The label function places text relative to a specific point, and you can use compass points or angles to indicate where it should be. So :N (for North) places a text label directly above the point, as does 3π/2.

octagon = ngon(O, 100, 8, 0, vertices=true)

compass = [:SE, :S, :SW, :W, :NW, :N, :NE, :E, :SE]

for i in 1:8
    circle(octagon[i], 5, :fill)
    label(string(compass[i]), compass[i], octagon[i], leader=true, leaderoffsets=[0.2, 0.9], offset=50)


Text on a circle

Use textcurve(str) to draw a string str on a circular arc or spiral.

sethue("royalblue4") # hide
textstring = join(names(Base), " ")
textcurve("this spiral contains every word in julia names(Base): " * textstring,
    350, 0, 0,
    spiral_in_out_shift = -8.0,
    letter_spacing = 0,
    spiral_ring_step = 0)
textcentered("julia names(Base)", 0, 0)

text on a curve or spiral

For shorter strings, textcurvecentered tries to place the text on a circular arc by its center point.

fontsize(24) # hide
sethue("black") # hide
setdash("dot") # hide
setline(0.25) # hide
circle(O, 100, action = :stroke)
textcurvecentered("hello world", -π/2, 100, O;
    clockwise = true,
    letter_spacing = 0,
    baselineshift = -20
textcurvecentered("hello world", π/2, 100, O;
    clockwise = false,
    letter_spacing = 0,
    baselineshift = 10

text centered on curve

Text on a polygon

Use textonpoly() to draw a string str that follows the route of a polygon.

            RGB(0.2, 0.2, 0.99),
            RGB(0.9, 0.2, 0.3),
            RGB(0.9, 0.9, 0.4),
            RGB(0.2, 0.8, 0.99),


    for y = -250:20:250
        points = [
                y + 30sin(x + rescale(y, -260, 250, 0, 2π)),
            ) for x = -2π:π/10:2π
        textonpoly("WAVES " ^ 15, points)