diff --git a/Source/Examples/OpenGL/JuliaSetFractal.cs b/Source/Examples/OpenGL/JuliaSetFractal.cs index 85951792..2ea7add4 100644 --- a/Source/Examples/OpenGL/JuliaSetFractal.cs +++ b/Source/Examples/OpenGL/JuliaSetFractal.cs @@ -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 diff --git a/Source/Examples/Tests/MathSpeed.cs b/Source/Examples/Tests/MathSpeed.cs index d4017abc..14efa5c6 100644 --- a/Source/Examples/Tests/MathSpeed.cs +++ b/Source/Examples/Tests/MathSpeed.cs @@ -16,7 +16,7 @@ namespace Examples.Tests public class MathSpeed { public static void Main() - { + { /* Stopwatch watch = new Stopwatch(); diff --git a/Source/Examples/Tutorial/Text.cs b/Source/Examples/Tutorial/Text.cs index cabc7cf2..266779f5 100644 --- a/Source/Examples/Tutorial/Text.cs +++ b/Source/Examples/Tutorial/Text.cs @@ -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(); diff --git a/Source/Utilities/Fonts/Glyph.cs b/Source/Utilities/Fonts/Glyph.cs index 25470c65..c64acfe5 100644 --- a/Source/Utilities/Fonts/Glyph.cs +++ b/Source/Utilities/Fonts/Glyph.cs @@ -111,12 +111,29 @@ namespace OpenTK.Graphics /// A System.Int32 containing a hashcode that uniquely identifies this Glyph. 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 + + /// + /// Gets the size of this Glyph. + /// + public SizeF Size { get { return size; } } + + #endregion + + #region public RectangleF Rectangle + + /// + /// Gets the bounding box of this Glyph. + /// + public RectangleF Rectangle { get { return new RectangleF(PointF.Empty, Size); } } + + #endregion + #endregion #region --- IPackable Members --- @@ -154,7 +171,7 @@ namespace OpenTK.Graphics /// True if both Glyphs represent the same character of the same Font, false otherwise. public bool Equals(Glyph other) { - return Character == other.Character && Font == other.Font; + return Character == other.Character && Font == other.Font && Size == other.Size; } #endregion diff --git a/Source/Utilities/Fonts/TextPrinter.cs b/Source/Utilities/Fonts/TextPrinter.cs index b59a5b0b..54613f94 100644 --- a/Source/Utilities/Fonts/TextPrinter.cs +++ b/Source/Utilities/Fonts/TextPrinter.cs @@ -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 /// /// Provides methods to perform layout and print hardware accelerated text. /// - 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 --- - /// - /// Constructs a new TextPrinter object. - /// - 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_cache = new Dictionary(); + + GlyphCache glyph_cache; + IGlyphRasterizer glyph_rasterizer; + ITextOutputProvider text_output; + + //TextExtents text_extents = new TextExtents(); + + #endregion + + #region Constructors + + /// + /// Constructs a new TextPrinter object. + /// + 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 vertices = new List(); + //List indices = new List(); + //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 vertices, List 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 } } diff --git a/Source/Utilities/Fonts/TextPrinterOptions.cs b/Source/Utilities/Fonts/TextPrinterOptions.cs new file mode 100644 index 00000000..55fb7dd0 --- /dev/null +++ b/Source/Utilities/Fonts/TextPrinterOptions.cs @@ -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, + } +} diff --git a/Source/Utilities/Graphics/CachedGlyphInfo.cs b/Source/Utilities/Graphics/CachedGlyphInfo.cs new file mode 100644 index 00000000..e5154f18 --- /dev/null +++ b/Source/Utilities/Graphics/CachedGlyphInfo.cs @@ -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); + } + } +} diff --git a/Source/Utilities/Graphics/GraphicsResourceException.cs b/Source/Utilities/Graphics/GraphicsResourceException.cs new file mode 100644 index 00000000..8a7aa297 --- /dev/null +++ b/Source/Utilities/Graphics/GraphicsResourceException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK.Graphics +{ + /// + /// Represents exceptions related to IGraphicsResources. + /// + public class GraphicsResourceException : Exception + { + /// Constructs a new GraphicsResourceException. + public GraphicsResourceException() : base() { } + /// Constructs a new string with the specified error message. + public GraphicsResourceException(string message) : base(message) { } + } +} diff --git a/Source/Utilities/Graphics/IGraphicsResource.cs b/Source/Utilities/Graphics/IGraphicsResource.cs new file mode 100644 index 00000000..7d5d4987 --- /dev/null +++ b/Source/Utilities/Graphics/IGraphicsResource.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK.Graphics +{ + /// + /// Defines a common interface to all OpenGL resources. + /// + interface IGraphicsResource : IDisposable + { + /// + /// Gets the GraphicsContext that owns this resource. + /// + GraphicsContext Context { get; } + + /// + /// Gets the Id of this IGraphicsResource. + /// + int Id { get; } + } +} diff --git a/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs b/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs new file mode 100644 index 00000000..5f00f8ea --- /dev/null +++ b/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs @@ -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 + } +} diff --git a/Source/Utilities/Graphics/Text/Glyph.cs b/Source/Utilities/Graphics/Text/Glyph.cs new file mode 100644 index 00000000..aea2403b --- /dev/null +++ b/Source/Utilities/Graphics/Text/Glyph.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; + +namespace OpenTK.Graphics.Text +{ + struct Glyph : IEquatable + { + char character; + Font font; + + #region Constructors + + /// + /// Constructs a new Glyph that represents the given character and Font. + /// + /// The character to represent. + /// The Font of the character. + 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 + + /// + /// Gets the character represented by this Glyph. + /// + public char Character + { + get { return character; } + private set { character = value; } + } + + #endregion + + #region public Font Font + + /// + /// Gets the Font of this Glyph. + /// + 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) + + /// + /// Checks whether the given object is equal (memberwise) to the current Glyph. + /// + /// The obj to check. + /// True, if the object is identical to the current Glyph. + 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() + + /// + /// Describes this Glyph object. + /// + /// Returns a System.String describing this Glyph. + 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() + + /// + /// Calculates the hashcode for this Glyph. + /// + /// A System.Int32 containing a hashcode that uniquely identifies this Glyph. + public override int GetHashCode() + { + return character.GetHashCode() ^ font.GetHashCode(); + } + + #endregion + + #endregion + + #region IEquatable Members + + public bool Equals(Glyph other) + { + return Character == other.Character && Font == other.Font; + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/Text/GlyphCache.cs b/Source/Utilities/Graphics/Text/GlyphCache.cs new file mode 100644 index 00000000..e862df9e --- /dev/null +++ b/Source/Utilities/Graphics/Text/GlyphCache.cs @@ -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 sheets = new List(); + Bitmap bmp = new Bitmap(256, 256); + + Dictionary cached_glyphs = new Dictionary(); + + #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 + } +} diff --git a/Source/Utilities/Graphics/Text/GlyphPacker.cs b/Source/Utilities/Graphics/Text/GlyphPacker.cs new file mode 100644 index 00000000..1a492aa5 --- /dev/null +++ b/Source/Utilities/Graphics/Text/GlyphPacker.cs @@ -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) + + /// + /// Adds boundingBox to the GlyphPacker. + /// + /// The bounding box of the item to pack. + /// A System.Drawing.Rectangle containing the coordinates of the packed item. + /// Occurs if the item is larger than the available TexturePacker area + /// Occurs if the item already exists in the TexturePacker. + 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) + + /// + /// Rounds boundingBox to the largest integer and adds the resulting Rectangle to the GlyphPacker. + /// + /// The bounding box of the item to pack. + /// A System.Drawing.Rectangle containing the coordinates of the packed item. + /// Occurs if the item is larger than the available TexturePacker area + /// Occurs if the item already exists in the TexturePacker. + 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() + + /// + /// Discards all packed items. + /// + public void Clear() + { + root.Clear(); + } + + #endregion + + #region public void ChangeSize(int new_width, int new_height) + + /// + /// Changes the dimensions of the TexturePacker surface. + /// + /// The new width of the TexturePacker surface. + /// The new height of the TexturePacker surface. + /// Changing the size of the TexturePacker surface will implicitly call TexturePacker.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.") { } + } +} diff --git a/Source/Utilities/Graphics/Text/GlyphSheet.cs b/Source/Utilities/Graphics/Text/GlyphSheet.cs new file mode 100644 index 00000000..1a709eb2 --- /dev/null +++ b/Source/Utilities/Graphics/Text/GlyphSheet.cs @@ -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 + } +} diff --git a/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs b/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs new file mode 100644 index 00000000..f8b2d2e8 --- /dev/null +++ b/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs @@ -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); + } +} diff --git a/Source/Utilities/Graphics/Text/ITextOutputProvider.cs b/Source/Utilities/Graphics/Text/ITextOutputProvider.cs new file mode 100644 index 00000000..4090d0e8 --- /dev/null +++ b/Source/Utilities/Graphics/Text/ITextOutputProvider.cs @@ -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); + } +} diff --git a/Source/Utilities/Graphics/Text/TextBlock.cs b/Source/Utilities/Graphics/Text/TextBlock.cs new file mode 100644 index 00000000..ed235cec --- /dev/null +++ b/Source/Utilities/Graphics/Text/TextBlock.cs @@ -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 + { + #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 Members + + public bool Equals(TextBlock other) + { + return + Text == other.Text && + Font == other.Font && + LayoutRectangle == other.LayoutRectangle && + Options == other.Options; + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/Text/TextBlockComparer.cs b/Source/Utilities/Graphics/Text/TextBlockComparer.cs new file mode 100644 index 00000000..524ba106 --- /dev/null +++ b/Source/Utilities/Graphics/Text/TextBlockComparer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK.Graphics.Text +{ + class TextBlockComparer : IComparer + { + #region Constructors + + public TextBlockComparer() { } + + #endregion + + #region IComparer Members + + public int Compare(TextBlock x, TextBlock y) + { + return x.UsageCount.CompareTo(y.UsageCount); + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/Text/TextExtents.cs b/Source/Utilities/Graphics/Text/TextExtents.cs new file mode 100644 index 00000000..98ae31bc --- /dev/null +++ b/Source/Utilities/Graphics/Text/TextExtents.cs @@ -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 glyph_extents; + + #endregion + + #region Constructors + + public TextExtents(RectangleF bbox) + : this(bbox, null) + { + } + + public TextExtents(RectangleF bbox, IEnumerable 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)[i]; } + internal set { (GlyphExtents as List)[i] = value; } + } + + public IEnumerable GlyphExtents + { + get + { + if (glyph_extents == null) + glyph_extents = new List(); + return (IEnumerable)glyph_extents; + } + } + + public int Count + { + get { return (GlyphExtents as List).Count; } + } + + #endregion + + #region Internal Members + + internal void Add(RectangleF glyphExtent) + { + (GlyphExtents as List).Add(glyphExtent); + } + + internal void AddRange(IEnumerable glyphExtents) + { + (GlyphExtents as List).AddRange(glyphExtents); + } + + internal void Clear() + { + BoundingBox = RectangleF.Empty; + (GlyphExtents as List).Clear(); + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/TextureRegion2D.cs b/Source/Utilities/Graphics/TextureRegion2D.cs new file mode 100644 index 00000000..d44969f4 --- /dev/null +++ b/Source/Utilities/Graphics/TextureRegion2D.cs @@ -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; } } + } + + /// + /// Holds part or the whole of a 2d OpenGL texture. + /// + class TextureRegion2D : 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 + } +} diff --git a/Source/Utilities/TexturePacker.cs b/Source/Utilities/TexturePacker.cs index ec44aa55..acffc12e 100644 --- a/Source/Utilities/TexturePacker.cs +++ b/Source/Utilities/TexturePacker.cs @@ -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.") { } + } }