In this article, you will learn how to draw text using Cairo from C# with full control over character positioning, linebreaking...
Introduction
I became more and more unhappy with the text drawing capabilities of Xlib/X11, I do heavily use for my Roma Widget Set (C# X11) project. Especially the laborious internationalization of strings and the lack of antialiasing became an increasing limitation to the project.
Fortunately, Cairo and Pango libraries provide a professional text output, that is based on UTF-8 encoded strings and provide a lot of cool display features - including antialiasing, gradients and outline. Both libraries have been integrated into most Linux distributions.
The GTK+ UI toolkit uses Cairo to render the majority of its controls, starting with version 2.8 from 2005, and it also uses Pango for its text rendering, starting with version 2.0 from 2002.
Background
Since the Mono.Cairo package already wraps the Cairo C API, it is obvious to try text drawing using this package. Unfortunately, only the 'toy' text API is provided by Mono.Cairo
. Although Cairo.Context.ShowText()
and Cairo.Context.TextExtents()
provide a lot of cool text display features - including antialiasing - the functionality is insufficient because it processes the text to draw always at once.
Usage of Cairo.Context.ShowGlyphs()
and Cairo.Context.GlyphsExtents()
can overcome this limitation, but there is no Mono.Cairo
method to convert UTF-8 strings into glyphs. And this type of convertion is a hard job to do:
- Not only that glyph indices differ from font family to font family (see the application screenshot and take a look at the lines "'Luxi sans' writing three '36' glyphs: AAA" and "'Utopia' writing three '33' glyphs: AAA" - the same output "AAA" but different glyph indices),
- the glyphs inside a font family are organized in a cmap and a cmap can have different formats (search the web for "character to glyph mapping" or "OpenFont glyph" to get more information).
Typically, this conversion is the job of the Pango library, but there is no Pango wrapper package available for Mono, that provides Pango separately. Instead, Pango is highly integrated into the gtk-sharp package.
Since my Roma Widget Set project should completely avoid any GTK+ stuff (not because it is bad, only to prevent a competition to Gtk#), I had to find a different way to convert UTF-8 strings into glyphs. Ironically, the C# sources for Cairo contained in the gtk-sharp
package lead me to a feasible solution.
Using the Code
The sample application was written with Mono Develop 2.4.1 for Mono 2.8.1 on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. Neither the port to any older nor to any newer version should be a problem. The sample application's solution consists of one project containing all the necessary source code.
The sample application is also tested with Mono Develop 3.0.6 for Mono 3.0.4 on OPEN SUSE 12.3 Linux 64 bit DE and GNOME desktop, IceWM, TWM und Xfce.
The Xlib/X11 window handling is based on the X11Wrapper assembly version 0.5, that defines the function prototypes, structures and types for Xlib/X11 calls to the libX11.so. It has been developed for the Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept) project and has been advanced during the Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Part 1, Basics project.
The sample application shows some text output using:
- Cairo's 'toy' text API
- a self-provided wrapper around basic Cairo 'toy' text API functions (like
cairo_get_current_transformation_matrix()
, cairo_set_current_transformation_matrix()
, cairo_set_source_rgba()
, cairo_move_to()
, cairo_show_text()
and cairo_set_scaled_font()
) - and - finally - the self-provided string-to-glyphs converter using Cairo's
cairo_scaled_font_text_to_glyphs()
The sample application is provided with full source code. The basic steps to use the self-provided string-to-glyphs converter looks like this:
context.SelectFontFace ("Sans", FontSlant.Normal, FontWeight.Normal);
Cairo.FontFace ffSans = context.ContextFontFace;
Cairo.Matrix fm = new Cairo.Matrix ( 20.0, 0.0, 0.0, 20.0,
0.0, 0.0);
Cairo.Matrix tm = new Cairo.Matrix (1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
FontOptions fo = new FontOptions();
Cairo.ScaledFont sfSans = new ScaledFont (ffSans, fm, tm, fo);
fo.Dispose ();
Cairo.CairoWrapper.SetScaledFont (context, sfSans);
Cairo.CairoWrapper.MoveTo (context, 15, 270);
Cairo.CairoWrapper.ShowText (context, "'Sans' writing some glyphs converted with");
Cairo.Glyph[] glyphs;
Cairo.CairoWrapper.ScaledFontTextToGlyphs (sfSans,
"the self-provided converter: μ-∑-√-‡-€-™", 15, 295, out glyphs);
Cairo.CairoWrapper.ShowGlyphs (context, glyphs);
sfSans.Dispose();
ffSans.Dispose();
The next step could be to move the additional Cairo wrapper functionality to Cairo method extensions.
To play with the executables, start either /bin/Debug/32/XrwCairo.exe on 32 bit systems or /bin/Debug/64/XrwCairo.exe on 64 bit systems.
To load the project, use either the XrwCairo32.sln on 32 bit systems or the XrwCairo64.sln on 64 bit systems.
Main Findings
Standard Encoding to UTF-8 Encoding Convertion
The C# sources for Cairo contained in the gtk-sharp
package already include a private
method TerminateUtf8()
that does the job reasonably good - only one thing is annoying:
The UTF-8 encoding of a character can consume 1 up to 3 byte. The TerminateUtf8()
implementation returns always a byte array, long enough to store the worst case (all characters require the maximum of bytes to be converted). Unused bytes are set to 0
.
This is where the self-provided TerminateUtf8
() comes into play.
private static byte[] TerminateUtf8 (string s, bool clean)
{
int byteCount = System.Text.Encoding.UTF8.GetMaxByteCount(s.Length + 1);
byte[] bytes = new byte[byteCount];
System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0);
if (!clean)
return bytes;
int realLength = byteCount;
for (int countByte = byteCount - 1; countByte >= 0 && bytes[countByte] == 0; countByte--)
realLength--;
byte[] result = new byte[realLength + 1];
if (realLength > 0)
Array.Copy (bytes, result, realLength);
result[realLength] = 0;
return result;
}
Managed Glyph to Unmanaged Memory Convertion (And Return)
The C# sources for Cairo contained in the gtk-sharp
package also include an internal
method FromGlyphToUnManagedMemory()
that is needed to provide cairo_show_glyphs()
with glyphs. Unfortunately, this implementation depends in the Context
class implementation and can't be called separately. Here is my reengineered method:
internal static IntPtr FromGlyphToUnManagedMemory(Glyph [] glyphs)
{
IntPtr dest = IntPtr.Zero;
int ptrSize = Marshal.SizeOf (typeof (IntPtr));
if (ptrSize != 4)
{
int native_glyph_size = Marshal.SizeOf (typeof (Glyph));
dest = Marshal.AllocHGlobal (native_glyph_size * glyphs.Length);
long pos = dest.ToInt64();
foreach (Glyph g in glyphs)
{
Marshal.StructureToPtr (g, (IntPtr)pos, false);
pos += native_glyph_size;
}
}
else
{
int native_glyph_size = Marshal.SizeOf (typeof (NativeGlyph_4byte_longs));
dest = Marshal.AllocHGlobal (native_glyph_size * glyphs.Length);
long pos = dest.ToInt64();
foreach (Glyph g in glyphs)
{
NativeGlyph_4byte_longs n = new NativeGlyph_4byte_longs (g);
Marshal.StructureToPtr (n, (IntPtr)pos, false);
pos += native_glyph_size;
}
}
return dest;
}
Based on this, I also implemented the opposite direction FromUnManagedMemoryToGlyph()
, that is required for ScaledFontTextToToGlyph()
.
internal static Glyph[] FromUnManagedMemoryToGlyph (IntPtr ptr, int length)
{
Glyph[] glyphs = new Glyph[Math.Max (0, length)];
if (length <= 0)
return glyphs;
int ptrSize = Marshal.SizeOf (typeof (IntPtr));
if (ptrSize != 4)
{
int native_glyph_size = Marshal.SizeOf (typeof (Glyph));
long pos = ptr.ToInt64();
for (int glyphCount = 0; glyphCount < length; glyphCount++)
{
glyphs[glyphCount] = (Glyph) Marshal.PtrToStructure ((IntPtr)pos, typeof(Glyph));
pos += native_glyph_size;
}
}
else
{
int native_glyph_size = Marshal.SizeOf (typeof (NativeGlyph_4byte_longs));
long pos = ptr.ToInt64();
NativeGlyph_4byte_longs buffer;
for (int glyphCount = 0; glyphCount < length; glyphCount++)
{
buffer = (NativeGlyph_4byte_longs) Marshal.PtrToStructure ((IntPtr)pos,
typeof(NativeGlyph_4byte_longs));
glyphs[glyphCount] = new Glyph (buffer.index, buffer.x, buffer.y);
pos += native_glyph_size;
}
}
return glyphs;
}
Both implementations work for 32 bit and 64 bit environments and distinguish the environment using the size of a void*
pointer: Marshal.SizeOf (typeof (IntPtr))
.
UTF-8 Text to Glyph Convertion
The self-provided wrapper around cairo_scaled_font_text_to_glyphs()
looks like this:
public static Status ScaledFontTextToGlyphs(ScaledFont scaledFont, string utf8text,
double startX, double startY, out Glyph[] glyphs)
{
byte[] terminatedUtf8 = TerminateUtf8(utf8text, true);
IntPtr arrGlyph;
int numGlyph;
Status status =
NativeMethodsEx.cairo_scaled_font_text_to_glyphs (scaledFont.Handle, startX, startY,
terminatedUtf8,
terminatedUtf8.Length - 1,
ref arrGlyph, out numGlyph,
IntPtr.Zero,
IntPtr.Zero, IntPtr.Zero);
if (status != Status.Success)
{
glyphs = new Glyph[0];
return status;
}
if (arrGlyph != IntPtr.Zero && numGlyph > 0)
glyphs = FromUnManagedMemoryToGlyph (arrGlyph, numGlyph);
else
glyphs = new Glyph[0];
NativeMethodsEx.cairo_glyph_free (arrGlyph);
return status;
}
Where cairo_scaled_font_text_to_glyphs()
and the other native Cairo methods are defined as:
internal static class NativeMethodsEx
{
const string cairo = "libcairo-2.dll";
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern Status
cairo_scaled_font_text_to_glyphs (IntPtr scaled_font, double x, double y,
byte[] utf8, int utf8_len,
ref IntPtr glyphs, out int num_glyphs,
ref IntPtr clusters, out int num_clusters,
ref IntPtr cluster_flags);
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern Status
cairo_scaled_font_text_to_glyphs (IntPtr scaled_font, double x, double y,
byte[] utf8, int utf8_len,
ref IntPtr glyphs, out int num_glyphs,
IntPtr clusters, IntPtr num_clusters,
IntPtr cluster_flags);
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern void
cairo_show_text_glyphs (IntPtr scaled_font, byte[] utf8, int utf8_len,
IntPtr glyphs, int num_glyphs,
ref IntPtr clusters, ref int num_clusters,
ref IntPtr cluster_flags);
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern void cairo_glyph_free (IntPtr glyphs);
[DllImport (cairo, CallingConvention=CallingConvention.Cdecl)]
internal static extern void cairo_text_cluster_free (IntPtr glyphs);
}
Points of Interest
The Cairo.CairoWrapper
class contains all structures and helper methods necessary in addition to the Mono.Cairo
package, to convert strings into glyph arrays, measure the glyphs (to realize auto line breaking) and draw them.
History
- 14th July, 2014: Initial version
- 29th July, 2014: Some orthography errors and minor bugs