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-04-13 18:45:56 +02:00
using OpenTK.Graphics ;
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-04-13 18:45:56 +02:00
using PixelFormat = OpenTK . Graphics . 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 ;
2008-06-22 18:29:13 +02:00
Dictionary < char , RectangleF > loaded_glyphs = new Dictionary < char , RectangleF > ( 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
2008-06-22 01:23:33 +02:00
#region - - - Public Methods - - -
2007-11-02 00:17:57 +01:00
#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 )
{
2008-06-22 18:29:13 +02:00
RectangleF rect = new RectangleF ( ) ;
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 )
{
2008-06-22 18:29:13 +02:00
RectangleF rect = new RectangleF ( ) ;
2007-11-06 21:59:15 +01:00
if ( ! loaded_glyphs . ContainsKey ( glyph ) )
LoadGlyph ( glyph , out rect ) ;
}
#endregion
2008-06-22 18:29:13 +02:00
#region public bool GlyphData ( char glyph , out float width , out float height , out RectangleF 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"/>
2008-06-22 18:29:13 +02:00
public bool GlyphData ( char glyph , out float width , out float height , out RectangleF 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
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>
2008-06-22 18:29:13 +02:00
/// <param name="accountForOverhangs">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>
2008-02-02 13:29:21 +01:00
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 ;
2008-06-22 18:29:13 +02:00
//format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
2008-02-02 13:29:21 +01:00
System . Drawing . SizeF size = gfx . MeasureString ( str , font , 16384 , format ) ;
height = size . Height ;
width = size . Width ;
2008-06-22 18:29:13 +02:00
//width = height = 0;
//RectangleF rect = new RectangleF(0, 0, 0, 0);
//ICollection<RectangleF> ranges = new List<RectangleF>();
//MeasureCharacterRanges(gfx, str, font, ref rect, format, ref ranges);
//foreach (RectangleF range in ranges)
//{
// width += range.Width;
// height = range.Height > height ?range.Height : height;
//}
2008-02-02 13:29:21 +01:00
// 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
2008-06-22 18:29:13 +02:00
#region public void MeasureCharacterRanges ( string text , ref ICollection < RectangleF > ranges )
/// <summary>
/// Measures the individual character positions for the specified string.
/// </summary>
/// <param name="text">The string to measure.</param>
/// <param name="ranges">An ICollection of RectangleF structures, containing the positions of individual characters.</param>
public void MeasureCharacterRanges ( string text , ref ICollection < RectangleF > ranges )
{
MeasureCharacterRanges ( text , false , ref ranges ) ;
}
#endregion
#region public void MeasureCharacterRanges ( string text , bool accountForOverhangs , ref ICollection < RectangleF > ranges )
/// <summary>
/// Measures the individual character positions for the specified string.
/// </summary>
/// <param name="text">The string to measure.</param>
/// <param name="accountForOverhangs">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>
/// <param name="ranges">An ICollection of RectangleF structures, containing the positions of individual characters.</param>
public void MeasureCharacterRanges ( string text , bool accountForOverhangs , ref ICollection < RectangleF > ranges )
{
System . Drawing . StringFormat format = accountForOverhangs ? System . Drawing . StringFormat . GenericDefault : System . Drawing . StringFormat . GenericTypographic ;
RectangleF rect = new RectangleF ( 0 , 0 , gfx . ClipBounds . Width , gfx . ClipBounds . Height ) ;
MeasureCharacterRanges ( text , ref rect , format , ref ranges ) ;
}
#endregion
2008-06-22 01:23:33 +02:00
#endregion
#region - - - Private Methods - - -
2008-06-22 18:29:13 +02:00
#region void MeasureCharacterRanges ( string text , ref RectangleF layoutRect , StringFormat format , ref ICollection < RectangleF > ranges )
void MeasureCharacterRanges ( string text , ref RectangleF layoutRect , StringFormat format , ref ICollection < RectangleF > ranges )
{
ranges . Clear ( ) ; // Hopefully this doesn't trim the collection.
//GPRECTF rect = new GPRECTF(layoutRect);
//rect = new GPRECTF(0, 0, 256, 256);
int status = 0 ;
if ( String . IsNullOrEmpty ( text ) )
return ;
if ( font = = null ) throw new ArgumentNullException ( "font" ) ;
if ( format = = null ) throw new ArgumentNullException ( "format" ) ;
IntPtr [ ] regions = new IntPtr [ GdiPlus . MaxMeasurableCharacterRanges ] ;
CharacterRange [ ] characterRanges = new CharacterRange [ GdiPlus . MaxMeasurableCharacterRanges ] ;
for ( int i = 0 ; i < text . Length ; i + = GdiPlus . MaxMeasurableCharacterRanges )
{
int num_characters = text . Length - i > GdiPlus . MaxMeasurableCharacterRanges ? GdiPlus . MaxMeasurableCharacterRanges : text . Length - i ;
for ( int j = 0 ; j < num_characters ; j + + )
{
characterRanges [ j ] = new CharacterRange ( i + j , 1 ) ;
IntPtr region ;
status = GdiPlus . CreateRegion ( out region ) ;
regions [ j ] = region ;
if ( status ! = 0 )
Debug . Print ( "GDI+ error: {0}" , status ) ;
}
CharacterRange [ ] a = ( CharacterRange [ ] ) characterRanges . Clone ( ) ;
Array . Resize ( ref a , num_characters ) ;
format . SetMeasurableCharacterRanges ( a ) ;
IntPtr native_graphics = GdiPlus . GetNativeGraphics ( gfx ) ;
IntPtr native_font = GdiPlus . GetNativeFont ( font ) ;
IntPtr native_string_format = GdiPlus . GetNativeStringFormat ( format ) ;
status = GdiPlus . MeasureCharacterRanges ( new HandleRef ( gfx , native_graphics ) , text , text . Length ,
new HandleRef ( font , native_font ) , ref layoutRect ,
new HandleRef ( format , ( format = = null ) ? IntPtr . Zero : native_string_format ) ,
num_characters , regions ) ;
for ( int j = 0 ; j < num_characters ; j + + )
{
RectangleF rect = new RectangleF ( ) ;
status = GdiPlus . GetRegionBounds ( regions [ j ] , new HandleRef ( gfx , GdiPlus . GetNativeGraphics ( gfx ) ) , ref rect ) ;
ranges . Add ( rect ) ;
status = GdiPlus . DeleteRegion ( regions [ j ] ) ;
}
}
}
#endregion
2008-06-22 01:23:33 +02:00
#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 ) ;
GL . BindTexture ( TextureTarget . Texture2D , texture ) ;
GL . TexParameter ( TextureTarget . Texture2D , TextureParameterName . TextureMinFilter , ( int ) All . Linear ) ;
GL . TexParameter ( TextureTarget . Texture2D , TextureParameterName . TextureMagFilter , ( int ) All . Linear ) ;
GL . TexImage2D ( TextureTarget . Texture2D , 0 , PixelInternalFormat . Alpha , texture_width , texture_height , 0 ,
OpenTK . Graphics . PixelFormat . Rgba , PixelType . UnsignedByte , IntPtr . Zero ) ;
}
#endregion
#region private void LoadGlyph ( char c , out Box2 rectangle )
/// <summary>
/// Adds a glyph to the texture packer.
/// </summary>
/// <param name="c">The character of the glyph.</param>
/// <param name="rectangle">An OpenTK.Math.Box2 that will hold the buffer for this glyph.</param>
2008-06-22 18:29:13 +02:00
private void LoadGlyph ( char c , out RectangleF rectangle )
2008-06-22 01:23:33 +02:00
{
if ( pack = = null )
PrepareTexturePacker ( ) ;
Glyph g = new Glyph ( c , font ) ;
Rectangle rect = new Rectangle ( ) ;
try
{
pack . Add ( g , out rect ) ;
}
catch ( InvalidOperationException expt )
{
// TODO: The TexturePacker is full, create a new font sheet.
Trace . WriteLine ( expt ) ;
throw ;
}
GL . BindTexture ( TextureTarget . Texture2D , texture ) ;
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);
//GL.TexSubImage2D(TextureTarget.Texture2D, 0, rect.Left, rect.Top, rect.Width, rect.Height,
// OpenTK.Graphics.Enums.PixelFormat.Rgba, PixelType.UnsignedByte, bitmap_data.Scan0);
//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 ;
for ( int y = 0 ; y < rect . Height ; y + + )
for ( int x = 0 ; x < rect . Width ; x + + )
data [ y * rect . Width + x ] = * ( bitmap_data_ptr + y * bmp . Width + x ) ;
fixed ( int * data_ptr = data )
GL . TexSubImage2D ( TextureTarget . Texture2D , 0 , rect . Left , rect . Top , rect . Width , rect . Height ,
OpenTK . Graphics . PixelFormat . Rgba , PixelType . UnsignedByte , ( IntPtr ) data_ptr ) ;
}
bmp . UnlockBits ( bitmap_data ) ;
2008-06-22 18:29:13 +02:00
rectangle = RectangleF . FromLTRB (
2008-06-22 01:23:33 +02:00
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 ) ;
}
#endregion
#endregion
#region - - - Internal Methods - - -
#region internal int Texture
/// <summary>
/// Gets the handle to the texture were this font resides.
/// </summary>
internal int Texture
{
get { return TextureFont . texture ; }
}
#endregion
#endregion
2007-11-02 00:17:57 +01:00
#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
}
}