First public commit of the new text renderer (WIP implementation).

This commit is contained in:
the_fiddler 2008-11-24 16:43:56 +00:00
parent f853a7b021
commit a57eb8f647
21 changed files with 1103 additions and 33 deletions

View file

@ -164,30 +164,30 @@ namespace Examples.Tutorial
PixelType.UnsignedByte, data.Scan0); PixelType.UnsignedByte, data.Scan0);
bitmap.UnlockBits(data); bitmap.UnlockBits(data);
} }
#endregion Textures #endregion Textures
Keyboard.KeyUp += KeyUp; Keyboard.KeyUp += KeyUp;
} }
int i = 0; int i = 0;
void KeyUp(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key e) void KeyUp(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key e)
{ {
if (e == OpenTK.Input.Key.F12) if (e == OpenTK.Input.Key.F12)
{ {
Bitmap bmp = new Bitmap(this.Width, this.Height); Bitmap bmp = new Bitmap(this.Width, this.Height);
System.Drawing.Imaging.BitmapData data = System.Drawing.Imaging.BitmapData data =
bmp.LockBits(new Rectangle(0, 0, this.Width, this.Height), bmp.LockBits(new Rectangle(0, 0, this.Width, this.Height),
System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb); System.Drawing.Imaging.PixelFormat.Format24bppRgb);
GL.ReadPixels(0, 0, this.Width, this.Height, GL.ReadPixels(0, 0, this.Width, this.Height,
OpenTK.Graphics.PixelFormat.Bgr, OpenTK.Graphics.PixelFormat.Bgr,
OpenTK.Graphics.PixelType.UnsignedByte, OpenTK.Graphics.PixelType.UnsignedByte,
data.Scan0); data.Scan0);
bmp.UnlockBits(data); bmp.UnlockBits(data);
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
bmp.Save("Screenshot" + (i++).ToString() + ".png", ImageFormat.Png); bmp.Save("Screenshot" + (i++).ToString() + ".png", ImageFormat.Png);
} }
} }
#endregion #endregion
@ -241,7 +241,7 @@ namespace Examples.Tutorial
if (Keyboard[OpenTK.Input.Key.Escape]) if (Keyboard[OpenTK.Input.Key.Escape])
{ {
this.Exit(); this.Exit();
} }
} }
#endregion #endregion

View file

@ -16,7 +16,7 @@ namespace Examples.Tests
public class MathSpeed public class MathSpeed
{ {
public static void Main() public static void Main()
{ {
/* /*
Stopwatch watch = new Stopwatch(); Stopwatch watch = new Stopwatch();

View file

@ -23,10 +23,11 @@ namespace Examples.Tutorial
[Example("Text", ExampleCategory.Tutorial, 4)] [Example("Text", ExampleCategory.Tutorial, 4)]
public class Text : GameWindow public class Text : GameWindow
{ {
TextureFont serif = new TextureFont(new Font(FontFamily.GenericSerif, 24.0f)); Font serif2 = new Font(FontFamily.GenericSerif, 14.0f);
TextureFont serif = new TextureFont(new Font(FontFamily.GenericSerif, 12.0f));
TextureFont sans = new TextureFont(new Font(FontFamily.GenericSansSerif, 14.0f)); TextureFont sans = new TextureFont(new Font(FontFamily.GenericSansSerif, 14.0f));
TextHandle poem_handle; TextHandle poem_handle;
ITextPrinter text = new TextPrinter(); TextPrinter text = new TextPrinter();
string poem = new StreamReader("Data/Poem.txt").ReadToEnd(); string poem = new StreamReader("Data/Poem.txt").ReadToEnd();
int lines; // How many lines the poem contains. int lines; // How many lines the poem contains.
@ -125,11 +126,14 @@ namespace Examples.Tutorial
// used in 2d graphics, and is necessary for achieving pixel-perfect glyph rendering. // used in 2d graphics, and is necessary for achieving pixel-perfect glyph rendering.
// TextPrinter.End() restores your previous projection/modelview matrices. // TextPrinter.End() restores your previous projection/modelview matrices.
text.Begin(); text.Begin();
GL.Color3(Color.LightBlue); //GL.Color3(Color.LightBlue);
text.Draw((1.0 / e.Time).ToString("F2"), sans); //text.Draw((1.0 / e.Time).ToString("F2"), sans);
GL.Translate(0.0f, current_position, 0.0f); GL.Translate(0.0f, current_position, 0.0f);
GL.Color3(Color.White); GL.Color3(Color.White);
text.Draw(poem_handle); //text.Draw(poem_handle);
//text.Draw(poem, serif);
//GL.BindTexture(TextureTarget.Texture2D, 1);
text.Print(poem, serif2);
text.End(); text.End();
SwapBuffers(); SwapBuffers();

View file

@ -111,12 +111,29 @@ namespace OpenTK.Graphics
/// <returns>A System.Int32 containing a hashcode that uniquely identifies this Glyph.</returns> /// <returns>A System.Int32 containing a hashcode that uniquely identifies this Glyph.</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
//return character.GetHashCode() ^ font.Style.GetHashCode() ^ font.Size.GetHashCode() ^ font.Unit.GetHashCode();
return character.GetHashCode() ^ font.GetHashCode() ^ size.GetHashCode(); return character.GetHashCode() ^ font.GetHashCode() ^ size.GetHashCode();
} }
#endregion #endregion
#region public SizeF Size
/// <summary>
/// Gets the size of this Glyph.
/// </summary>
public SizeF Size { get { return size; } }
#endregion
#region public RectangleF Rectangle
/// <summary>
/// Gets the bounding box of this Glyph.
/// </summary>
public RectangleF Rectangle { get { return new RectangleF(PointF.Empty, Size); } }
#endregion
#endregion #endregion
#region --- IPackable<T> Members --- #region --- IPackable<T> Members ---
@ -154,7 +171,7 @@ namespace OpenTK.Graphics
/// <returns>True if both Glyphs represent the same character of the same Font, false otherwise.</returns> /// <returns>True if both Glyphs represent the same character of the same Font, false otherwise.</returns>
public bool Equals(Glyph other) public bool Equals(Glyph other)
{ {
return Character == other.Character && Font == other.Font; return Character == other.Character && Font == other.Font && Size == other.Size;
} }
#endregion #endregion

View file

@ -12,11 +12,12 @@ using System.Text;
using System.Drawing; using System.Drawing;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Diagnostics;
using OpenTK.Math; using OpenTK.Math;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL.Enums; using OpenTK.Graphics.Text;
using System.Diagnostics; using OpenTK.Platform;
namespace OpenTK.Fonts { } namespace OpenTK.Fonts { }
@ -25,7 +26,7 @@ namespace OpenTK.Graphics
/// <summary> /// <summary>
/// Provides methods to perform layout and print hardware accelerated text. /// Provides methods to perform layout and print hardware accelerated text.
/// </summary> /// </summary>
public class TextPrinter : ITextPrinter public sealed class TextPrinter : ITextPrinter
{ {
//static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase); //static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
//static char[] split_chars = new char[] //static char[] split_chars = new char[]
@ -42,11 +43,6 @@ namespace OpenTK.Graphics
#region --- Constructors --- #region --- Constructors ---
/// <summary>
/// Constructs a new TextPrinter object.
/// </summary>
public TextPrinter() { }
public TextPrinter(ITextPrinterImplementation implementation) public TextPrinter(ITextPrinterImplementation implementation)
{ {
if (implementation == null) if (implementation == null)
@ -359,5 +355,154 @@ namespace OpenTK.Graphics
#endregion #endregion
#endregion #endregion
#region New Implementation
#region Fields
Dictionary<Font, TextureFont> font_cache = new Dictionary<Font, TextureFont>();
GlyphCache glyph_cache;
IGlyphRasterizer glyph_rasterizer;
ITextOutputProvider text_output;
//TextExtents text_extents = new TextExtents();
#endregion
#region Constructors
/// <summary>
/// Constructs a new TextPrinter object.
/// </summary>
public TextPrinter()
: this(null, null)
{
}
TextPrinter(IGlyphRasterizer rasterizer, ITextOutputProvider output/*, IGlyphCacheProvider, ITextOutputProvider */)
{
if (rasterizer == null)
rasterizer = new GdiPlusGlyphRasterizer();
if (output == null)
output = new GL1TextOutputProvider();
glyph_rasterizer = rasterizer;
glyph_cache = new GlyphCache(rasterizer);
text_output = output;
}
#endregion
#region Public Members
#region Print
public void Print(string text, Font font)
{
Print(text, font, 0, RectangleF.Empty);
}
public void Print(string text, Font font, TextPrinterOptions options)
{
Print(text, font, options, RectangleF.Empty);
}
public void Print(string text, Font font, TextPrinterOptions options, RectangleF layoutRectangle)
{
if (String.IsNullOrEmpty(text))
return;
if (font == null)
throw new ArgumentNullException("font");
text_output.Print(new TextBlock(text, font, options, layoutRectangle), glyph_rasterizer, glyph_cache);
//glyph_rasterizer.MeasureText(text, font, options, layoutRectangle, ref text_extents);
//List<Vector2> vertices = new List<Vector2>();
//List<int> indices = new List<int>();
//PerformLayout(new TextBlock(text, font, layoutRectangle, options), text_extents, vertices, indices);
//GL.Begin(BeginMode.Triangles);
//foreach (int i in indices)
//{
// GL.TexCoord2(vertices[i + 1]);
// GL.Vertex2(vertices[i]);
//}
//GL.End();
}
#endregion
#endregion
#region Private Members
#region PerformLayout
void PerformLayout(TextBlock block, TextExtents extents, List<Vector2> vertices, List<int> indices)
{
vertices.Clear();
vertices.Capacity = 4 * block.Text.Length;
indices.Clear();
indices.Capacity = 6 * block.Text.Length;
float x_pos = 0, y_pos = 0;
RectangleF rect = new RectangleF();
float char_width, char_height;
// Every character comprises of 4 vertices, forming two triangles. We generate an index array which
// indexes vertices in a triangle-list fashion.
int current = 0;
foreach (char c in block.Text)
{
if (c == '\n' || c == '\r')
continue;
else if (Char.IsWhiteSpace(c))
{
current++;
continue;
}
else if (!glyph_cache.Contains(c, block.Font))
glyph_cache.Add(c, block.Font);
//font.GlyphData(c, out char_width, out char_height, out rect, out texture);
CachedGlyphInfo cache_info = glyph_cache[c, block.Font];
RectangleF glyph_position = extents[current];
x_pos = glyph_position.X;
y_pos = glyph_position.Y;
char_width = glyph_position.Width;
char_height = glyph_position.Height;
// Interleaved array: Vertex, TexCoord, Vertex, ...
vertices.Add(new Vector2(x_pos, y_pos)); // Vertex
vertices.Add(new Vector2(rect.Left, rect.Top)); // Texcoord
vertices.Add(new Vector2(x_pos, y_pos + char_height));
vertices.Add(new Vector2(rect.Left, rect.Bottom));
vertices.Add(new Vector2(x_pos + char_width, y_pos + char_height));
vertices.Add(new Vector2(rect.Right, rect.Bottom));
vertices.Add(new Vector2(x_pos + char_width, y_pos));
vertices.Add(new Vector2(rect.Right, rect.Top));
indices.Add(vertices.Count - 8);
indices.Add(vertices.Count - 6);
indices.Add(vertices.Count - 4);
indices.Add(vertices.Count - 4);
indices.Add(vertices.Count - 2);
indices.Add(vertices.Count - 8);
}
}
#endregion
#endregion
#endregion
} }
} }

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics
{
[Flags]
public enum TextPrinterOptions
{
NoCache = 1,
RightToLeft = 2,
Vertical = 4,
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics
{
struct CachedGlyphInfo
{
public readonly AlphaTexture2D Texture;
public readonly RectangleF RectangleNormalized;
public Rectangle Rectangle
{
get
{
return new Rectangle(
(int)(RectangleNormalized.X * Texture.Width),
(int)(RectangleNormalized.Y * Texture.Height),
(int)(RectangleNormalized.Width * Texture.Width),
(int)(RectangleNormalized.Height * Texture.Height));
}
}
// Rect denotes the absolute position of the glyph in the texture [0, Texture.Width], [0, Texture.Height].
public CachedGlyphInfo(AlphaTexture2D texture, Rectangle rect)
{
Texture = texture;
RectangleNormalized = new RectangleF(
rect.X / (float)texture.Width,
rect.Y / (float)texture.Height,
rect.Width / (float)texture.Width,
rect.Height / (float)texture.Height);
}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics
{
/// <summary>
/// Represents exceptions related to IGraphicsResources.
/// </summary>
public class GraphicsResourceException : Exception
{
/// <summary>Constructs a new GraphicsResourceException.</summary>
public GraphicsResourceException() : base() { }
/// <summary>Constructs a new string with the specified error message.</summary>
public GraphicsResourceException(string message) : base(message) { }
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics
{
/// <summary>
/// Defines a common interface to all OpenGL resources.
/// </summary>
interface IGraphicsResource : IDisposable
{
/// <summary>
/// Gets the GraphicsContext that owns this resource.
/// </summary>
GraphicsContext Context { get; }
/// <summary>
/// Gets the Id of this IGraphicsResource.
/// </summary>
int Id { get; }
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
class GL1TextOutputProvider : ITextOutputProvider
{
#region Constructors
public GL1TextOutputProvider() { }
#endregion
#region ITextOutputProvider Members
public void Print(TextBlock block, IGlyphRasterizer rasterizer, GlyphCache cache)
{
TextExtents extents = rasterizer.MeasureText(block);
foreach (char c in block.Text)
if (c != '\n' && c != '\r' && !Char.IsWhiteSpace(c) && !cache.Contains(c, block.Font))
cache.Add(c, block.Font);
GL.BindTexture(TextureTarget.Texture2D, 1);
//GL.Begin(BeginMode.Quads);
//GL.TexCoord2(0, 0);
//GL.Vertex2(0, 0);
//GL.TexCoord2(1, 0);
//GL.Vertex2(256, 0);
//GL.TexCoord2(1, 1);
//GL.Vertex2(256, 256);
//GL.TexCoord2(0, 1);
//GL.Vertex2(0, 256);
//GL.End();
//GL.Translate(0, 256, 0);
//GL.Disable(EnableCap.Texture2D);
//GL.BindTexture(TextureTarget.Texture2D, 1);
GL.Begin(BeginMode.Triangles);
int current = 0;
foreach (char c in block.Text)
{
if (c == '\n' || c == '\r')
continue;
else if (Char.IsWhiteSpace(c))
{
current++;
continue;
}
CachedGlyphInfo info = cache[c, block.Font];
RectangleF position = extents[current++];
position.Size = info.Rectangle.Size;
// Interleaved array: Vertex, TexCoord, Vertex, ...
GL.TexCoord2(info.RectangleNormalized.Left, info.RectangleNormalized.Top);
GL.Vertex2(position.Left, position.Top);
GL.TexCoord2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom);
GL.Vertex2(position.Left, position.Bottom);
GL.TexCoord2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom);
GL.Vertex2(position.Right, position.Bottom);
GL.TexCoord2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom);
GL.Vertex2(position.Right, position.Bottom);
GL.TexCoord2(info.RectangleNormalized.Right, info.RectangleNormalized.Top);
GL.Vertex2(position.Right, position.Top);
GL.TexCoord2(info.RectangleNormalized.Left, info.RectangleNormalized.Top);
GL.Vertex2(position.Left, position.Top);
}
GL.End();
}
#endregion
}
}

View file

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
struct Glyph : IEquatable<Glyph>
{
char character;
Font font;
#region Constructors
/// <summary>
/// Constructs a new Glyph that represents the given character and Font.
/// </summary>
/// <param name="c">The character to represent.</param>
/// <param name="f">The Font of the character.</param>
public Glyph(char c, Font font)
{
if (font == null)
throw new ArgumentNullException("font");
character = c;
this.font = font;
}
#endregion
#region --- Public Methods ---
#region public char Character
/// <summary>
/// Gets the character represented by this Glyph.
/// </summary>
public char Character
{
get { return character; }
private set { character = value; }
}
#endregion
#region public Font Font
/// <summary>
/// Gets the Font of this Glyph.
/// </summary>
public Font Font
{
get { return font; }
private set
{
if (value == null)
throw new ArgumentNullException("Font", "Glyph font cannot be null");
font = value;
}
}
#endregion
#region public override bool Equals(object obj)
/// <summary>
/// Checks whether the given object is equal (memberwise) to the current Glyph.
/// </summary>
/// <param name="obj">The obj to check.</param>
/// <returns>True, if the object is identical to the current Glyph.</returns>
public override bool Equals(object obj)
{
if (obj is Glyph)
return this.Equals((Glyph)obj);
return base.Equals(obj);
}
#endregion
#region public override string ToString()
/// <summary>
/// Describes this Glyph object.
/// </summary>
/// <returns>Returns a System.String describing this Glyph.</returns>
public override string ToString()
{
return String.Format("'{0}', {1} {2}, {3} {4}", Character, Font.Name, font.Style, font.Size, font.Unit);
}
#endregion
#region public override int GetHashCode()
/// <summary>
/// Calculates the hashcode for this Glyph.
/// </summary>
/// <returns>A System.Int32 containing a hashcode that uniquely identifies this Glyph.</returns>
public override int GetHashCode()
{
return character.GetHashCode() ^ font.GetHashCode();
}
#endregion
#endregion
#region IEquatable<Glyph> Members
public bool Equals(Glyph other)
{
return Character == other.Character && Font == other.Font;
}
#endregion
}
}

View file

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using OpenTK.Graphics;
using System.Diagnostics;
namespace OpenTK.Graphics.Text
{
class GlyphCache
{
#region Fields
IGlyphRasterizer rasterizer;
List<GlyphSheet> sheets = new List<GlyphSheet>();
Bitmap bmp = new Bitmap(256, 256);
Dictionary<Glyph, CachedGlyphInfo> cached_glyphs = new Dictionary<Glyph, CachedGlyphInfo>();
#endregion
#region Constructors
public GlyphCache(IGlyphRasterizer rasterizer)
{
if (rasterizer == null)
throw new ArgumentNullException("rasterizer");
this.rasterizer = rasterizer;
sheets.Add(new GlyphSheet());
}
#endregion
#region Public Members
public void Add(char c, Font font)
{
Glyph glyph = new Glyph(c, font);
bool inserted = false;
using (Bitmap bmp = rasterizer.Rasterize(glyph))
{
foreach (GlyphSheet sheet in sheets)
{
try
{
InsertGlyph(glyph, bmp, sheet);
inserted = true;
break;
}
catch (TexturePackerFullException)
{
}
}
if (!inserted)
{
GlyphSheet sheet = new GlyphSheet();
sheets.Add(sheet);
InsertGlyph(glyph, bmp, sheet);
}
}
}
void InsertGlyph(Glyph glyph, Bitmap bmp, GlyphSheet sheet)
{
Rectangle source = new Rectangle(0, 0, bmp.Width, bmp.Height);
Rectangle target = sheet.Packer.Add(source);
sheet.Texture.WriteRegion(source, target, 0, bmp);
cached_glyphs.Add(glyph, new CachedGlyphInfo(sheet.Texture, target));
}
public bool Contains(char c, Font font)
{
return cached_glyphs.ContainsKey(new Glyph(c, font));
}
public CachedGlyphInfo this[char c, Font font]
{
get
{
return cached_glyphs[new Glyph(c, font)];
}
}
public CachedGlyphInfo this[Glyph glyph]
{
get
{
return cached_glyphs[glyph];
}
}
#endregion
}
}

View file

@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
class GlyphPacker
{
Node root;
#region --- Constructors ---
public GlyphPacker(int width, int height)
{
if (width <= 0)
throw new ArgumentOutOfRangeException("width", width, "Must be greater than zero.");
if (height <= 0)
throw new ArgumentOutOfRangeException("height", height, "Must be greater than zero.");
root = new Node();
root.Rectangle = new Rectangle(0, 0, width, width);
}
#endregion
#region --- Public Methods ---
#region public Rectangle Add(Rectangle boundingBox)
/// <summary>
/// Adds boundingBox to the GlyphPacker.
/// </summary>
/// <param name="boundingBox">The bounding box of the item to pack.</param>
/// <returns>A System.Drawing.Rectangle containing the coordinates of the packed item.</returns>
/// <exception cref="InvalidOperationException">Occurs if the item is larger than the available TexturePacker area</exception>
/// <exception cref="ArgumentException">Occurs if the item already exists in the TexturePacker.</exception>
public Rectangle Add(Rectangle boundingBox)
{
if (!root.Rectangle.Contains(boundingBox))
throw new InvalidOperationException("The item is too large for this TexturePacker");
Node node;
node = root.Insert(boundingBox);
// Tree is full and insertion failed:
if (node == null)
throw new TexturePackerFullException();
return node.Rectangle;
}
#endregion
#region public Rectangle Add(RectangleF boundingBox)
/// <summary>
/// Rounds boundingBox to the largest integer and adds the resulting Rectangle to the GlyphPacker.
/// </summary>
/// <param name="boundingBox">The bounding box of the item to pack.</param>
/// <returns>A System.Drawing.Rectangle containing the coordinates of the packed item.</returns>
/// <exception cref="InvalidOperationException">Occurs if the item is larger than the available TexturePacker area</exception>
/// <exception cref="ArgumentException">Occurs if the item already exists in the TexturePacker.</exception>
public Rectangle Add(RectangleF boundingBox)
{
Rectangle bbox = new Rectangle(
(int)boundingBox.X, (int)boundingBox.Y,
(int)(boundingBox.Width + 0.5f), (int)(boundingBox.Height + 0.5f));
return Add(bbox);
}
#endregion
#region public void Clear()
/// <summary>
/// Discards all packed items.
/// </summary>
public void Clear()
{
root.Clear();
}
#endregion
#region public void ChangeSize(int new_width, int new_height)
/// <summary>
/// Changes the dimensions of the TexturePacker surface.
/// </summary>
/// <param name="new_width">The new width of the TexturePacker surface.</param>
/// <param name="new_height">The new height of the TexturePacker surface.</param>
/// <remarks>Changing the size of the TexturePacker surface will implicitly call TexturePacker.Clear().</remarks>
/// <seealso cref="Clear"/>
public void ChangeSize(int new_width, int new_height)
{
throw new NotImplementedException();
}
#endregion
#endregion
#region Node
class Node
{
public Node()
{
}
Node left, right;
Rectangle rect;
bool occupied;
public Rectangle Rectangle { get { return rect; } set { rect = value; } }
public Node Left { get { return left; } set { left = value; } }
public Node Right { get { return right; } set { right = value; } }
#region --- Constructor ---
public bool Leaf
{
get { return left == null && right == null; }
}
#endregion
#region Node Insert(Rectangle bbox)
public Node Insert( Rectangle bbox)
{
if (!this.Leaf)
{
// Recurse towards left child, and if that fails, towards the right.
Node new_node = left.Insert(bbox);
return new_node ?? right.Insert(bbox);
}
else
{
// We have recursed to a leaf.
// If it is not empty go back.
if (occupied)
return null;
// If this leaf is too small go back.
if (rect.Width < bbox.Width || rect.Height < bbox.Height)
return null;
// If this leaf is the right size, insert here.
if (rect.Width == bbox.Width && rect.Height == bbox.Height)
{
occupied = true;
return this;
}
// This leaf is too large, split it up. We'll decide which way to split
// by checking the width and height difference between this rectangle and
// out item's bounding box. If the width difference is larger, we'll split
// horizontaly, else verticaly.
left = new Node();
right = new Node();
int dw = this.rect.Width - bbox.Width + 1;
int dh = this.rect.Height - bbox.Height + 1;
if (dw > dh)
{
left.rect = new Rectangle(rect.Left, rect.Top, bbox.Width, rect.Height);
right.rect = new Rectangle(rect.Left + bbox.Width, rect.Top, rect.Width - bbox.Width, rect.Height);
}
else
{
left.rect = new Rectangle(rect.Left, rect.Top, rect.Width, bbox.Height);
right.rect = new Rectangle(rect.Left, rect.Top + bbox.Height, rect.Width, rect.Height - bbox.Height);
}
return left.Insert(bbox);
}
}
#endregion
#region public void Clear()
public void Clear()
{
if (left != null)
left.Clear();
if (right != null)
right.Clear();
left = right = null;
}
#endregion
}
#endregion
}
class TexturePackerFullException : Exception
{
public TexturePackerFullException() : base("There is not enough space to add this item. Consider calling the Clear() method.") { }
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
class GlyphSheet
{
#region Fields
AlphaTexture2D texture;
GlyphPacker packer;
#endregion
#region Constructors
public GlyphSheet()
{
Texture = new AlphaTexture2D(256, 256);
Packer = new GlyphPacker(256, 256);
}
#endregion
#region Public Members
public AlphaTexture2D Texture
{
get { return texture; }
private set { texture = value; }
}
public GlyphPacker Packer
{
get { return packer; }
private set { packer = value; }
}
#endregion
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using OpenTK.Graphics.Text;
namespace OpenTK.Graphics.Text
{
interface IGlyphRasterizer
{
Bitmap Rasterize(Glyph glyph);
//void Rasterize(Glyph glyph, ref Bitmap bmp);
TextExtents MeasureText(TextBlock block);
RectangleF MeasureGlyph(Glyph glyph);
}
}

View file

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics.Text
{
interface ITextOutputProvider
{
void Print(TextBlock block, IGlyphRasterizer rasterizer, GlyphCache cache);
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
// Uniquely identifies a block of text. This structure can be used to identify text blocks for caching.
struct TextBlock : IEquatable<TextBlock>
{
#region Fields
public readonly string Text;
public readonly Font Font;
public readonly RectangleF LayoutRectangle;
public readonly TextPrinterOptions Options;
public int UsageCount; // Used to identify old and unused blocks of text.
#endregion
#region Constructors
public TextBlock(string text, Font font, TextPrinterOptions options, RectangleF layoutRectangle)
{
Text = text;
Font = font;
LayoutRectangle = layoutRectangle;
Options = options;
UsageCount = 0;
}
#endregion
#region Public Members
public override bool Equals(object obj)
{
if (!(obj is TextBlock))
return false;
return Equals((TextBlock)obj);
}
public override int GetHashCode()
{
return Text.GetHashCode() ^ Font.GetHashCode() ^ LayoutRectangle.GetHashCode() ^ Options.GetHashCode();
}
#endregion
#region IEquatable<TextBlock> Members
public bool Equals(TextBlock other)
{
return
Text == other.Text &&
Font == other.Font &&
LayoutRectangle == other.LayoutRectangle &&
Options == other.Options;
}
#endregion
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics.Text
{
class TextBlockComparer : IComparer<TextBlock>
{
#region Constructors
public TextBlockComparer() { }
#endregion
#region IComparer<TextBlock> Members
public int Compare(TextBlock x, TextBlock y)
{
return x.UsageCount.CompareTo(y.UsageCount);
}
#endregion
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
// Holds layout information about a TextBlock.
public struct TextExtents
{
#region Fields
RectangleF text_extents;
List<RectangleF> glyph_extents;
#endregion
#region Constructors
public TextExtents(RectangleF bbox)
: this(bbox, null)
{
}
public TextExtents(RectangleF bbox, IEnumerable<RectangleF> glyphExtents)
: this()
{
BoundingBox = bbox;
if (glyphExtents != null)
AddRange(glyphExtents);
}
#endregion
#region Public Members
public RectangleF BoundingBox
{
get { return text_extents; }
internal set { text_extents = value; }
}
public RectangleF this[int i]
{
get { return (GlyphExtents as List<RectangleF>)[i]; }
internal set { (GlyphExtents as List<RectangleF>)[i] = value; }
}
public IEnumerable<RectangleF> GlyphExtents
{
get
{
if (glyph_extents == null)
glyph_extents = new List<RectangleF>();
return (IEnumerable<RectangleF>)glyph_extents;
}
}
public int Count
{
get { return (GlyphExtents as List<RectangleF>).Count; }
}
#endregion
#region Internal Members
internal void Add(RectangleF glyphExtent)
{
(GlyphExtents as List<RectangleF>).Add(glyphExtent);
}
internal void AddRange(IEnumerable<RectangleF> glyphExtents)
{
(GlyphExtents as List<RectangleF>).AddRange(glyphExtents);
}
internal void Clear()
{
BoundingBox = RectangleF.Empty;
(GlyphExtents as List<RectangleF>).Clear();
}
#endregion
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics
{
abstract class TextureRegion2D
{
Rectangle rectangle;
public Rectangle Rectangle { get { return rectangle; } protected set { rectangle = value; } }
}
/// <summary>
/// Holds part or the whole of a 2d OpenGL texture.
/// </summary>
class TextureRegion2D<T> : TextureRegion2D where T : struct
{
#region Fields
T[,] data;
#endregion
#region Constructors
internal TextureRegion2D(Rectangle rect)
{
data = new T[rect.Width, rect.Height];
Rectangle = rect;
}
#endregion
#region Public Members
public T this[int x, int y]
{
get { return data[x, y]; }
set { data[x, y] = value; }
}
#endregion
#region Internal Members
internal T[,] Data { get { return data; } }
#endregion
}
}

View file

@ -54,7 +54,7 @@ namespace OpenTK
// Tree is full and insertion failed: // Tree is full and insertion failed:
if (node == null) if (node == null)
throw new InvalidOperationException("There is not enough space to add this item. Consider calling the Clear() method."); throw new TexturePackerFullException();
//items.Add(item, node); //items.Add(item, node);
rect = node.Rect; rect = node.Rect;
@ -193,4 +193,9 @@ namespace OpenTK
#endregion #endregion
} }
class TexturePackerFullException : Exception
{
public TexturePackerFullException() : base("There is not enough space to add this item. Consider calling the Clear() method.") { }
}
} }