/*
 * Font.cs - Implementation of the "System.Drawing.Font" class.
 *
 * Copyright (C) 2003  Southern Storm Software, Pty Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

namespace System.Drawing
{

using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Drawing.Toolkit;
using System.ComponentModel;
using System.Drawing.Design;

#if CONFIG_COMPONENT_MODEL_DESIGN
[Editor("System.Drawing.Design.FontEditor, System.Drawing.Design",
		typeof(UITypeEditor))]
[TypeConverter(typeof(FontConverter))]
#endif
#if !ECMA_COMPAT
[Serializable]
[ComVisible(true)]
#endif
public sealed class Font
	: MarshalByRefObject, ICloneable, IDisposable
#if CONFIG_SERIALIZATION
	, ISerializable
#endif
{
	// Internal state.
	private IToolkit toolkit;
	private IToolkitFont toolkitFont;
	private FontFamily fontFamily;
	private FontStyle fontStyle;
	private byte gdiCharSet;
	private bool gdiVerticalFont;
	private float size;
	private GraphicsUnit unit;
	// Cached height of the font in pixels.
	private int pixelHeight;

	// Constructors.
	public Font(Font prototype, FontStyle newStyle)
			: this(prototype.FontFamily, prototype.Size, newStyle,
				   prototype.Unit, prototype.GdiCharSet,
				   prototype.GdiVerticalFont) {}
	public Font(FontFamily family, float emSize)
			: this(family, emSize, FontStyle.Regular,
			       GraphicsUnit.Point, 1, false) {}
	public Font(FontFamily family, float emSize, FontStyle style)
			: this(family, emSize, style, GraphicsUnit.Point, 1, false) {}
	public Font(FontFamily family, float emSize, GraphicsUnit unit)
			: this(family, emSize, FontStyle.Regular, unit, 1, false) {}
	public Font(FontFamily family, float emSize, FontStyle style,
				GraphicsUnit unit)
			: this(family, emSize, style, unit, 1, false) {}
	public Font(FontFamily family, float emSize, FontStyle style,
				GraphicsUnit unit, byte gdiCharSet)
			: this(family, emSize, style, unit, gdiCharSet, false) {}
	public Font(String familyName, float emSize)
			: this(new FontFamily(familyName), emSize,
			       FontStyle.Regular, GraphicsUnit.Point, 1, false) {}
	public Font(String familyName, float emSize, FontStyle style)
			: this(new FontFamily(familyName), emSize,
			       style, GraphicsUnit.Point, 1, false) {}
	public Font(String familyName, float emSize, GraphicsUnit unit)
			: this(new FontFamily(familyName), emSize,
			       FontStyle.Regular, unit, 1, false) {}
	public Font(String familyName, float emSize, FontStyle style,
				GraphicsUnit unit)
			: this(new FontFamily(familyName), emSize,
			       style, unit, 1, false) {}
	public Font(String familyName, float emSize, FontStyle style,
				GraphicsUnit unit, byte gdiCharSet)
			: this(new FontFamily(familyName), emSize,
			       style, unit, gdiCharSet, false) {}
	public Font(String familyName, float emSize, FontStyle style,
				GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
			: this(new FontFamily(familyName), emSize,
			       style, unit, gdiCharSet, gdiVerticalFont) {}
	public Font(FontFamily family, float emSize, FontStyle style,
				GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
			{
				if(family == null)
				{
					throw new ArgumentNullException("family");
				}
				this.fontFamily = family;
				this.size = emSize;
				this.fontStyle = style;
				this.unit = unit;
				this.gdiCharSet = gdiCharSet;
				this.gdiVerticalFont = gdiVerticalFont;
			}
#if CONFIG_SERIALIZATION
	internal Font(SerializationInfo info, StreamingContext context)
			{
				if(info == null)
				{
					throw new ArgumentNullException("info");
				}
				fontFamily = new FontFamily(info.GetString("Name"));
				size = info.GetSingle("Size");
				fontStyle = (FontStyle)info.GetValue
					("Style", typeof(FontStyle));
				unit = (GraphicsUnit)info.GetValue
					("Unit", typeof(GraphicsUnit));
				gdiCharSet = 1;
				gdiVerticalFont = false;
			}
#endif
	private Font(IToolkitFont font)
			{
				this.toolkit = toolkit;
				this.toolkitFont = font;
				// TODO: load the font information from the IToolkitFont
			}

	// Destructor.
	~Font()
			{
				Dispose();
			}

	// Font properties.
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public bool Bold
			{
				get
				{
					return ((fontStyle & FontStyle.Bold) != 0);
				}
			}
#if CONFIG_COMPONENT_MODEL
	[Browsable(false)]
#endif
	public FontFamily FontFamily
			{
				get
				{
					return fontFamily;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public byte GdiCharSet
			{
				get
				{
					return gdiCharSet;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public bool GdiVerticalFont
			{
				get
				{
					return gdiVerticalFont;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[Browsable(false)]
#endif
	public int Height
			{
				get
				{
				#if CONFIG_EXTENDED_NUMERICS
					return (int)(Math.Ceiling(GetHeight()));
				#else
					return (int)(GetHeight() + 0.99f);
				#endif
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public bool Italic
			{
				get
				{
					return ((fontStyle & FontStyle.Italic) != 0);
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	[TypeConverter(typeof(FontConverter.FontNameConverter))]
#endif
#if CONFIG_COMPONENT_MODEL_DESIGN
	[Editor("System.Drawing.Design.FontNameEditor, System.Drawing.Design",
			typeof(UITypeEditor))]
#endif
	public String Name
			{
				get
				{
					return fontFamily.Name;
				}
			}
	public float Size
			{
				get
				{
					return size;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[Browsable(false)]
#endif
	public float SizeInPoints
			{
				get
				{
					float adjust;
					switch(unit)
					{
						case GraphicsUnit.World:
						case GraphicsUnit.Pixel:
						default:
						{
							adjust = 72.0f / ToolkitManager.Toolkit.GetDefaultGraphics().DpiY;
						}
						break;

						case GraphicsUnit.Display:
						{
							adjust = 72.0f / 75.0f;
						}
						break;

						case GraphicsUnit.Point:
						{
							return size;
						}
						// Not reached.

						case GraphicsUnit.Inch:
						{
							adjust = 72.0f;
						}
						break;

						case GraphicsUnit.Document:
						{
							adjust = 72.0f / 300.0f;
						}
						break;

						case GraphicsUnit.Millimeter:
						{
							adjust = 72.0f / (75.0f / 25.4f);
						}
						break;
					}
					return size * adjust;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public bool Strikeout
			{
				get
				{
					return ((fontStyle & FontStyle.Strikeout) != 0);
				}
			}
#if CONFIG_COMPONENT_MODEL
	[Browsable(false)]
#endif
	public FontStyle Style
			{
				get
				{
					return fontStyle;
				}
			}
#if CONFIG_COMPONENT_MODEL
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
	public bool Underline
			{
				get
				{
					return ((fontStyle & FontStyle.Underline) != 0);
				}
			}
#if CONFIG_COMPONENT_MODEL
	[TypeConverter(typeof(FontConverter.FontUnitConverter))]
#endif
	public GraphicsUnit Unit
			{
				get
				{
					return unit;
				}
			}

	// Clone this object.
	public Object Clone()
			{
				lock(this)
				{
					Font font = (Font)(MemberwiseClone());
					font.toolkit = null;
					font.toolkitFont = null;
					return font;
				}
			}

	// Dispose of this object.
	public void Dispose()
			{
				lock(this)
				{
					if(toolkitFont != null)
					{
						toolkitFont.Dispose();
						toolkitFont = null;
					}
					toolkit = null;
				}
			}

	// Determine if two objects are equal.
	public override bool Equals(Object obj)
			{
				Font other = (obj as Font);
				if(other != null)
				{
					return (other.Name == Name &&
							other.Size == Size &&
							other.Style == Style &&
							other.GdiCharSet == GdiCharSet &&
							other.GdiVerticalFont == GdiVerticalFont);
				}
				else
				{
					return false;
				}
			}

	// Get a hash code for this object.
	public override int GetHashCode()
			{
				return Name.GetHashCode() + (((int)size) << 4) +
				       (int)fontStyle;
			}

	// Extract the active font from a native device context.
	public static Font FromHdc(IntPtr hdc)
			{
				IToolkitFont font;
				font = ToolkitManager.Toolkit.GetFontFromHdc(hdc);
				if(font != null)
				{
					return new Font(font);
				}
				else
				{
					return null;
				}
			}

	// Convert a native font handle into a font object.
	public static Font FromHfont(IntPtr hfont)
			{
				IToolkitFont font;
				font = ToolkitManager.Toolkit.GetFontFromHfont(hfont);
				if(font != null)
				{
					return new Font(font);
				}
				else
				{
					return null;
				}
			}

	// Convert a native logical font descriptor into a font object.
	public static Font FromLogFont(Object lf)
			{
				return FromLogFont(lf, IntPtr.Zero);
			}
	public static Font FromLogFont(Object lf, IntPtr hdc)
			{
				IToolkitFont font;
				font = ToolkitManager.Toolkit.GetFontFromLogFont(lf, hdc);
				if(font != null)
				{
					return new Font(font);
				}
				else
				{
					return null;
				}
			}

	// Get the height of this font.
	public float GetHeight()
			{
				return GetHeight(Graphics.DefaultGraphics);
			}
	public float GetHeight(Graphics graphics)
			{
				if(graphics == null)
				{
					throw new ArgumentNullException("graphics");
				}
				
				// Get the font size in raw pixels.
				if (pixelHeight == 0)
					pixelHeight = graphics.GetLineSpacing(this);

				// If the graphics object uses pixels (the most common case),
				// then return the pixel value directly.
				if(graphics.IsPixelUnits())
				{
					return (float)pixelHeight;
				}

				// Get the dpi of the screen for the y axis.
				float dpiY = graphics.DpiY;

				// Convert the pixel value back into points.
				float points = ((float)pixelHeight) / (dpiY / 72.0f);

				// Convert the points into the graphics object's unit.
				switch(graphics.PageUnit)
				{
					case GraphicsUnit.World:
					case GraphicsUnit.Pixel:
					{
						points *= (dpiY / 72.0f);
					}
					break;

					case GraphicsUnit.Display:
					{
						points *= (75.0f / 72.0f);
					}
					break;

					case GraphicsUnit.Point: break;

					case GraphicsUnit.Inch:
					{
						points /= 72.0f;
					}
					break;

					case GraphicsUnit.Document:
					{
						points *= (300.0f / 72.0f);
					}
					break;

					case GraphicsUnit.Millimeter:
					{
						points *= (25.4f / 72.0f);
					}
					break;
				}
				return points;
			}
	public float GetHeight(float dpi)
			{
				if(unit == GraphicsUnit.World || unit == GraphicsUnit.Pixel)
				{
					return GetHeight(Graphics.DefaultGraphics);
				}
				else
				{
					return fontFamily.GetLineSpacing(fontStyle) *
							(size / fontFamily.GetEmHeight(fontStyle)) * dpi;
				}
			}

	// Convert this object into a native font handle.
	public IntPtr ToHfont()
			{
				if(toolkitFont == null)
				{
					IToolkit toolkit = ToolkitManager.Toolkit;
					float dpiY = toolkit.GetDefaultGraphics().DpiY;
					GetFont(toolkit, dpiY);
				}
				return toolkitFont.GetHfont();
			}

	// Fill in a native font information structure with info about this font.
	public void ToLogFont(Object lf)
			{
				ToLogFont(lf, null);
			}
	public void ToLogFont(Object lf, Graphics graphics)
			{
				IToolkitGraphics g;
				if(graphics != null)
				{
					g = graphics.ToolkitGraphics;
				}
				else
				{
					g = null;
				}
				lock(this)
				{
					if(toolkitFont == null)
					{
						if(g != null)
						{
							GetFont(g.Toolkit, graphics.DpiY);
						}
						else
						{
							IToolkit toolkit = ToolkitManager.Toolkit;
							float dpiY = toolkit.GetDefaultGraphics().DpiY;
							GetFont(toolkit, dpiY);
						}
					}
					toolkitFont.ToLogFont(lf, g);
				}
			}

	// Convert this object into a string.
	public override String ToString()
			{
				StringBuilder builder = new StringBuilder();
				builder.Append("[Font: Name=");
				builder.Append(Name);
				builder.Append(" Size=");
			#if CONFIG_EXTENDED_NUMERICS
				builder.Append(Size);
			#else
				builder.Append((int)Size);
			#endif
				builder.Append(" Units=");
				builder.Append((int)Unit);
				builder.Append(" GdiCharSet=");
				builder.Append(GdiCharSet);
				builder.Append(" GdiVerticalFont=");
				builder.Append(GdiVerticalFont);
				if(Style != FontStyle.Regular)
				{
					builder.Append(" Style=");
					builder.Append(Style);
				}
				builder.Append("]");
				return builder.ToString();
			}

#if CONFIG_SERIALIZATION

	// Get the serialization information for this object.
	void ISerializable.GetObjectData(SerializationInfo info,
									 StreamingContext context)
			{
				if(info == null)
				{
					throw new ArgumentNullException("info");
				}
				info.AddValue("Name", Name);
				info.AddValue("Size", Size);
				info.AddValue("Style", Style);
				info.AddValue("Unit", Unit);
			}

#endif

	// Get the toolkit version of this font for a specific toolkit.
	internal IToolkitFont GetFont(IToolkit toolkit, float dpi)
			{
				lock(this)
				{
					if(this.toolkitFont == null)
					{
						// We don't yet have a toolkit font yet.
						this.toolkitFont = toolkit.CreateFont(this, dpi);
						this.toolkit = toolkit;
						return this.toolkitFont;
					}
					else if(this.toolkit == toolkit)
					{
						// Same toolkit - return the cached pen information.
						return this.toolkitFont;
					}
					else
					{
						// We have a font object for another toolkit,
						// so dispose it and create for this toolkit.
						// We null out "toolkitFont" before calling
						// "CreateFont()" just in case an exception
						// is thrown while creating the toolkit font.
						this.toolkitFont.Dispose();
						this.toolkitFont = null;
						this.toolkitFont = toolkit.CreateFont(this, dpi);
						this.toolkit = toolkit;
						return this.toolkitFont;
					}
				}
			}

}; // class Font

}; // namespace System.Drawing
