2007-11-02 00:17:57 +01:00
#region - - - License - - -
/ * Copyright ( c ) 2006 , 2007 Stefanos Apostolopoulos
* See license . txt for license info
* /
#endregion
using System ;
2007-10-20 12:31:59 +02:00
using System.Collections.Generic ;
using System.Text ;
using System.Drawing.Text ;
2008-01-23 13:42:07 +01:00
using System.Drawing ;
using System.Drawing.Imaging ;
using System.Runtime.InteropServices ;
using System.Diagnostics ;
2007-10-20 12:31:59 +02:00
using OpenTK.Math ;
2008-02-02 01:58:26 +01:00
using OpenTK.Graphics.OpenGL ;
using OpenTK.Graphics.OpenGL.Enums ;
2007-10-20 12:31:59 +02:00
using OpenTK.Platform ;
2008-03-08 15:38:10 +01:00
namespace OpenTK.Graphics
2007-10-20 12:31:59 +02:00
{
2008-01-23 13:42:07 +01:00
using Graphics = System . Drawing . Graphics ;
2008-02-02 01:58:26 +01:00
using PixelFormat = OpenTK . Graphics . OpenGL . PixelFormat ;
2008-01-23 13:42:07 +01:00
2007-11-02 00:17:57 +01:00
public class TextureFont : IFont
2007-10-20 12:31:59 +02:00
{
2007-11-06 21:59:15 +01:00
Font font ;
2007-11-06 14:30:46 +01:00
Dictionary < char , Box2 > loaded_glyphs = new Dictionary < char , Box2 > ( 64 ) ;
2007-11-02 00:17:57 +01:00
2007-11-06 21:59:15 +01:00
Bitmap bmp ;
Graphics gfx ;
2007-11-08 16:54:38 +01:00
// TODO: We need to be able to use multiple font sheets.
2007-10-20 12:31:59 +02:00
static int texture ;
static TexturePacker < Glyph > pack ;
static int texture_width , texture_height ;
2007-11-08 16:54:38 +01:00
2008-04-04 21:46:08 +02:00
int [ ] data = new int [ 256 ] ; // Used to upload glyph buffer to the OpenGL texture.
2007-11-08 16:54:38 +01:00
object upload_lock = new object ( ) ;
2007-10-20 12:31:59 +02:00
2007-11-02 00:17:57 +01:00
#region - - - Constructor - - -
2007-10-26 17:55:24 +02:00
/// <summary>
/// Constructs a new TextureFont object, using the specified System.Drawing.Font.
/// </summary>
2007-11-02 00:17:57 +01:00
/// <param name="font">The System.Drawing.Font to use.</param>
2007-11-08 16:54:38 +01:00
public TextureFont ( Font font )
2007-10-20 12:31:59 +02:00
{
if ( font = = null )
throw new ArgumentNullException ( "font" , "Argument to TextureFont constructor cannot be null." ) ;
this . font = font ;
2007-11-06 21:59:15 +01:00
bmp = new Bitmap ( font . Height * 2 , font . Height * 2 ) ;
gfx = Graphics . FromImage ( bmp ) ;
2007-11-08 16:54:38 +01:00
// Adjust font rendering mode. Small sizes look blurry without gridfitting, so turn
// that on. Increasing contrast also seems to help.
if ( font . Size < = 18.0f )
{
gfx . TextRenderingHint = TextRenderingHint . AntiAliasGridFit ;
gfx . TextContrast = 1 ;
}
else
{
gfx . TextRenderingHint = TextRenderingHint . AntiAlias ;
gfx . TextContrast = 0 ;
}
2007-10-20 12:31:59 +02:00
}
2007-11-02 00:17:57 +01:00
#endregion
#region private void PrepareTexturePacker ( )
/// <summary>
/// Calculates the optimal size for the font texture and TexturePacker, and creates both.
/// </summary>
private void PrepareTexturePacker ( )
{
// Calculate the size of the texture packer. We want a power-of-two size
// that is less than 1024 (supported in Geforce256-era cards), but large
// enough to hold at least 256 (16*16) font glyphs.
// TODO: Find the actual card limits, maybe?
int size = ( int ) ( font . Size * 16 ) ;
size = ( int ) System . Math . Pow ( 2.0 , System . Math . Ceiling ( System . Math . Log ( ( double ) size , 2.0 ) ) ) ;
if ( size > 1024 )
size = 1024 ;
texture_width = size ;
texture_height = size ;
pack = new TexturePacker < Glyph > ( texture_width , texture_height ) ;
GL . GenTextures ( 1 , out texture ) ;
2008-01-24 10:16:15 +01:00
GL . BindTexture ( TextureTarget . Texture2D , texture ) ;
GL . TexParameter ( TextureTarget . Texture2D , TextureParameterName . TextureMinFilter , ( int ) All . Linear ) ;
GL . TexParameter ( TextureTarget . Texture2D , TextureParameterName . TextureMagFilter , ( int ) All . Linear ) ;
2007-11-02 00:17:57 +01:00
2008-01-24 10:16:15 +01:00
GL . TexImage2D ( TextureTarget . Texture2D , 0 , PixelInternalFormat . Alpha , texture_width , texture_height , 0 ,
2008-02-02 01:58:26 +01:00
PixelFormat . Rgba , PixelType . UnsignedByte , IntPtr . Zero ) ;
2007-11-02 00:17:57 +01:00
}
#endregion
#region public void LoadGlyphs ( string glyphs )
2007-10-20 12:31:59 +02:00
/// <summary>
2007-10-26 17:55:24 +02:00
/// Prepares the specified glyphs for rendering.
2007-10-20 12:31:59 +02:00
/// </summary>
2007-10-26 17:55:24 +02:00
/// <param name="glyphs">The glyphs to prepare for rendering.</param>
2007-10-20 12:31:59 +02:00
public void LoadGlyphs ( string glyphs )
{
2007-11-06 14:30:46 +01:00
Box2 rect = new Box2 ( ) ;
2007-10-20 12:31:59 +02:00
foreach ( char c in glyphs )
{
if ( ! loaded_glyphs . ContainsKey ( c ) )
2007-11-06 14:30:46 +01:00
LoadGlyph ( c , out rect ) ;
2007-10-20 12:31:59 +02:00
}
}
2007-11-02 00:17:57 +01:00
#endregion
2007-11-06 21:59:15 +01:00
#region public void LoadGlyph ( char glyph )
/// <summary>
/// Prepares the specified glyph for rendering.
/// </summary>
/// <param name="glyphs">The glyph to prepare for rendering.</param>
public void LoadGlyph ( char glyph )
{
Box2 rect = new Box2 ( ) ;
if ( ! loaded_glyphs . ContainsKey ( glyph ) )
LoadGlyph ( glyph , out rect ) ;
}
#endregion
2007-11-06 14:30:46 +01:00
#region private void LoadGlyph ( char c , out Box2 rectangle )
2007-11-02 00:17:57 +01:00
2007-11-06 14:30:46 +01:00
/// <summary>
/// Adds a glyph to the texture packer.
/// </summary>
/// <param name="c">The character of the glyph.</param>
2008-04-04 21:46:08 +02:00
/// <param name="rectangle">An OpenTK.Math.Box2 that will hold the buffer for this glyph.</param>
2007-11-06 14:30:46 +01:00
private void LoadGlyph ( char c , out Box2 rectangle )
2007-10-20 12:31:59 +02:00
{
2007-10-26 17:55:24 +02:00
if ( pack = = null )
PrepareTexturePacker ( ) ;
2007-10-20 12:31:59 +02:00
Glyph g = new Glyph ( c , font ) ;
2007-11-08 16:54:38 +01:00
Rectangle rect = new Rectangle ( ) ;
2007-10-20 12:31:59 +02:00
2007-11-08 16:54:38 +01:00
try
2007-10-20 12:31:59 +02:00
{
2007-11-08 16:54:38 +01:00
pack . Add ( g , out rect ) ;
}
catch ( InvalidOperationException expt )
{
// TODO: The TexturePacker is full, create a new font sheet.
Trace . WriteLine ( expt ) ;
throw ;
}
2007-10-26 17:55:24 +02:00
2008-01-24 10:16:15 +01:00
GL . BindTexture ( TextureTarget . Texture2D , texture ) ;
2007-11-08 16:54:38 +01:00
gfx . Clear ( System . Drawing . Color . Transparent ) ;
gfx . DrawString ( g . Character . ToString ( ) , g . Font , System . Drawing . Brushes . White , 0.0f , 0.0f ) ;
//BitmapData bitmap_data = bitmap.LockBits(new Rectangle(0, 0, rect.Width, rect.Height), ImageLockMode.ReadOnly,
// System.Drawing.Imaging.PixelFormat.Format32bppArgb);
2008-01-24 10:16:15 +01:00
//GL.TexSubImage2D(TextureTarget.Texture2D, 0, rect.Left, rect.Top, rect.Width, rect.Height,
2008-02-02 01:58:26 +01:00
// OpenTK.Graphics.OpenGL.Enums.PixelFormat.Rgba, PixelType.UnsignedByte, bitmap_data.Scan0);
2007-11-08 16:54:38 +01:00
//bitmap.UnlockBits(bitmap_data);
BitmapData bitmap_data = bmp . LockBits ( new Rectangle ( 0 , 0 , bmp . Width , bmp . Height ) , ImageLockMode . ReadOnly ,
System . Drawing . Imaging . PixelFormat . Format32bppArgb ) ;
int needed_size = rect . Width * rect . Height ;
if ( data . Length < needed_size )
Array . Resize < int > ( ref data , needed_size ) ;
Array . Clear ( data , 0 , needed_size ) ;
unsafe
{
int * bitmap_data_ptr = ( int * ) bitmap_data . Scan0 ;
2007-11-06 21:59:15 +01:00
for ( int y = 0 ; y < rect . Height ; y + + )
{
for ( int x = 0 ; x < rect . Width ; x + + )
{
2007-11-08 16:54:38 +01:00
data [ y * rect . Width + x ] = * ( bitmap_data_ptr + y * bmp . Width + x ) ;
2007-11-06 21:59:15 +01:00
}
}
2007-10-20 12:31:59 +02:00
2007-11-08 16:54:38 +01:00
fixed ( int * data_ptr = data )
2008-01-24 10:16:15 +01:00
GL . TexSubImage2D ( TextureTarget . Texture2D , 0 , rect . Left , rect . Top , rect . Width , rect . Height ,
2008-02-02 01:58:26 +01:00
PixelFormat . Rgba , PixelType . UnsignedByte , ( IntPtr ) data_ptr ) ;
2007-10-20 12:31:59 +02:00
}
2007-11-08 16:54:38 +01:00
bmp . UnlockBits ( bitmap_data ) ;
rectangle = new Box2 (
rect . Left / ( float ) texture_width ,
rect . Top / ( float ) texture_height ,
rect . Right / ( float ) texture_width ,
rect . Bottom / ( float ) texture_height ) ;
loaded_glyphs . Add ( g . Character , rectangle ) ;
2007-10-20 12:31:59 +02:00
}
2007-11-02 00:17:57 +01:00
#endregion
2007-11-06 14:30:46 +01:00
#region public bool GlyphData ( char glyph , out float width , out float height , out Box2 textureRectangle , out int texture )
2007-11-02 00:17:57 +01:00
2007-11-06 14:30:46 +01:00
/// <summary>
/// Returns the characteristics of a loaded glyph.
/// </summary>
/// <param name="glyph">The character corresponding to this glyph.</param>
/// <param name="width">The width of this glyph.</param>
/// <param name="height">The height of this glyph (line spacing).</param>
2008-04-04 21:46:08 +02:00
/// <param name="textureRectangle">The bounding box of the texture buffer of this glyph.</param>
2007-11-06 14:30:46 +01:00
/// <param name="texture">The handle to the texture that contains this glyph.</param>
/// <returns>True if the glyph has been loaded, false otherwise.</returns>
/// <seealso cref="LoadGlyphs"/>
public bool GlyphData ( char glyph , out float width , out float height , out Box2 textureRectangle , out int texture )
2007-11-02 00:17:57 +01:00
{
if ( loaded_glyphs . TryGetValue ( glyph , out textureRectangle ) )
{
width = textureRectangle . Width * texture_width ;
height = textureRectangle . Height * texture_height ;
texture = TextureFont . texture ;
return true ;
}
width = height = texture = 0 ;
return false ;
}
#endregion
2007-11-06 14:30:46 +01:00
#region public float Height
2007-11-02 00:17:57 +01:00
2007-10-26 17:55:24 +02:00
/// <summary>
2007-11-06 14:30:46 +01:00
/// Gets a float indicating the default line spacing of this font.
2007-10-26 17:55:24 +02:00
/// </summary>
2007-11-06 14:30:46 +01:00
public float Height
2007-10-26 17:55:24 +02:00
{
2007-11-06 14:30:46 +01:00
get { return font . Height ; }
2007-10-20 12:31:59 +02:00
}
2007-11-02 00:17:57 +01:00
#endregion
2007-11-06 21:59:15 +01:00
#region public float Width
/// <summary>
/// Gets a float indicating the default line spacing of this font.
/// </summary>
public float Width
{
get { return font . SizeInPoints ; }
}
#endregion
2007-11-06 14:30:46 +01:00
#region internal int Texture
2007-10-20 12:31:59 +02:00
2007-10-26 17:55:24 +02:00
/// <summary>
2007-11-06 14:30:46 +01:00
/// Gets the handle to the texture were this font resides.
2007-10-26 17:55:24 +02:00
/// </summary>
2007-11-06 14:30:46 +01:00
internal int Texture
2007-10-26 17:55:24 +02:00
{
2007-11-06 14:30:46 +01:00
get { return TextureFont . texture ; }
2007-10-26 17:55:24 +02:00
}
2007-11-02 00:17:57 +01:00
#endregion
2008-02-02 13:29:21 +01:00
#region public void MeasureString ( string str , out float width , out float height , bool accountForOverhangs )
/// <summary>
/// Measures the width of the specified string.
/// </summary>
/// <param name="str">The string to measure.</param>
/// <param name="width">The measured width.</param>
/// <param name="height">The measured height.</param>
/// <param name="addSpace">If true, adds space to account for glyph overhangs. Set to true if you wish to measure a complete string. Set to false if you wish to perform layout on adjacent strings.</param>
public void MeasureString ( string str , out float width , out float height , bool accountForOverhangs )
{
System . Drawing . StringFormat format = accountForOverhangs ? System . Drawing . StringFormat . GenericDefault : System . Drawing . StringFormat . GenericTypographic ;
format . FormatFlags | = StringFormatFlags . MeasureTrailingSpaces ;
System . Drawing . SizeF size = gfx . MeasureString ( str , font , 16384 , format ) ;
height = size . Height ;
width = size . Width ;
// width = 0;
// height = 0;
// int i = 0;
// foreach (char c in str)
// {
// if (c != '\n' && c != '\r')
// {
// SizeF size = gfx.MeasureString(str.Substring(i, 1), font, 16384, System.Drawing.StringFormat.GenericTypographic);
// width += size.Width == 0 ? font.SizeInPoints * 0.5f : size.Width;
// if (height < size.Height)
// height = size.Height;
// }
// ++i;
// }
}
#endregion
2007-11-06 14:30:46 +01:00
#region public void MeasureString ( string str , out float width , out float height )
2007-11-02 00:17:57 +01:00
2007-10-26 17:55:24 +02:00
/// <summary>
/// Measures the width of the specified string.
/// </summary>
2007-11-06 14:30:46 +01:00
/// <param name="str">The string to measure.</param>
/// <param name="width">The measured width.</param>
/// <param name="height">The measured height.</param>
2008-02-02 13:29:21 +01:00
/// <seealso cref="public void MeasureString(string str, out float width, out float height, bool accountForOverhangs)"/>
2007-11-06 14:30:46 +01:00
public void MeasureString ( string str , out float width , out float height )
2007-10-26 17:55:24 +02:00
{
2008-02-02 13:29:21 +01:00
MeasureString ( str , out width , out height , true ) ;
2007-10-26 17:55:24 +02:00
}
2007-10-20 12:31:59 +02:00
2007-11-02 00:17:57 +01:00
#endregion
#region - - - IDisposable Members - - -
2007-10-20 12:31:59 +02:00
2007-10-26 17:55:24 +02:00
bool disposed ;
2007-10-20 12:31:59 +02:00
2007-10-26 17:55:24 +02:00
/// <summary>
2008-03-08 15:38:10 +01:00
/// Releases all resources used by this OpenTK.Graphics.TextureFont.
2007-10-26 17:55:24 +02:00
/// </summary>
public void Dispose ( )
{
GC . SuppressFinalize ( this ) ;
Dispose ( true ) ;
2007-10-20 12:31:59 +02:00
}
2007-10-26 17:55:24 +02:00
private void Dispose ( bool manual )
2007-10-20 12:31:59 +02:00
{
2007-10-26 17:55:24 +02:00
if ( ! disposed )
{
pack = null ;
if ( manual )
{
GL . DeleteTextures ( 1 , ref texture ) ;
font . Dispose ( ) ;
gfx . Dispose ( ) ;
}
disposed = true ;
}
2007-10-20 12:31:59 +02:00
}
2007-10-26 17:55:24 +02:00
~ TextureFont ( )
2007-10-20 12:31:59 +02:00
{
2007-10-26 17:55:24 +02:00
Dispose ( false ) ;
2007-10-20 12:31:59 +02:00
}
2007-10-26 17:55:24 +02:00
#endregion
2007-10-20 12:31:59 +02:00
}
}