#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2008 the Open Toolkit library, except where noted. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #endregion 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 bool TryAdd(Rectangle boundingBox) /// /// Adds boundingBox to the GlyphPacker. /// /// The bounding box of the item to pack. /// The System.Drawing.Rectangle that contains the position of the packed item. /// True, if the item was successfully packed; false if the item is too big for this packer.. /// Occurs if the item is larger than the available TexturePacker area /// Occurs if the item cannot fit in the remaining packer space. public bool TryAdd(Rectangle boundingBox, out Rectangle packedRectangle) { if (!root.Rectangle.Contains(boundingBox)) { packedRectangle = new Rectangle(); return false; } // Increase size so that the glyphs do not touch each other (to avoid rendering artifacts). boundingBox.Width += 2; boundingBox.Height += 2; Node node = root.Insert(boundingBox); // Tree is full and insertion failed: if (node == null) { packedRectangle = new Rectangle(); return false; } packedRectangle = new Rectangle(node.Rectangle.X, node.Rectangle.Y, node.Rectangle.Width - 2, node.Rectangle.Height - 2); return true; } #endregion #region public Rectangle TryAdd(RectangleF boundingBox) /// /// Adds boundingBox to the GlyphPacker. /// /// The bounding box of the item to pack. /// The System.Drawing.RectangleF that contains the position of the packed item. /// True, if the item was successfully packed; false if the item is too big for this packer.. /// Occurs if the item is larger than the available TexturePacker area /// Occurs if the item cannot fit in the remaining packer space. public bool TryAdd(RectangleF boundingBox, out RectangleF packedRectangle) { Rectangle bbox = new Rectangle( (int)boundingBox.X, (int)boundingBox.Y, (int)(boundingBox.Width + 0.5f), (int)(boundingBox.Height + 0.5f)); return TryAdd(bbox, out packedRectangle); } #endregion #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 cannot fit in the remaining packer space. public Rectangle Add(Rectangle boundingBox) { if (!TryAdd(boundingBox, out boundingBox)) throw new TexturePackerFullException(); return boundingBox; } #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 #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.") { } } }