diff --git a/Source/OpenTK/Fonts/DefaultLayoutProvider.cs b/Source/OpenTK/Fonts/DefaultLayoutProvider.cs
index fb24c349..4400e513 100644
--- a/Source/OpenTK/Fonts/DefaultLayoutProvider.cs
+++ b/Source/OpenTK/Fonts/DefaultLayoutProvider.cs
@@ -1,4 +1,10 @@
-using System;
+#region --- License ---
+/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
+ * See license.txt for license info
+ */
+#endregion
+
+using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
@@ -6,69 +12,137 @@ using System.Text.RegularExpressions;
using OpenTK.Math;
using OpenTK.OpenGL;
+using OpenTK.OpenGL.Enums;
+using System.Runtime.InteropServices;
namespace OpenTK.Fonts
{
- public class DefaultLayoutProvider : ILayoutProvider
+ ///
+ /// Provides methods to perform layout and print hardware accelerated text.
+ ///
+ public class TextPrinter : ITextPrinter
{
- static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
- static char[] split_chars = new char[] { ' ', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '=' };
+ //static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ //static char[] split_chars = new char[] { ' ', '\n', '\t', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '=' };
+ static bool use_vbo, use_arb_vbo, use_display_list;
+ static bool functionality_checked = false;
+ static IPrinter printer;
- #region --- ILayoutProvider Members ---
+ #region --- Constructor ---
- public void PerformLayout(string text, IFont font, RectangleF layoutRect, StringAlignment alignment, bool rightToLeft)
+ ///
+ /// Constructs a new DefaultLayoutProvider object.
+ ///
+ public TextPrinter() { }
+
+ #endregion
+
+ ///
+ /// Checks the machine's capabilities and selects the fastest method to print text.
+ ///
+ static void CheckNeededFunctionality()
{
- Vector2[] vertices = new Vector2[2 * text.Length + 4];
- float x_pos, y_pos;
- int i = 0, count, last_break_point = 0;
+ printer =
+ GL.SupportsExtension("VERSION_1_5") ? new VboPrinter() :
+ GL.SupportsExtension("ARB_vertex_buffer_object") ? null :
+ GL.SupportsExtension("VERSION_1_1") ? null : null;
+ if (printer == null)
+ throw new NotSupportedException("DefaultLayoutProvider requires at least OpenGL 1.1 support.");
+
+ functionality_checked = true;
+ }
+
+ #region --- ITextPrinter Members ---
+
+ public void Prepare(string text, TextureFont font, out TextHandle handle)
+ {
+ this.Prepare(text, font, out handle, 0, false, StringAlignment.Near, false);
+ }
+
+ public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp)
+ {
+ this.Prepare(text, font, out handle, width, wordWarp, StringAlignment.Near, false);
+ }
+
+ public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment)
+ {
+ this.Prepare(text, font, out handle, width, wordWarp, alignment, false);
+ }
+
+ public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft)
+ {
+ if (!functionality_checked)
+ CheckNeededFunctionality();
+
+ if (text == null)
+ throw new ArgumentNullException("Parameter cannot be null.", "text");
+
+ if (text.Length > 4096)
+ throw new ArgumentOutOfRangeException("text", text.Length, "Text length must be between 1 and 4096 characters");
+
+ Vector2[] vertices = new Vector2[8 * text.Length]; // Interleaved, vertex, texcoord, vertex, etc...
+ ushort[] indices = new ushort[6 * text.Length];
+ float x_pos = 0, y_pos = 0;
+ ushort i = 0, index_count = 0, vertex_count = 0, last_break_point = 0;
+ Box2 rect = new Box2();
+ float char_width, char_height, measured_width, measured_height;
+ int texture;
+
+ // Every character comprises of 4 vertices, forming two triangles. We generate an index array which
+ // indexes vertices in a triangle-strip fashion. To create a single strip for the whole string, we
+ // need to add a degenerate triangle (0 height) to connect the last vertex of the previous line with
+ // the first vertex of the next line.
+ // This algorithm works for left-to-right scripts.
+
if (alignment == StringAlignment.Near && !rightToLeft || alignment == StringAlignment.Far && rightToLeft)
{
- x_pos = vertices[0].X = layoutRect.Left;
- y_pos = vertices[0].Y = layoutRect.Top;
- vertices[1].X = x_pos;
- vertices[1].Y = y_pos - font.Height;
-
- count = 2;
foreach (char c in text)
{
- // TODO: This code breaks for small widths or for words that do not fit in one width.
if (Char.IsSeparator(c))
- last_break_point = i;
+ last_break_point = index_count;
- x_pos += font.MeasureString(text.Substring(i, 1)).Width;
- if (x_pos > layoutRect.Width)
+ if (c == '\n' || c == '\r')
{
- // Move the last word to the next line. If there is not enough vertical space
- // for a new line, finish layout.
-
+ //x_pos = layoutRect.Left;
+ x_pos = 0;
y_pos += font.Height;
- if (y_pos > layoutRect.Bottom)
- break;
- x_pos = layoutRect.Left;
-
- for (int j = last_break_point; j < i; j++)
- {
- int current_vertex = 2 * (j + 1);
- vertices[current_vertex].X = x_pos;
- vertices[current_vertex].Y += font.Height;
- current_vertex++;
- vertices[current_vertex].X = x_pos;
- vertices[current_vertex].Y += font.Height;
-
- x_pos += font.MeasureString(text.Substring(j, 1)).Width;
- }
-
- x_pos += font.MeasureString(text.Substring(i, 1)).Width;
}
- vertices[count].X = x_pos;
- vertices[count].Y = y_pos;
- ++count;
- vertices[count].X = x_pos;
- vertices[count].Y = y_pos - font.Height;
- ++count;
+ else
+ {
+ font.GlyphData(c, out char_width, out char_height, out rect, out texture);
+ vertices[vertex_count].X = x_pos; // Vertex
+ vertices[vertex_count++].Y = y_pos;
+ vertices[vertex_count].X = rect.Left; // Texcoord
+ vertices[vertex_count++].Y = rect.Top;
+ vertices[vertex_count].X = x_pos; // Vertex
+ vertices[vertex_count++].Y = y_pos + char_height;
+ vertices[vertex_count].X = rect.Left; // Texcoord
+ vertices[vertex_count++].Y = rect.Bottom;
+
+ vertices[vertex_count].X = x_pos + char_width; // Vertex
+ vertices[vertex_count++].Y = y_pos + char_height;
+ vertices[vertex_count].X = rect.Right; // Texcoord
+ vertices[vertex_count++].Y = rect.Bottom;
+ vertices[vertex_count].X = x_pos + char_width; // Vertex
+ vertices[vertex_count++].Y = y_pos;
+ vertices[vertex_count].X = rect.Right; // Texcoord
+ vertices[vertex_count++].Y = rect.Top;
+
+ indices[index_count++] = (ushort)(vertex_count - 8);
+ indices[index_count++] = (ushort)(vertex_count - 6);
+ indices[index_count++] = (ushort)(vertex_count - 4);
+
+ indices[index_count++] = (ushort)(vertex_count - 4);
+ indices[index_count++] = (ushort)(vertex_count - 2);
+ indices[index_count++] = (ushort)(vertex_count - 8);
+
+
+ font.MeasureString(text.Substring(i, 1), out measured_width, out measured_height);
+ x_pos += measured_width;
+ }
++i;
}
}
@@ -81,9 +155,100 @@ namespace OpenTK.Fonts
throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience.");
}
- int buf;
- GL.GenBuffers(1, out buf);
+ handle = printer.Load(vertices, indices);
+ handle.font = font;
+ }
+
+ public void Draw(TextHandle handle)
+ {
+ GL.PushAttrib(AttribMask.TextureBit);
+ GL.PushAttrib(AttribMask.EnableBit);
+
+ GL.Enable(EnableCap.Texture2d);
+ GL.Enable(EnableCap.Blend);
+ GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
+
+ GL.Enable(EnableCap.Blend);
+ GL.BindTexture(TextureTarget.Texture2d, handle.font.Texture);
+ printer.Draw(handle);
+
+ GL.PopAttrib();
+ GL.PopAttrib();
+ }
+
+ #endregion
+ }
+
+ public class TextHandle
+ {
+ internal TextHandle(int handle)
+ {
+ Handle = handle;
+ }
+
+ public readonly int Handle;
+ internal TextureFont font;
+ }
+
+ class VboTextHandle : TextHandle
+ {
+ public VboTextHandle(int handle) : base(handle) { }
+
+ internal int vbo_id; // vertex buffer object id.
+ internal int ebo_id; // index buffer object id.
+ internal int element_count; // Number of elements in the ebo.
+ }
+
+ interface IPrinter
+ {
+ TextHandle Load(Vector2[] vertices, ushort[] indices);
+ void Draw(TextHandle handle);
+ }
+
+ class VboPrinter : IPrinter
+ {
+ static int allocated_handles;
+ static int vector2_size = Marshal.SizeOf(new Vector2());
+
+ #region --- IPrinter Members ---
+
+ public TextHandle Load(Vector2[] vertices, ushort[] indices)
+ {
+ VboTextHandle handle = new VboTextHandle(++allocated_handles);
+ GL.GenBuffers(1, out handle.vbo_id);
+ GL.GenBuffers(1, out handle.ebo_id);
+
+ GL.BindBuffer(Version15.ArrayBuffer, handle.vbo_id);
+ GL.BufferData(Version15.ArrayBuffer, (IntPtr)(vertices.Length * vector2_size), vertices,
+ Version15.StaticDraw);
+
+ GL.BindBuffer(Version15.ElementArrayBuffer, handle.ebo_id);
+ GL.BufferData(Version15.ElementArrayBuffer, (IntPtr)(indices.Length * sizeof(ushort)), indices,
+ Version15.StaticDraw);
+ handle.element_count = indices.Length;
+
+ return handle;
+ }
+
+ public void Draw(TextHandle handle)
+ {
+ VboTextHandle vbo = (VboTextHandle)handle;
+
+ GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
+
+ GL.BindBuffer(Version15.StaticDraw, vbo.vbo_id);
+ GL.BindBuffer(Version15.ElementArrayBuffer, vbo.ebo_id);
+
+ GL.EnableClientState(EnableCap.VertexArray);
+ GL.EnableClientState(EnableCap.TextureCoordArray);
+
+ GL.TexCoordPointer(2, TexCoordPointerType.Float, vector2_size, (IntPtr)vector2_size);
+ GL.VertexPointer(2, VertexPointerType.Float, vector2_size, IntPtr.Zero);
+
+ GL.DrawElements(BeginMode.Triangles, vbo.element_count, All.UnsignedShort, IntPtr.Zero);
+
+ GL.PopClientAttrib();
}
#endregion