Fonts - Yet again

Windows XP
I converted my application over from using TAO.SDL and the SDL TTF library for rendering fonts to using the OpenTK fonts.
The OpenTK fonts don't have the correct pixel precision and the look blurry. Also the bottom of the font is getting cut off.
Am I missing a setting? I did find that if I change the font size to 8.25 instead of 9.0 the bottom of the font does not get cut off.

SDL Version

OpenTK Version (Updated with fixes resulting from this thread)

AttachmentSize
Program.cs3.13 KB

Comments

Re: Fonts - Yet again
posted by the Fiddler

The bluriness is a known issue (the glyphs are not snapped to the pixel grid), which will be corrected in the next version.

I'm looking into the font size issue. IIRC, glyph size is always rounded up, however I haven't really tested with fractional sizes.

Re: Fonts - Yet again
posted by the Fiddler

Ok, I've finally commited the code that fixes the bluriness. Can you please checkout the latest source and see how things are looking now?

I'm still working on the size issue.

Edit: I haven't been able to reproduce the size issue with the latest code. Could you please test if it still occurs?

Re: Fonts - Yet again
posted by JTalton

Sweet.

I'll check it out as soon as I can and let you know.

BTW I just have to say once again, how great I think the OpenTK project is. Thanks for putting so much work and thought into it.

Re: Fonts - Yet again
posted by JTalton

I currently get an error when building on my XP box. I'll give my other box a try later to see if it is an environment thing. - UPDATE: Other machine shows the same problem.

C:\opentk\Build>build net
Building OpenTK using .Net
[!] Match returned no files: .\OpenTK.Utilities.dll.config
Creating NAnt build files
...Creating project: Build
...Creating project: Examples
...Creating project: OpenTK
...Creating project: Bind
...Creating project: OpenTK.Utilities

Unhandled Exception: System.InvalidOperationException: No process is associated
with this object.
at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.get_ProcessName()
at OpenTK.Build.Project.ExecuteProcess(String path, String args)
at OpenTK.Build.Project.Main(String[] args)

Re: Fonts - Yet again
posted by the Fiddler

Make sure nant is in your path! Alternatively, type build vs and use the visual studio solution to build OpenTK.

I'm going to revamp the build system once nant 0.86 is released - the Build.exe script is a little icky.

Re: Fonts - Yet again
posted by JTalton

Looks good. Screen shot posted.

I'll test out the measurement functions another night.

Thanks!

Re: Fonts - Yet again
posted by the Fiddler

Indeed, looks much better. Out of curiosity, is cleartype enabled on your PC?

Re: Fonts - Yet again
posted by JTalton

I enabled cleartype and it did not seem to make a difference.

Re: Fonts - Yet again
posted by the Fiddler

Thanks for testing. That's normal I think - cleartype is disabled for sizes smaller than 10em to improve readability (compare the first and third screenshots).

I'm working on a more comprehensive font test now, to help weed out any remaining gremlins from this code. Did you notice any pixels getting cut off?

Re: Fonts - Yet again
posted by JTalton

There are some pixels still being cut off at different font sizes. Screen shot attached.
Also the font measurements are still a little off.

Re: Fonts - Yet again
posted by Inertia

It's a wild guess, but could it be that the string contains some kind of termination character/sequence that is measured too? It kinda looks like as if there was an extra char in the string, or maybe a rounding error.

Re: Fonts - Yet again
posted by the Fiddler

Well MeasureCharacterRanges doesn't measure \r\n\0 characters (these are positioned at (0,0)), so that's probably not it.

My guess is that the measurement is done with MeasureString while the positioning with MeasureCharacterRanges - and these two GDI+ methods return different results.

I'll write a quick test to see if that's the matter - if it is, I'll change the internal code to use MeasureCharacterRanges for everything.

Edit: Indeed, MeasureString returns different results than MeasureCharacterRanges. I am working on a fix right now.

Edit 2: Fix commited. I'm working on the cutoff issue right now.

Edit 3: All known issues should be fixed now. I still plan to make changes to this code, but it should work fine now.

Re: Fonts - Yet again
posted by JTalton

Are the changes committed to the SVN repository? I grabbed the latest code and didn't see much difference with the MeasureString.

Re: Fonts - Yet again
posted by the Fiddler

MeasureString should be deprecated in the latest version (or did I forget to commit that part? :) ). Try using MeasureText instead. Besides accuracy, this function can also return individual character positions, so you can place a cursor for example.

I'm cleaning up font API for 0.9.2, so expect some minor changes before the release.

Re: Fonts - Yet again
posted by JTalton

Attached a new screen shot of my font test using the new MeasureText. The measurements are closer but still are a little off.
Also I was wondering about the space at the beginning of the text? Where does that come from?

Note: When I draw the rectangle to indicate the measurements I do round up the floats to the closest whole number.

I'm in no rush for a fix, so take your time and work on what you need to. Thanks!

Re: Fonts - Yet again
posted by the Fiddler

Ok, I've just updated SVN to use StringFormat.GenericDefault instead of GenericTypographic, which deals with the measurement issue.

I'm still looking where the extra space to the left comes from. I suspect it could be a caused by rounding during glyph rasterization (since it's always 1 extra pixel).

Re: Fonts - Yet again
posted by JTalton

I believe the GdiPlus.MeasureCharacterRanges call on the first character in the text is coming back with an offset and that is being used for the drawing thus causing the string to be moved to the right and down.

Since the rectangle returned from MeasureText has this offset set as it's location, if I adjust the position of the text before I draw it, it shows up in the right place.

Re: Fonts - Yet again
posted by the Fiddler

Ok, I fixed this issue at last. You were right about the offset - the problem was caused by a mismatch between glyph rasterization and measurement. The first used a GenericDefault StringFormat, while the latter used a GenericTypographic.

Both measurement and rasterization now explicitly defines GenericTypographic, which completely resolves the measurement issues. My tests now show a tight fit for the bounding box, without cut or missing overhangs for letters. As long as the font is correctly designed, it will hopefully show up correctly.

Can you please confirm that the issue is resolved?

Re: Fonts - Yet again
posted by JTalton

First font loads fine.
Second font gives a "Divide by zero exception" in LoadGlyph on the 4th character.
Out of time at the moment, I'll debug it tonight.

Re: Fonts - Yet again
posted by the Fiddler

Ok, this is starting to get weird. Here is what I get after the latest updates (just checked and these *are* in SVN):

Edit: I think I'm replying to an earlier version of your post. :-) Divide by zero in LoadGlyph? The only division is by the texture size (width and height of the glyph cache), which shouldn't ever be zero - unless there's no OpenGL context (which shouldn't be the case occur, since the first font loads fine).

Any ideas how to reproduce this?

Re: Fonts - Yet again
posted by JTalton

Name = "Microsoft Sans Serif" Size=7.0
The size of the size character (32 ' ') is 0 width 0 height from MeasureText() and thus pack.Add().
The divide by zero exception comes from
GL.TexSubImage2D(TextureTarget.Texture2D, 0, rect.Left, rect.Top, rect.Width, rect.Height, OpenTK.Graphics.PixelFormat.Rgba, PixelType.UnsignedByte, (IntPtr)data_ptr);
since rect.Width and Rect.Height are 0.

Attaching my test program to the original post.

Edit - BTW Font size 10 works for me. Output and measurements look good!

Re: Fonts - Yet again
posted by JTalton

The fonts are hanging down below my rectangle in the test. This is because the returned rectangle from MeasureText has a Y location of 1.998 and I am ignoring the location when drawing the gray rectangle, but the text drawing function uses this location offset.

It may be that this is the desired functionality since the 1.99 = ~2 which is how much the font in question hangs down on letters that hang (i.e. 'g' 'j'). So it is drawing the font at the location I specified but that is the baseline and not the bottom of what is drawn. If that makes sense. In a way that is a nice feature, and I can work around it in my code since I want the actual bottom of rendering by using the location offset before rendering the text.

System.DivideByZeroException
posted by JTalton

Compiled on second machine and I am still getting this exception:

System.DivideByZeroException

StackTrace:
 GL.Delegates.TexSubImage2D.Invoke(target, level,xoffset, yoffset, width, height, format, type, pixels)
 GL.TexSubImage2D(target=Texture2D, level=0, xoffset=8, yoffset=18, width=0, height=0, format=Rgba, type=UnsignedByte, pixels=)
 TextureFont.LoadGlyph(c=' ', rectangle={X=0 Y=0 Width=0 Height=0})
 TextureFont.LoadGlyphs(glyphs="Main Window")
 TextPrinter.PerformLayout(text="Main Window", font={TextureFont}, width=0, wordWarp=false, alignment=Near, rightToLeft=false, vertices={}, indices={}, num_indices=66)
 TextPrinter.Prepare(text="Main Window", font={TextureFont}, handle=null, width=0, wordWarp=false, alignment=Near, rightToLeft=false)
 TextPrinter.Prepare(text="Main Window", font={TextureFont}, handle=null)

You can see by the stack trace that the space character is trying to be loaded with a width and height of zero which causes TexSubImage2D() to throw the exception.

Re: Fonts - Yet again
posted by the Fiddler

[Hanging glyphs]
The fonts are hanging down below my rectangle in the test
I think this depends on the font, actually. Different fonts use different coordinate systems (e.g. (0,0) could be the left-most pixel of the baseline or the glyph "center") - you can verify this is the case by loading a few different fonts (e.g. Verdana and Times New Roman at 7.0em - the latter has parts of the font hanging outside the rectangle while the first doesn't).

A strange thing I just happened to observe, is that the bounding box returned by TextureFont.MeasureText correctly surrounds all overhangs - only the individual rectangles show this effect. I'm not really sure *why* this happens, as the rectangle is calculated simply from the first and last glyph sizes.

Ah, I'll have to research this a little more, I guess.

[DivideByZero exception]
I must be getting crazy, because I can't reproduce that. I've tested 10 different fonts (including MS Sans Serif) at sizes 5-10em with the "Main Window" string, and the ' ' is correctly measured.

I'm now grapsing at straws: which version of .Net are you using / building OpenTK against? If it is 2.0, do you have SP1 installed?

Re: Fonts - Yet again
posted by JTalton

Windows XP - SP3
I believe .NET 2.0

GdiPlus.GetRegionBounds(regions[j], new HandleRef(gfx, GdiPlus.GetNativeGraphics(gfx)), ref rect);
The rect after this call for the space character has a width and height of zero.

I have reproduced this on two separate machines using the latest SVN code and different test code in each case.

Re: Fonts - Yet again
posted by the Fiddler

Interesting. I'm still not able to reproduce the issue (Vista x64 SP1 / .Net 2.0 SP1, Windows XP SP3 / .Net 2.0 SP1, Windows 2000 / .Net 2.0). I haven't tested on Linux yet, but this problem looks rather curious. Can you test what the following returns on your machine?

Just before the failing call, place the following lines. Then put a breakpoint on the MeasureText call, and examine the output of "ranges". Does the space character still exhibit a 0 width/height?

// Somewhere before the code which crashes, place the following line:
StringFormat format = StringFormat.GenericTypographic;
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
List<RectangleF> ranges = new List<RectangleF>();

textureFont.MeasureText(str, SizeF.Empty, format, ranges);

If yes, can you test against the sample default .Net MeasureCharacterRanges implementation? Create a new WinForms project and replace class Form1 with the following:

   public partial class Form1 : Form
    {
        static readonly string text = "Main Window";
        static readonly Font font = new Font(new FontFamily("Microsoft Sans Serif"), 7.0f);

        public Form1()
        {
            InitializeComponent();
        }

        protected override void  OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;

            StringFormat format = StringFormat.GenericDefault;

            CharacterRange[] ranges = new CharacterRange[text.Length];
            for (int i = 0; i < ranges.Length; i++)
                ranges[i] = new CharacterRange(i, 1);

            format.SetMeasurableCharacterRanges(ranges);

            Region[] regions = e.Graphics.MeasureCharacterRanges(text, font, new RectangleF(0, 0, 1000.0f, 1000.0f), format);

            Rectangle measure_string_size = new Rectangle(
                new Point((int)regions[0].GetBounds(e.Graphics).Left,
                          (int)regions[0].GetBounds(e.Graphics).Top),
                new Size((int)Math.Ceiling(regions[regions.Length - 1].GetBounds(e.Graphics).Right),
                         (int)Math.Ceiling(regions[regions.Length - 1].GetBounds(e.Graphics).Bottom)));
           
            e.Graphics.FillRectangle(Brushes.White, measure_string_size);

            for (int i = 0; i < regions.Length; i++)
            {
                Region region = regions[i];
                e.Graphics.DrawString(text[i].ToString(), font, Brushes.Black,
                    new Point((int)region.GetBounds(e.Graphics).Left,
                              (int)region.GetBounds(e.Graphics).Top));
                region.Dispose();
            }
           
            //SizeF temp_size = e.Graphics.MeasureString(text, font);
            //Size measure_string_size = new Size((int)Math.Ceiling(temp_size.Width), (int)Math.Ceiling(temp_size.Height));

            //e.Graphics.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0), measure_string_size));
            //e.Graphics.DrawString(text, font, Brushes.Black, new PointF(0, 0));
        }
    }

Does this code correctly display a space character on your test machine?

I'm trying to find out what's going wrong here.

On the upside, I've found a problem with italics getting cut off, which I'm now investigating. I'm also working on a new glyph packing method that will allow multiple texture sheets with glyph eviction (LRU scheme with configurable memory consumption).

Re: Fonts - Yet again
posted by JTalton

Tried the same tests I have been doing on a 3rd XP SP3 machine. Works flawlessly. So something about the first two machines is off. I'll run the tests you outlined when I get a chance.

Re: Fonts - Yet again
posted by BlueMonkMN

Oops, I missed out on this whole discussion. But it looks like you're stuck the same place I was: MeasureString returns a true bounding box for the text and all it's overhanging bits while MeasureCharacterRanges seems more intended to return boundaries for the main parts of the characters (for the purpose of highlighting text or something), ignoring some outlying bits, especially in the case of large italic lowercase "f" characters in Times New Roman. That's what happened when I tried to convert everything to use MeasureCharacterRanges too. But maybe you improved it somewhat by taking into account the offset of the glyph into the cell where it's drawn? I never got that far. I'll try getting and testing the new code soon and see if my results are better than before.

Re: Fonts - Yet again
posted by objarni

JTalton: sorry for off topic question -- but the images on the buttons in Golem are very nice indeed. What software/tools did you use to draw them?

Re: Fonts - Yet again
posted by JTalton

Inkscape Open source Linux/MacOS/Windows vector graphics program.
I highly recommend it. Exports the SVG vector icons as PNG with full transparency support.
I also try to somewhat follow the Tango icon design guidelines.
The 4 icons on the left of the bar are using the new guidelines. I still need to work the others up.