First public commit of the new text renderer (WIP implementation).
This commit is contained in:
parent
f853a7b021
commit
a57eb8f647
21 changed files with 1103 additions and 33 deletions
|
@ -164,30 +164,30 @@ namespace Examples.Tutorial
|
|||
PixelType.UnsignedByte, data.Scan0);
|
||||
bitmap.UnlockBits(data);
|
||||
}
|
||||
#endregion Textures
|
||||
|
||||
#endregion Textures
|
||||
|
||||
Keyboard.KeyUp += KeyUp;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
void KeyUp(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key e)
|
||||
{
|
||||
if (e == OpenTK.Input.Key.F12)
|
||||
{
|
||||
|
||||
int i = 0;
|
||||
void KeyUp(OpenTK.Input.KeyboardDevice sender, OpenTK.Input.Key e)
|
||||
{
|
||||
if (e == OpenTK.Input.Key.F12)
|
||||
{
|
||||
Bitmap bmp = new Bitmap(this.Width, this.Height);
|
||||
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.PixelFormat.Format24bppRgb);
|
||||
GL.ReadPixels(0, 0, this.Width, this.Height,
|
||||
OpenTK.Graphics.PixelFormat.Bgr,
|
||||
GL.ReadPixels(0, 0, this.Width, this.Height,
|
||||
OpenTK.Graphics.PixelFormat.Bgr,
|
||||
OpenTK.Graphics.PixelType.UnsignedByte,
|
||||
data.Scan0);
|
||||
bmp.UnlockBits(data);
|
||||
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
bmp.Save("Screenshot" + (i++).ToString() + ".png", ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
bmp.Save("Screenshot" + (i++).ToString() + ".png", ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -241,7 +241,7 @@ namespace Examples.Tutorial
|
|||
if (Keyboard[OpenTK.Input.Key.Escape])
|
||||
{
|
||||
this.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Examples.Tests
|
|||
public class MathSpeed
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
{
|
||||
/*
|
||||
Stopwatch watch = new Stopwatch();
|
||||
|
||||
|
|
|
@ -23,10 +23,11 @@ namespace Examples.Tutorial
|
|||
[Example("Text", ExampleCategory.Tutorial, 4)]
|
||||
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));
|
||||
TextHandle poem_handle;
|
||||
ITextPrinter text = new TextPrinter();
|
||||
TextPrinter text = new TextPrinter();
|
||||
|
||||
string poem = new StreamReader("Data/Poem.txt").ReadToEnd();
|
||||
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.
|
||||
// TextPrinter.End() restores your previous projection/modelview matrices.
|
||||
text.Begin();
|
||||
GL.Color3(Color.LightBlue);
|
||||
text.Draw((1.0 / e.Time).ToString("F2"), sans);
|
||||
//GL.Color3(Color.LightBlue);
|
||||
//text.Draw((1.0 / e.Time).ToString("F2"), sans);
|
||||
GL.Translate(0.0f, current_position, 0.0f);
|
||||
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();
|
||||
|
||||
SwapBuffers();
|
||||
|
|
|
@ -111,12 +111,29 @@ namespace OpenTK.Graphics
|
|||
/// <returns>A System.Int32 containing a hashcode that uniquely identifies this Glyph.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
//return character.GetHashCode() ^ font.Style.GetHashCode() ^ font.Size.GetHashCode() ^ font.Unit.GetHashCode();
|
||||
return character.GetHashCode() ^ font.GetHashCode() ^ size.GetHashCode();
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
#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>
|
||||
public bool Equals(Glyph other)
|
||||
{
|
||||
return Character == other.Character && Font == other.Font;
|
||||
return Character == other.Character && Font == other.Font && Size == other.Size;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -12,11 +12,12 @@ using System.Text;
|
|||
using System.Drawing;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
using OpenTK.Math;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Graphics.OpenGL.Enums;
|
||||
using System.Diagnostics;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.Text;
|
||||
using OpenTK.Platform;
|
||||
|
||||
namespace OpenTK.Fonts { }
|
||||
|
||||
|
@ -25,7 +26,7 @@ namespace OpenTK.Graphics
|
|||
/// <summary>
|
||||
/// Provides methods to perform layout and print hardware accelerated text.
|
||||
/// </summary>
|
||||
public class TextPrinter : ITextPrinter
|
||||
public sealed class TextPrinter : ITextPrinter
|
||||
{
|
||||
//static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
//static char[] split_chars = new char[]
|
||||
|
@ -42,11 +43,6 @@ namespace OpenTK.Graphics
|
|||
|
||||
#region --- Constructors ---
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new TextPrinter object.
|
||||
/// </summary>
|
||||
public TextPrinter() { }
|
||||
|
||||
public TextPrinter(ITextPrinterImplementation implementation)
|
||||
{
|
||||
if (implementation == null)
|
||||
|
@ -359,5 +355,154 @@ namespace OpenTK.Graphics
|
|||
#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
|
||||
}
|
||||
}
|
||||
|
|
14
Source/Utilities/Fonts/TextPrinterOptions.cs
Normal file
14
Source/Utilities/Fonts/TextPrinterOptions.cs
Normal 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,
|
||||
}
|
||||
}
|
35
Source/Utilities/Graphics/CachedGlyphInfo.cs
Normal file
35
Source/Utilities/Graphics/CachedGlyphInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
17
Source/Utilities/Graphics/GraphicsResourceException.cs
Normal file
17
Source/Utilities/Graphics/GraphicsResourceException.cs
Normal 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) { }
|
||||
}
|
||||
}
|
22
Source/Utilities/Graphics/IGraphicsResource.cs
Normal file
22
Source/Utilities/Graphics/IGraphicsResource.cs
Normal 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; }
|
||||
}
|
||||
}
|
84
Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs
Normal file
84
Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs
Normal 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
|
||||
}
|
||||
}
|
117
Source/Utilities/Graphics/Text/Glyph.cs
Normal file
117
Source/Utilities/Graphics/Text/Glyph.cs
Normal 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
|
||||
}
|
||||
}
|
100
Source/Utilities/Graphics/Text/GlyphCache.cs
Normal file
100
Source/Utilities/Graphics/Text/GlyphCache.cs
Normal 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
|
||||
}
|
||||
}
|
208
Source/Utilities/Graphics/Text/GlyphPacker.cs
Normal file
208
Source/Utilities/Graphics/Text/GlyphPacker.cs
Normal 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.") { }
|
||||
}
|
||||
}
|
43
Source/Utilities/Graphics/Text/GlyphSheet.cs
Normal file
43
Source/Utilities/Graphics/Text/GlyphSheet.cs
Normal 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
|
||||
}
|
||||
}
|
17
Source/Utilities/Graphics/Text/IGlyphRasterizer.cs
Normal file
17
Source/Utilities/Graphics/Text/IGlyphRasterizer.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Source/Utilities/Graphics/Text/ITextOutputProvider.cs
Normal file
11
Source/Utilities/Graphics/Text/ITextOutputProvider.cs
Normal 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);
|
||||
}
|
||||
}
|
68
Source/Utilities/Graphics/Text/TextBlock.cs
Normal file
68
Source/Utilities/Graphics/Text/TextBlock.cs
Normal 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
|
||||
}
|
||||
}
|
24
Source/Utilities/Graphics/Text/TextBlockComparer.cs
Normal file
24
Source/Utilities/Graphics/Text/TextBlockComparer.cs
Normal 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
|
||||
}
|
||||
}
|
87
Source/Utilities/Graphics/Text/TextExtents.cs
Normal file
87
Source/Utilities/Graphics/Text/TextExtents.cs
Normal 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
|
||||
}
|
||||
}
|
52
Source/Utilities/Graphics/TextureRegion2D.cs
Normal file
52
Source/Utilities/Graphics/TextureRegion2D.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ namespace OpenTK
|
|||
|
||||
// Tree is full and insertion failed:
|
||||
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);
|
||||
rect = node.Rect;
|
||||
|
@ -193,4 +193,9 @@ namespace OpenTK
|
|||
|
||||
#endregion
|
||||
}
|
||||
|
||||
class TexturePackerFullException : Exception
|
||||
{
|
||||
public TexturePackerFullException() : base("There is not enough space to add this item. Consider calling the Clear() method.") { }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue