#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;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using OpenTK.Math;
using OpenTK.OpenGL;
using OpenTK.OpenGL.Enums;
using System.Diagnostics;
namespace OpenTK.Fonts
{
///
/// 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[] { ' ', '\n', '\t', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '=' };
static bool functionality_checked = false;
static ITextPrinterImplementation printer;
float[] viewport = new float[6];
// 8 chars by default
Vector2[] vertices = new Vector2[8 * 8]; // Interleaved, vertex, texcoord, vertex, etc...
ushort[] indices = new ushort[6 * 8];
#region --- Constructor ---
///
/// Constructs a new DefaultLayoutProvider object.
///
public TextPrinter() { }
#endregion
#region static void CheckNeededFunctionality()
///
/// Checks the machine's capabilities and selects the fastest method to print text.
///
static void CheckNeededFunctionality()
{
printer = (ITextPrinterImplementation)new DisplayListTextPrinter();
/*
GL.SupportsExtension("VERSION_1_5") ?
(ITextPrinterImplementation)new VboTextPrinter() :
GL.SupportsExtension("ARB_vertex_buffer_object") ? null :
GL.SupportsExtension("VERSION_1_1") ? null : null;
*/
if (printer == null)
throw new NotSupportedException("TextPrinter requires at least OpenGL 1.1 support.");
functionality_checked = true;
Debug.Print("Using {0} for font printing.", printer);
}
#endregion
#region --- ITextPrinter Members ---
#region public void Prepare(string text, TextureFont font, out TextHandle handle)
///
/// Prepares text for drawing.
///
/// The string to draw.
/// The font to use for drawing.
/// The handle to the cached text. Use this to draw the text with the Draw() function.
///
public void Prepare(string text, TextureFont font, out TextHandle handle)
{
this.Prepare(text, font, out handle, 0, false, StringAlignment.Near, false);
}
#endregion
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp)
///
/// Prepares text for drawing.
///
/// The string to draw.
/// The font to use for drawing.
/// The handle to the cached text. Use this to draw the text with the Draw() function.
/// Not implemented.
/// Not implemented.
///
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);
}
#endregion
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment)
///
/// Prepares text for drawing.
///
/// The string to draw.
/// The font to use for drawing.
/// The handle to the cached text. Use this to draw the text with the Draw() function.
/// Not implemented.
/// Not implemented.
/// Not implemented.
///
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);
}
#endregion
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft)
///
/// Prepares text for drawing.
///
/// The string to draw.
/// The font to use for drawing.
/// The handle to the cached text. Use this to draw the text with the Draw() function.
/// Not implemented.
/// Not implemented.
/// Not implemented.
/// Not implemented.
///
/// Occurs when OpenGL 1.1 is not supported.
public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft)
{
if (!functionality_checked)
CheckNeededFunctionality();
int num_indices;
PerformLayout(text, font, width, wordWarp, alignment, rightToLeft, ref vertices, ref indices, out num_indices);
handle = printer.Load(vertices, indices, num_indices);
handle.font = font;
}
#endregion
#region void PerformLayout(string text, TextureFont font, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft, ref Vector2[] vertices, ref ushort[] indices, out int num_indices)
// Performs layout on the given string.
void PerformLayout(string text, TextureFont font, float width, bool wordWarp, StringAlignment alignment,
bool rightToLeft, ref Vector2[] vertices, ref ushort[] indices, out int num_indices)
{
if (text == null)
throw new ArgumentNullException("Parameter cannot be null.", "text");
if (text.Length > 8192)
throw new ArgumentOutOfRangeException("text", text.Length, "Text length must be between 1 and 8192 characters");
if (wordWarp || rightToLeft || alignment != StringAlignment.Near)
throw new NotImplementedException();
while (8 * text.Length > vertices.Length)
vertices = new Vector2[vertices.Length << 1];
while (6 * text.Length > indices.Length)
indices = new ushort[indices.Length << 1];
num_indices = 6 * text.Length;
//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;
font.LoadGlyphs(text);
// 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)
{
foreach (char c in text)
{
if (Char.IsSeparator(c))
last_break_point = index_count;
if (c != '\n' && c != '\r')
{
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;
}
else if (c == '\n')
{
//x_pos = layoutRect.Left;
x_pos = 0;
y_pos += font.Height;
}
++i;
}
}
else if (alignment != StringAlignment.Center)
{
throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience.");
}
else
{
throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience.");
}
}
#endregion
#region public void Draw(TextHandle handle)
///
/// Draws the cached text referred to by the TextHandle.
///
/// The TextHandle to the cached text.
public void Draw(TextHandle handle)
{
GL.BindTexture(TextureTarget.Texture2D, handle.font.Texture);
printer.Draw(handle);
}
#endregion
#region public void Draw(string text, TextureFont font)
///
/// Draws dynamic text without caching. Not implemented yet!
///
/// The System.String to draw.
/// The OpenTK.Fonts.TextureFont to draw the text in.
public void Draw(string text, TextureFont font)
{
//printer.Draw(text);
}
#endregion
#region public void Begin()
///
/// Sets up OpenGL state for drawing text.
///
public void Begin()
{
GL.GetFloat(GetPName.Viewport, viewport);
// Prepare to draw text. We want pixel perfect precision, so we setup a 2D mode,
// with size equal to the window (in pixels).
// While we could also render text in 3D mode, it would be very hard to get
// pixel-perfect precision.
GL.MatrixMode(MatrixMode.Projection);
GL.PushMatrix();
GL.LoadIdentity();
GL.Ortho(viewport[0], viewport[2], viewport[3], viewport[1], 0.0, 1.0);
GL.MatrixMode(MatrixMode.Modelview);
GL.PushMatrix();
GL.LoadIdentity();
GL.PushAttrib(AttribMask.TextureBit);
GL.PushAttrib(AttribMask.EnableBit);
GL.Enable(EnableCap.Texture2D);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
GL.Disable(EnableCap.DepthTest);
}
#endregion
#region public void End()
///
/// Restores OpenGL state.
///
public void End()
{
GL.PopAttrib();
GL.PopAttrib();
GL.MatrixMode(MatrixMode.Projection);
GL.PopMatrix();
GL.MatrixMode(MatrixMode.Modelview);
GL.PopMatrix();
}
#endregion
#endregion
}
}