diff --git a/Source/Examples/Tutorial/Fonts.cs b/Source/Examples/Tutorial/Fonts.cs index ce6e63c6..1ad97a9d 100644 --- a/Source/Examples/Tutorial/Fonts.cs +++ b/Source/Examples/Tutorial/Fonts.cs @@ -24,7 +24,7 @@ namespace Examples.Tutorial this.VSync = VSyncMode.On; } - TextureFont serif; + TextureFont serif = new TextureFont(new Font(FontFamily.GenericSerif, 16.0f)); string[] poem = new StreamReader("Data/Poem.txt").ReadToEnd().Replace('\r', ' ').Split('\n'); float scroll_speed; @@ -33,24 +33,56 @@ namespace Examples.Tutorial float warparound_position; float current_position; + int display_list; + public override void OnLoad(EventArgs e) { GL.Enable(GL.Enums.EnableCap.TEXTURE_2D); - serif = new TextureFont(new Font(FontFamily.GenericSerif, 24, FontStyle.Regular, GraphicsUnit.Pixel)); - serif.LoadGlyphs("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz,.!?"); + serif.LoadGlyphs("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz,.!?;()\'- "); - scroll_speed = 4/(float)Height; - initial_position = -1.0f - 64 / (float)Height; // 64 pixels below the bottom of the screen; - warparound_position = 7.0f; current_position = initial_position; + scroll_speed = -1.0f; + + display_list = GL.GenLists(1); + GL.NewList(1, GL.Enums.ListMode.COMPILE); + + GL.PushMatrix(); + + GL.MatrixMode(GL.Enums.MatrixMode.PROJECTION); + GL.LoadIdentity(); + GL.Ortho(0.0, Width, Height, 0.0, 0.0, 1.0); + GL.MatrixMode(GL.Enums.MatrixMode.MODELVIEW); + GL.LoadIdentity(); + + int i = 0, line = 0; + float x_pos, accum_x_pos = 0.0f; + foreach (string str in poem) + { + GL.Translate(0.0f, serif.LineSpacing * line, 0.0f); + foreach (char c in str) + { + serif.PrintFast(c); + x_pos = serif.MeasureWidth(str.Substring(i++, 1)); + accum_x_pos += x_pos; + + GL.Translate((int)(x_pos + 0.5f), 0.0f, 0.0f); + } + GL.LoadIdentity(); + i = 0; + ++line; + } + + GL.PopMatrix(); + + GL.EndList(); } protected override void OnResize(OpenTK.Platform.ResizeEventArgs e) { GL.Viewport(0, 0, Width, Height); - GL.MatrixMode(GL.Enums.MatrixMode.PROJECTION); - GL.LoadIdentity(); - GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 16.0); + + initial_position = Height + serif.LineSpacing; + warparound_position = -(poem.Length + 1) * serif.LineSpacing; } public override void OnUpdateFrame(UpdateFrameEventArgs e) @@ -58,38 +90,29 @@ namespace Examples.Tutorial if (Keyboard[Key.Space]) scroll_speed = 0.0f; if (Keyboard[Key.Down]) - scroll_speed -= 1 / (float)Height; + scroll_speed += 1; if (Keyboard[Key.Up]) - scroll_speed += 1 / (float)Height; + scroll_speed -= 1; if (Keyboard[Key.Escape]) this.Exit(); } public override void OnRenderFrame(RenderFrameEventArgs e) { - GL.MatrixMode(GL.Enums.MatrixMode.MODELVIEW); - GL.LoadIdentity(); - // We'll start printing from the lower left corner of the screen. The text // will slowly move updwards - the user can control the movement speed with // the keyboard arrows and the space bar. current_position += scroll_speed * (float)e.ScaleFactor; - if (current_position > 0.0f && current_position > warparound_position) - current_position = initial_position; - else if (current_position < 0.0f && current_position < initial_position) + if (scroll_speed > 0.0f && current_position > initial_position) current_position = warparound_position; - scroll_position = ((int)(current_position * (float)Height)) / (float)Height; // Round to closest pixel. - - GL.Translate(-1.0f, scroll_position, 0.0f); + else if (scroll_speed < 0.0f && current_position < warparound_position) + current_position = initial_position; + scroll_position = current_position; GL.Clear(GL.Enums.ClearBufferMask.COLOR_BUFFER_BIT); - - float line_spacing = -2.0f * serif.LineSpacing / (float)Height; - foreach (string line in poem) - { - serif.Print(line); - GL.Translate(0.0f, line_spacing, 0.0f); // Move to the next line. - } + + //GL.Translate(0.0f, scroll_position, 0.0f); + GL.CallList(display_list); SwapBuffers(); } @@ -99,7 +122,7 @@ namespace Examples.Tutorial public void Launch() { - Run(30.0, 85.0); + Run(30.0, 0.0); } public static readonly int order = 6; diff --git a/Source/OpenTK/Fonts/TextureFont.cs b/Source/OpenTK/Fonts/TextureFont.cs index c0496a56..29113a05 100644 --- a/Source/OpenTK/Fonts/TextureFont.cs +++ b/Source/OpenTK/Fonts/TextureFont.cs @@ -11,65 +11,33 @@ using OpenTK.Platform; namespace OpenTK.Fonts { - public class TextureFont + public class TextureFont : IDisposable { Font font; - Dictionary loaded_glyphs = new Dictionary(36); - Graphics gfx; + Dictionary loaded_glyphs = new Dictionary(36); + Graphics gfx = Graphics.FromImage(new Bitmap(1, 1)); - struct LoadedGlyph - { - public int List; - public float Width, Height; - } - static int texture; static TexturePacker pack; static int texture_width, texture_height; + float[] viewport = new float[6]; + /// + /// Constructs a new TextureFont object, using the specified System.Drawing.Font. + /// + /// public TextureFont(Font font) { if (font == null) throw new ArgumentNullException("font", "Argument to TextureFont constructor cannot be null."); this.font = font; - - if (pack == null) - { - // Calculate the size of the texture packer. We want a power-of-two size - // that is less than 1024 (supported in Geforce256-era cards), but large - // enough to hold at least 256 (16*16) font glyphs. - int size = (int)(font.Size*16); - size = (int)System.Math.Pow(2.0, System.Math.Ceiling(System.Math.Log((double)size, 2.0))); - if (size > 1024) - size = 1024; - - PrepareTexturePacker(size, size); - gfx = Graphics.FromImage(new Bitmap(1, 1)); - } } /// - /// Not ready yet. + /// Prepares the specified glyphs for rendering. /// - /// - /// - private void PrepareTexturePacker(int width, int height) - { - texture_width = width; - texture_height = height; - pack = new TexturePacker(texture_width, texture_height); - - GL.GenTextures(1, out texture); - GL.BindTexture(GL.Enums.TextureTarget.TEXTURE_2D, texture); - GL.TexParameter(GL.Enums.TextureTarget.TEXTURE_2D, GL.Enums.TextureParameterName.TEXTURE_MIN_FILTER, (int)GL.Enums.All.LINEAR); - GL.TexParameter(GL.Enums.TextureTarget.TEXTURE_2D, GL.Enums.TextureParameterName.TEXTURE_MAG_FILTER, (int)GL.Enums.All.LINEAR); - - byte[] data = new byte[texture_height * texture_width * 4]; - GL.TexImage2D(GL.Enums.TextureTarget.TEXTURE_2D, 0, 4, texture_width, texture_height, 0, - GL.Enums.PixelFormat.RGBA, GL.Enums.PixelType.UNSIGNED_BYTE, data); - } - + /// The glyphs to prepare for rendering. public void LoadGlyphs(string glyphs) { foreach (char c in glyphs) @@ -79,16 +47,22 @@ namespace OpenTK.Fonts } } - private LoadedGlyph LoadGlyph(char c) + private int LoadGlyph(char c) { + if (pack == null) + PrepareTexturePacker(); + Glyph g = new Glyph(c, font); Rectangle rect = pack.Add(g); - using (Bitmap bmp = new Bitmap(g.Width, g.Height)) + using (Bitmap bmp = new Bitmap(g.Width, g.Height, PixelFormat.Format32bppArgb)) using (Graphics gfx = Graphics.FromImage(bmp)) { // Upload texture and create Display List: + GL.BindTexture(GL.Enums.TextureTarget.TEXTURE_2D, texture); + gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + //gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; gfx.Clear(Color.Transparent); gfx.DrawString(g.Character.ToString(), g.Font, Brushes.White, 0.0f, 0.0f); @@ -101,17 +75,11 @@ namespace OpenTK.Fonts float bottom = rect.Bottom / (float)texture_height; float right = rect.Right / (float)texture_width; float top = rect.Top / (float)texture_height; - float width = (rect.Right - rect.Left); // / (float)screen_width; - float height = (rect.Top - rect.Bottom); // / (float)screen_height; + float width = rect.Right - rect.Left; + float height = rect.Bottom - rect.Top; - //width /= 2.0f; - - LoadedGlyph lg = new LoadedGlyph(); - - lg.List = GL.GenLists(1); - GL.NewList(lg.List, GL.Enums.ListMode.COMPILE); - - //GL.PushAttrib(GL.Enums.AttribMask.ALL_ATTRIB_BITS); + int list = GL.GenLists(1); + GL.NewList(list, GL.Enums.ListMode.COMPILE); GL.Enable(GL.Enums.EnableCap.BLEND); GL.BlendFunc(GL.Enums.BlendingFactorSrc.ONE, GL.Enums.BlendingFactorDest.ONE_MINUS_SRC_ALPHA); @@ -119,86 +87,159 @@ namespace OpenTK.Fonts GL.Begin(GL.Enums.BeginMode.QUADS); GL.TexCoord2(left, top); - GL.Vertex2(0.375f, 0.375f); + //GL.Vertex2(0.375f, 0.375f); + GL.Vertex2(0.0f, 0.0f); GL.TexCoord2(right, top); - GL.Vertex2(0.375f + 2 * width, 0.375f); + //GL.Vertex2(0.375f + 2 * width, 0.375f); + GL.Vertex2(width, 0.0f); GL.TexCoord2(right, bottom); - GL.Vertex2(0.375f + 2 * width, 0.375f + 2 * height); + //GL.Vertex2(0.375f + 2 * width, 0.375f + 2 * height); + GL.Vertex2(width, height); GL.TexCoord2(left, bottom); - GL.Vertex2(0.375f, 0.375f + 2 * height); + //GL.Vertex2(0.375f, 0.375f + 2 * height); + GL.Vertex2(0.0f, height); GL.End(); - GL.PopAttrib(); - GL.EndList(); - lg.Width = 2 * width; - lg.Height = 2 * height; + loaded_glyphs.Add(g.Character, list); - loaded_glyphs.Add(g.Character, lg); - - return lg; + return list; } } - float[] viewport = new float[6]; + /// + /// Calculates the optimal size for the font texture and TexturePacker, and creates both. + /// + private void PrepareTexturePacker() + { + // Calculate the size of the texture packer. We want a power-of-two size + // that is less than 1024 (supported in Geforce256-era cards), but large + // enough to hold at least 256 (16*16) font glyphs. + int size = (int)(font.Size * 16); + size = (int)System.Math.Pow(2.0, System.Math.Ceiling(System.Math.Log((double)size, 2.0))); + if (size > 1024) + size = 1024; + + texture_width = size; + texture_height = size; + pack = new TexturePacker(texture_width, texture_height); + + GL.GenTextures(1, out texture); + GL.BindTexture(GL.Enums.TextureTarget.TEXTURE_2D, texture); + GL.TexParameter(GL.Enums.TextureTarget.TEXTURE_2D, GL.Enums.TextureParameterName.TEXTURE_MIN_FILTER, (int)GL.Enums.All.LINEAR); + GL.TexParameter(GL.Enums.TextureTarget.TEXTURE_2D, GL.Enums.TextureParameterName.TEXTURE_MAG_FILTER, (int)GL.Enums.All.NEAREST); + + byte[] data = new byte[texture_height * texture_width * 4]; + GL.TexImage2D(GL.Enums.TextureTarget.TEXTURE_2D, 0, 4, texture_width, texture_height, 0, + GL.Enums.PixelFormat.RGBA, GL.Enums.PixelType.UNSIGNED_BYTE, data); + } + + /// + /// Prints a glyph. + /// + /// The character corresponding to the glyph to print. + /// + /// The print position is specified by the active Modelview matrix. + /// + /// To print pixel perfect fonts, you must setup a Projection matrix that is maps one texel to one pixel. This + /// can be achieved by calling GL.Ortho with width/height set to the actual viewport size, or alternatively, + /// by calling GL.Scale(1.0f/viewport_width, 1.0f/viewport_height, 0.0f). + /// + /// + /// To avoid filtering artifacts, avoid positioning characters on fractional pixels. + /// This is usually achieved by adding 0.5f to the glyph's position and extracting the integer component, + /// i.e. GL.Translate((int)(x_pos + 0.5f), (int)(y_pos + 0.5f), 0.0f); + /// + /// + /// public void Print(char c) { - LoadedGlyph lg; - GL.GetFloat(GL.Enums.GetPName.VIEWPORT, viewport); - - GL.PushMatrix(); - - GL.Scale(1.0f / (viewport[2] - viewport[0]), 1.0f / (viewport[3] - viewport[1]), 1.0f); - if (loaded_glyphs.TryGetValue(c, out lg)) + int list; + if (loaded_glyphs.TryGetValue(c, out list)) { - GL.CallList(lg.List); + GL.CallList(list); } else { - GL.CallList(LoadGlyph(c).List); + GL.CallList(LoadGlyph(c)); } - - GL.PopMatrix(); } - public void Print(string str) + /// + /// Prints a previously loaded glyph. + /// + /// The character corresponding to the glyph to print. + /// + /// You must call the LoadGlyphs function with the corresponding glyph, before using + /// PrintFast. Otherwise, this function works exactly like Print. + /// + /// + /// + public void PrintFast(char c) { - GL.GetFloat(GL.Enums.GetPName.VIEWPORT, viewport); - - GL.PushMatrix(); - - GL.Scale(1.0f / (viewport[2] - viewport[0]), 1.0f / (viewport[3] - viewport[1]), 1.0f); - - LoadGlyphs(str); - int i = 0; - foreach (char c in str) - { - LoadedGlyph lg = loaded_glyphs[c]; - GL.CallList(lg.List); - - //GL.Translate(lg.Width, 0.0f, 0.0f); - float width = gfx.MeasureString(str.Substring(i, 1), font, 256, StringFormat.GenericTypographic).Width; - if (width == 0.0f) width = 8.0f; // Spacebar. - GL.Translate(2 * width, 0.0f, 0.0f); - ++i; - } - - GL.PopMatrix(); - } - - public void Print(string format, params object[] args) - { - Print(String.Format(format, args)); + GL.CallList(loaded_glyphs[c]); } + /// + /// Gets a float indicating the default line spacing of this font. + /// public float LineSpacing { get { return font.Height; } } + + /// + /// Measures the width of the specified string. + /// + /// + /// + public float MeasureWidth(string str) + { + float distance = gfx.MeasureString(str, font, 16384, StringFormat.GenericTypographic).Width; + if (distance == 0) + distance = font.SizeInPoints * 0.5f; + return distance; + } + + #region IDisposable Members + + bool disposed; + + /// + /// Releases all resources used by this OpenTK.Fonts.TextureFont. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + private void Dispose(bool manual) + { + if (!disposed) + { + pack = null; + if (manual) + { + GL.DeleteTextures(1, ref texture); + font.Dispose(); + gfx.Dispose(); + } + + disposed = true; + } + } + + ~TextureFont() + { + Dispose(false); + } + + #endregion } }