the Fiddler's picture

Text rendering on Mono - investigate GDI+ alternatives

Project:The Open Toolkit library
Version:0.9.x-dev
Component:Code
Category:bug report
Priority:normal
Assigned:the Fiddler
Status:won't fix
Description

OpenTK relies on System.Drawing (GDI+) to render glyphs and lay them out to form text. Unfortunately, Mono's implementation of GDI+ suffers from several bugs: it cannot handle newline characters or word-wrapping.

We have worked around the first issue by splitting text on newline characters. This allows text rendering to work on Mono, but puts pressure on the GC. Unfortunately, there is no workaround for the second issue.

There are two solutions to this issue:

  • Wait for Mono to finish it's new Pango-based text renderer, which will (hopefully) fix the bugs. This won't likely appear before Mono 2.8 (scheduled for December 2009 or somewhere near to that).
  • Implement our own Mono.Pango-based IGlyphRasterizer.

The internal IGlyphRasterizer interface (OpenTK.Utilities) defines the methods for the rasterizer. There are two main methods in IGlyphRasterizer:

  1. MeasureText, which returns a collection of TextExtent instances describing the layout of the text (one TextExtent per character).
  2. Rasterize, which rasterizes a glyph into a System.Drawing.Bitmap

We need to find how these methods can be implemented with Pango/Mono.Pango. As far as I can see, we need three things:

  1. Create a Pango context. I think we need to create a Cairo or GTK# context first.
  2. Map the desired System.Drawing.Font to a Pango.Font.
  3. Perform text layout and glyph rasterization using either high- or low-level Pango methods.

Once these are implemented, we can simply hook the new PangoGlyphRasterizer into the existing engine and use it when OpenTK.Configuration.RunningOnMono is true.

Any help with tasks 1 - 3 will be appreciated!

You can download Mono.Pango from mono-project.com (it is in the GTK# installer). It should come pre-installed on Ubuntu.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
triton's picture

#1

I have been reading the Pango docs and it seems pretty straightforward what needs to be done. I'll talk in terms of the C API, I haven't checked out the C# API, but it shouldn't be too difficult to map the functions.

1. As you said, a Pango context needs to be created. This can be done with pango_context_new().

2. Well, I don't know how System.Drawing.Font works, but to create a font in Pango we can use pango_font_description_from_string() which creates a new FontDescription from the string describing the font. I think the information necessary can be obtained from the System.Drawing.Font fields, but I haven't looked at it more closely.

3. To get the measures from the text, I think it's easier to use the high-level interface of Pango. A new Layout can be created with pango_layout_new(), passing the previously created context. Then it needs to be setup with the font description obtained ealier and the text to be layouted.

To get each glyph, we need to iterate by ever line in the layout. We call pango_layout_get_iter() and get a PangoLayoutIter that can be used for iterating. I didn't quite understand what a 'run' means in Pango (the docs are quite poor) but I think it's what we need to use to iterate by all the glyphs in the iterator (it says: "(...) it is simply an alternate name for PangoGlyphItem.).

We get each 'run' by calling pango_layout_iter_get_run_readonly(). There is also another approach to this, by first iterating through the lines and then getting each glyph in a line, but if I understood correctly, this 'run' stuff lets us iterate only once by everything in every line and returns NULL if we got to the end of a line.

Getting the information for each glyph is then a matter of calling the corresponding functions in Pango. I don't know what info OpenTK needs, so if someone can check the functions, and see if all the stuff is there (http://library.gnome.org/devel/pango/stable/pango-Glyph-Storage.html#Pan...).

Regarding the rendering, what backend is more interesting to use to render the glyphs? The Pango docs mention different alternatives we can choose, like using the Cairo library, FreeType 2 or Xft.

the Fiddler's picture

#2

Assigned to:Anonymous» the Fiddler
Status:open» in progress

Thanks for the explanation. I have managed to implement points 1 and 2 using Mono.Pango and Mono.Cairo and I'm reading the docs for LayoutIter for point 3.

I have attached a (very) small project with the implementation so far. It relies on GTK#, which can be found on mono-project.com (it comes preinstalled on Ubuntu).

Edit: We'll probably use Cairo for font rendering, if only because the API is very simple.

AttachmentSize
Pango.zip12.47 KB
the Fiddler's picture

#3

The following code implements point 3. Unfortunately, the GetCharExtents signature uses pass-by-value semantics that don't allow this to work. Why can't life be simple for once?

            Pango.LayoutIter iter = pango_layout.Iter;
 
            do
 
            {
 
                Pango.Rectangle rect = new Pango.Rectangle();
 
                iter.GetCharExtents(rect);
 
                Console.WriteLine("({0}, {1}) - ({2}, {3})",
 
                    rect.X, rect.Y, rect.Width, rect.Height);
 
            }
 
            while (iter.NextChar());
the Fiddler's picture

#4

And success!

        System.Drawing.Rectangle GetCharExtents(Pango.LayoutIter iterator)
        {
            Pango.Rectangle rect = new Pango.Rectangle();
            pango_layout_iter_get_char_extents(iterator.Handle, ref rect);
 
            return new System.Drawing.Rectangle(
                rect.X, rect.Y, rect.Width, rect.Height);
        }
 
        [DllImport("libpango-1.0.so.0")]
        private static extern void pango_layout_iter_get_char_extents(
            IntPtr layout_iter, ref Pango.Rectangle logical_rect);

The final step is to figure out how to upload the contents of a Cairo surface onto a texture.

triton's picture

#5

I wasn't really understanding what a run was, so I went to IRC. This was the answer:

owen wrote:

owen_> a run is a consecutive sequence of glyphs in the same font, with the same rendering attributes (color, underline, etc), and in the same direction (left-to-right, right-to-left)

I booted my Ubuntu VM and tested your code and it worked fine. Now regarding your last post, did you hit a binding bug? From what I can see, the parameters should be 'out' or 'ref', right?

the Fiddler's picture

#6

Indeed, GetCharExtents should actually be "out Rectangle". I checked the Mono.Pango source and it seems this part is not finished yet. I've filed a bug at https://bugzilla.novell.com/show_bug.cgi?id=510105, but fortunately the issue was easy to work around using the code from my last post.

The final issue is also easy to solve: you can get the raw data from a cairo ImageSurface using the DataPtr property.

I must admit that I'm very impressed with the Cairo and Pango APIs. They are both clean and intuitive, unlike say GDI+. Using Pango.TextLayout, I've managed to recreate weeks worth of GDI/GDI+ research, in just a couple of hours.

Thank you for your help, triton. I am going to put all this together and add it to OpenTK over the next few days. If anything else comes up, I'll make a post.

Edit: regarding text runs, they are a step above the TextPrinter. Conceptually, every call to TextPrinter.Print renders a single text run. I'm working on a "FormattedText" class that is composed of multiple text runs, but I don't know if that will ever become part of OpenTK (I'll upload it to the database when it's ready).

Things would be very different if .Net text-rendering wasn't so hopelessly broken...

the Fiddler's picture

#7

I have copied and simplified the necessary Cairo and Pango binaries, which means the test project no longer relies on Mono.Pango and Mono.Cairo being installed. This was done mainly to avoid introducing new build dependencies.

I'll push the code to OpenTK for testing, once I get glyph rasterization up and running.

the Fiddler's picture

#8

Status:in progress» won't fix

The TextPrinter project will be spun off from the main OpenTK distribution. Maintainers wanted.

triton's picture

#9

Couldn't it be part of an OpenTK.Extras package or something?

I think TextPrinter and maybe TexUtil could be provided just for helping the beginners get something running quickly.

the Fiddler's picture

#10

It has been moved to OpenTK.Compatibility.

The will continue to work, but unless someone steps up the TextPrinter won't be developed any further. This means that stuff like OpenGL 3.0 or Mono.Pango will not be added.