PageRenderTime 698ms CodeModel.GetById 185ms app.highlight 253ms RepoModel.GetById 243ms app.codeStats 1ms

/ContentSystem/Rendering/FontData.cs

#
C# | 1441 lines | 826 code | 144 blank | 471 comment | 90 complexity | 36f42db1212527fad6e6864f395aab3a MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1using System;
   2using System.Collections.Generic;
   3using Delta.ContentSystem.Rendering.Helpers;
   4using Delta.ContentSystem.Xml;
   5using Delta.Engine;
   6using Delta.Utilities;
   7using Delta.Utilities.Datatypes;
   8using Delta.Utilities.Datatypes.Advanced;
   9using Delta.Utilities.Graphics;
  10using Delta.Utilities.Helpers;
  11using Delta.Utilities.Xml;
  12using NUnit.Framework;
  13
  14namespace Delta.ContentSystem.Rendering
  15{
  16	/// <summary>
  17	/// That class takes care about the correct loading of the requested bitmap
  18	/// font based on the given parameters in the constructor. The font bitmap
  19	/// textures itself (font maps) will be created by the FontGenerator of the
  20	/// ContentServer.
  21	/// </summary>
  22	public class FontData : XmlData
  23	{
  24		#region Constants
  25		/// <summary>
  26		/// The minimal size (in points) that a font is allowed to have.
  27		/// </summary>
  28		public const int MinFontSize = 5;
  29
  30		/// <summary>
  31		/// The maximal size (in points) that a font is allowed to have.
  32		/// </summary>
  33		public const int MaxFontSize = 72;
  34
  35		/// <summary>
  36		/// Helper constant to initialize the "char" variables
  37		/// </summary>
  38		private const char NoChar = '\0';
  39
  40		/// <summary>
  41		/// The fallback character which is used if a character isn't supported by
  42		/// a font.
  43		/// </summary>
  44		/// <remarks>
  45		/// Is used for parsing a text.
  46		/// </remarks>
  47		private const char FallbackChar = '?';
  48		#endregion
  49
  50		#region Get (Static)
  51		/// <summary>
  52		/// Get and load content based on the content name. This method makes sure
  53		/// we do not load the same content twice (the constructor is protected).
  54		/// </summary>
  55		/// <param name="contentName">
  56		/// Content name we want to load, this is passed onto the Content System,
  57		/// which will do the actual loading with help of the Load method in this
  58		/// class.
  59		/// </param>
  60		/// <returns>
  61		/// The loaded Content object, always unique for the same name, this helps
  62		/// comparing data.
  63		/// </returns>
  64		public new static FontData Get(string contentName)
  65		{
  66			return Get<FontData>(contentName, ContentType.Font);
  67		}
  68
  69		/// <summary>
  70		/// Get or load a font without the content name, but instead by searching
  71		/// for all the meta data: Font Family Name, Font Size and Font Style.
  72		/// Please note that other values like Font Tracking is ignored, you have
  73		/// to use the Get(contentName) overload for that.
  74		/// <para />
  75		/// This method will always return a font. If just a style or size was not
  76		/// found a content entry with the same font family is returned. If that
  77		/// is also not found the default font is returned (which is part of each
  78		/// project because the Engine content is always available as fallback
  79		/// content).
  80		/// </summary>
  81		/// <param name="fontFamilyName">
  82		/// Font family name (like "Verdana", "Arial", etc.).
  83		/// </param>
  84		/// <param name="fontSize">Font size (like 9pt, 12pt, 24pt, etc.).</param>
  85		/// <param name="fontStyle">Font style flags (bold, italic, sharp, etc.)
  86		/// </param>
  87		/// <returns>
  88		/// The loaded FontData content object. If this content was loaded before
  89		/// the same copy is used again.
  90		/// </returns>
  91		public static FontData Get(string fontFamilyName, int fontSize,
  92			FontStyle fontStyle)
  93		{
  94			ContentMetaData bestMatch = null;
  95			Dictionary<string, ContentMetaData> allFontContent =
  96				ContentManager.GetAllContentMetaData(ContentType.Font);
  97			foreach (ContentMetaData fontContent in allFontContent.Values)
  98			{
  99				// Note: ShaderFlags is used for ShaderFeatureFlags.
 100				if (fontContent.FontFamilyName.Compare(fontFamilyName))
 101				{
 102					// Did we find a matching font with the correct size and style?
 103					if (fontContent.FontSize == fontSize &&
 104					    fontContent.FontStyle == fontStyle)
 105					{
 106						// Thats good. Get out of here and load the FontData!
 107						return Get(fontContent.Name);
 108					}
 109
 110					// Just name matches, then only set this if we have nothing yet.
 111					if (bestMatch == null)
 112					{
 113						bestMatch = fontContent;
 114					}
 115					// If the font size matches too, replace the entry.
 116					if (fontContent.FontSize == fontSize)
 117					{
 118						if (bestMatch.FontSize != fontSize)
 119						{
 120							bestMatch = fontContent;
 121						}
 122					}
 123				}
 124			} // foreach
 125
 126			// Did we find something? Then load that, but report that this is not
 127			// a perfect match.
 128			if (bestMatch != null)
 129			{
 130				Log.Warning(
 131					"Found a font with the font family name '" + fontFamilyName +
 132					"', but the size='" + fontSize + "' or style='" + fontStyle +
 133					"' did not match: " + bestMatch);
 134				// Return and load it anyway.
 135				return Get(bestMatch.Name);
 136			}
 137
 138			Log.Warning(
 139				"Found no font with the font family name '" + fontFamilyName +
 140				"' (in any size, but the size='" + fontSize + "' and style='" +
 141				fontStyle + "' was requested).");
 142
 143			// Nothing found? Then load the fallback font (will throw more warnings)!
 144			return Get("");
 145		}
 146		#endregion
 147
 148		#region Default (Static)
 149		/// <summary>
 150		/// The font data which is chosen by default if a concrete one is not
 151		/// available (yet). This is always Verdana, 12pt, with an outline.
 152		/// </summary>
 153		public static FontData Default
 154		{
 155			get
 156			{
 157				if (defaultData == null)
 158				{
 159					defaultData = Get("Verdana", 12, FontStyle.AddOutline);
 160				} // if
 161
 162				return defaultData;
 163			} // get
 164		}
 165		#endregion
 166
 167		#region ResolutionFonts (Public)
 168		/// <summary>
 169		/// Font content entries can either be concrete font settings with an
 170		/// xml file for all the glyph data or just a parent entry with children
 171		/// that contain the resolution specific font data (4 children for the 4
 172		/// default resolutions, see Font.DetermineBestFont, which switches fonts
 173		/// at 320p, 480p, 640p and 960p).
 174		/// </summary>
 175		public FontData[] ResolutionFonts;
 176		#endregion
 177
 178		#region FamilyName (Public)
 179		/// <summary>
 180		/// The family name of the font, e.g. Verdana, Consolas, etc.
 181		/// </summary>
 182		public string FamilyName
 183		{
 184			get
 185			{
 186				return data.FontFamilyName;
 187			}
 188		}
 189		#endregion
 190
 191		#region SizeInPoints (Public)
 192		/// <summary>
 193		/// The size of the font in points, e.g. 12, 14, etc.
 194		/// </summary>
 195		public int SizeInPoints
 196		{
 197			get
 198			{
 199				return data.FontSize;
 200			}
 201		}
 202		#endregion
 203
 204		#region Style (Public)
 205		/// <summary>
 206		/// The style how the characters will be shown, e.g. normal, bold, italic,
 207		/// etc.
 208		/// </summary>
 209		public FontStyle Style
 210		{
 211			get
 212			{
 213				return data.FontStyle;
 214			}
 215		}
 216		#endregion
 217
 218		#region Tracking (Public)
 219		/// <summary>
 220		/// Defines the distance (in normalized percentage) which is added between
 221		/// 2 characters. This is only used for this FontData and set by the
 222		/// content system (cannot be changed by the user, unlike
 223		/// Font.TrackingMultiplier, which is just a multiplier to adjust spacing).
 224		/// <para/>
 225		/// 0.0 means no additional space (default)
 226		/// <para/>
 227		/// 1.0 means 100% of the AdvanceWidth of the character as additional space
 228		/// to the right.
 229		/// <para/>
 230		/// -1.0 means 100% of the AdvanceWidth of the character as "reverse" space
 231		/// to the left.
 232		/// </summary>
 233		public float Tracking
 234		{
 235			get
 236			{
 237				return data.FontTracking / 100.0f;
 238			}
 239		}
 240		#endregion
 241
 242		#region PixelLineHeight (Public)
 243		/// <summary>
 244		/// The height of the font in pixel space based on the set font size.
 245		/// <para/>
 246		/// CAUTION: That is not the final height of the font on the screen,
 247		/// it is multiplied by the font meta data line height multiplier and then
 248		/// converted to quad space for rendering (see Font class). Finally it can
 249		/// also be modified by the Font class with the LineHeightMultiplier.
 250		/// </summary>
 251		public int PixelLineHeight
 252		{
 253			get;
 254			private set;
 255		}
 256		#endregion
 257
 258		#region LeftOffset
 259		/// <summary>
 260		/// When each font character glyph is drawn by the FontGenerator several
 261		/// factors might offset the character like the distance from the baseline
 262		/// to the actual first pixel drawn and also margins added and effects
 263		/// like outlining and adding shadow might change the render size. In oder
 264		/// to still position the character to render correctly this offset must be
 265		/// added to the render position to correctly position each glyph when
 266		/// rendering a font.
 267		/// </summary>
 268		public int LeftOffset
 269		{
 270			get;
 271			private set;
 272		}
 273		#endregion
 274
 275		#region TopOffset
 276		/// <summary>
 277		/// Similar to RenderMarginLeft each glyph might have been offseted because
 278		/// of margins used in FontGenerator plus outlining and shadow effects.
 279		/// In oder to still position the character to render correctly this offset
 280		/// must be added to the render position to correctly position each glyph
 281		/// when rendering a font.
 282		/// </summary>
 283		public int TopOffset
 284		{
 285			get;
 286			private set;
 287		}
 288		#endregion
 289
 290		#region FontMapNames (Public)
 291		/// <summary>
 292		/// The list of the names of font maps where all characters are stored.
 293		/// </summary>
 294		public string[] FontMapNames
 295		{
 296			get;
 297			private set;
 298		}
 299		#endregion
 300
 301		#region Internal
 302
 303		#region glyphDictionary (Internal)
 304		/// <summary>
 305		/// Dictionary of each unicode character we have in the font maps and can
 306		/// use for drawing.
 307		/// </summary>
 308		internal Dictionary<char, Glyph> glyphDictionary;
 309		#endregion
 310
 311		#endregion
 312
 313		#region Private
 314
 315		#region defaultData (Private)
 316		/// <summary>
 317		/// Default data
 318		/// </summary>
 319		private static FontData defaultData;
 320		#endregion
 321
 322		#region kernDictionary (Private)
 323		/// <summary>
 324		/// Character spacing with kerning dictionary, which tells us how much
 325		/// spacing is needed between two characters. Most character combinations
 326		/// have no extra values in here, but some have (like W, I, etc.)
 327		/// </summary>
 328		private Dictionary<char, Dictionary<char, int>> kernDictionary;
 329		#endregion
 330
 331		#endregion
 332
 333		#region Constructors
 334		/// <summary>
 335		/// Creates a font from the content data. All font data is stored in xml
 336		/// files (in the Fonts content directory). Will also load all bitmaps
 337		/// required for this font.
 338		/// </summary>
 339		/// <param name="contentName">Font content name to load. If this is empty
 340		/// no content will be loaded (just fallback data will be set).</param>
 341		protected FontData(string contentName)
 342			// For the content we need to make sure that each font content name
 343			// is unique and therefore we need to add the size, etc. to the name!
 344			: base(contentName, ContentType.Font)
 345		{
 346		}
 347		#endregion
 348
 349		#region GetGlyphDrawInfos (Public)
 350		/// <summary>
 351		/// Return the draw info of all glyphs that are needed to show the text
 352		/// on the screen (in pixel space).
 353		/// <para/>
 354		/// ASCII reference:
 355		/// http://www.tcp-ip-info.de/tcp_ip_und_internet/ascii.htm
 356		/// <para/>
 357		/// Note: The glyph info exists as single array but the data itself
 358		/// represents the text inclusive line breaks, so in other words the
 359		/// several text lines are "flattened" to a single-dimensioned array.
 360		/// </summary>
 361		/// <param name="text">Text</param>
 362		/// <param name="lineSpacing">Line spacing</param>
 363		/// <param name="textAlignment">Horizontal text alignment mode</param>
 364		/// <returns>
 365		/// List of GlyphDrawInfos ready for drawing.
 366		/// </returns>
 367		public GlyphDrawInfo[] GetGlyphDrawInfos(string text, float lineSpacing,
 368			HorizontalAlignment textAlignment)
 369		{
 370			Size infiniteSize = Size.Zero;
 371			return GetGlyphDrawInfos(text, lineSpacing, textAlignment, false, false,
 372				ref infiniteSize);
 373		}
 374
 375		/// <summary>
 376		/// Return the draw info of all glyphs that are needed to show the text
 377		/// on the screen (in pixel space).
 378		/// <para/>
 379		/// ASCII reference:
 380		/// http://www.tcp-ip-info.de/tcp_ip_und_internet/ascii.htm
 381		/// <para/>
 382		/// Note: The glyph info exists as single array but the data itself
 383		/// represents the text inclusive line breaks, so in other words the
 384		/// several text lines are "flattened" to a single-dimensioned array.
 385		/// </summary>
 386		/// <param name="text">Text</param>
 387		/// <param name="lineSpacing">Line spacing</param>
 388		/// <param name="textAlignment">Horizontal text alignment mode</param>
 389		/// <param name="isClippingOn">Is clipping check required</param>
 390		/// <param name="maxTextSize">Max. available size in Pixel Space</param>
 391		/// <param name="isWordWrapOn">
 392		/// Indicates if the words of the given text should be wrapped or clipped
 393		/// (if enabled) at the end of a text line.
 394		/// </param>
 395		/// <returns>
 396		/// List of GlyphDrawInfos ready for drawing.
 397		/// </returns>
 398		public GlyphDrawInfo[] GetGlyphDrawInfos(string text, float lineSpacing,
 399			HorizontalAlignment textAlignment, bool isClippingOn, bool isWordWrapOn,
 400			ref Size maxTextSize)
 401		{
 402			if (FontMapNames.Length == 0)
 403			{
 404				// This warning is kind of a duplicate because we already got a warning
 405				// from the font data loading, but this one is useful because it
 406				// happens when trying to render some text, which will be aborted!
 407				return new GlyphDrawInfo[0];
 408			} // if
 409
 410			// Define the list of glyphs which represents the drawing info for
 411			// each character of the text
 412			List<GlyphDrawInfo> glyphInfos = new List<GlyphDrawInfo>();
 413
 414			// At first split the given text in several text lines
 415			// (if there are line breaks)
 416			List<float> textlineWidths;
 417			float maxTextlineWidth;
 418			List<List<char>> textlines = GetTextLines(text, lineSpacing, maxTextSize,
 419				textAlignment, isClippingOn, isWordWrapOn,
 420				out textlineWidths, out maxTextlineWidth);
 421
 422			// The total height for every line
 423			float finalLineHeight = PixelLineHeight * lineSpacing;
 424			// The values of the drawing info of the last character
 425			GlyphDrawInfo lastDrawInfo = new GlyphDrawInfo
 426			{
 427				DrawArea = new Rectangle(Point.Zero, new Size(0, finalLineHeight)),
 428				UV = new Rectangle(Point.Zero, new Size(0, finalLineHeight)),
 429			};
 430
 431			for (int lineId = 0; lineId < textlines.Count; lineId++)
 432			{
 433				List<char> textline = textlines[lineId];
 434
 435				// Sanity check, because we only need to align and handle characters
 436				// if we have some
 437				if (textline.Count > 0)
 438				{
 439					switch (textAlignment)
 440					{
 441							// For left-aligned texts (that goes left-to-right) we need to
 442							// subtract the LeftSideBearing to make sure that every first
 443							// character of a text line starts really at the same left border
 444							// -> 'abcde'   instead of   'abcde'
 445							//    'bcd'                  ' bcd'
 446							//    'cdef'                 'cdef'
 447						case HorizontalAlignment.Left:
 448							char firstChar = textline[0];
 449							lastDrawInfo.DrawArea.X = MathHelper.Round(
 450								glyphDictionary[firstChar].LeftSideBearing);
 451							break;
 452
 453							// For center-aligned text, we add here the LeftSideBearing too
 454							// like in the left-aligned mode, but additionally we still make
 455							// sure that the every text line is centered (based on the
 456							// longest text line)
 457						case HorizontalAlignment.Centered:
 458							firstChar = textline[0];
 459							lastDrawInfo.DrawArea.X = MathHelper.Round(
 460								(maxTextlineWidth - textlineWidths[lineId]) * 0.5f +
 461								glyphDictionary[firstChar].LeftSideBearing);
 462							break;
 463
 464							// For right-aligned texts (that goes left-to-right) we need to
 465							// add the RightSideBearing of the last character of a text line
 466							// at the beginning the text line to make sure that every last
 467							// character of a text line ends really at the same right border
 468							// (because we all add the chars from left-to-right)
 469							// -> 'abcde'   instead of   'abcde'
 470							//      'bcd'                 'bcd '
 471							//     'cdef'                 'cdef'
 472						case HorizontalAlignment.Right:
 473							char lastChar = textline[textline.Count - 1];
 474							lastDrawInfo.DrawArea.X = MathHelper.Round(
 475								(maxTextlineWidth - textlineWidths[lineId]) -
 476								glyphDictionary[lastChar].RightSideBearing);
 477							break;
 478
 479						default:
 480							lastDrawInfo.DrawArea.X = 0;
 481							// Nothing to do for us
 482							break;
 483					} // switch
 484
 485					// Also apply the offset for each letter
 486					lastDrawInfo.DrawArea.X -= LeftOffset;
 487					lastDrawInfo.DrawArea.Y -= TopOffset;
 488
 489					// Now iterate through all characters of the text
 490					for (int charIndex = 0; charIndex < textline.Count; charIndex++)
 491					{
 492						char textChar = textline[charIndex];
 493
 494						// Figure out which is the next character, because we need it for
 495						// the kerning to it
 496						char nextChar =
 497							charIndex + 1 < textline.Count
 498								? textline[charIndex + 1]
 499								: NoChar;
 500
 501						// Get now the character we have to handle here.
 502						// Note: We don't need to care about sanity checks, because that
 503						// has happen in the "GetTextlines()" already which means that we
 504						// get here only supported characters
 505						Glyph characterGlyph;
 506						glyphDictionary.TryGetValue(textChar, out characterGlyph);
 507						if (characterGlyph == null)
 508						{
 509							Log.Warning(
 510								"The glyph for the character '" + textChar + 
 511								"' doesn't exists yet in the FontData '" + Name + "', will " +
 512								"use the '?' glyph instead.", false);
 513							characterGlyph = glyphDictionary['?'];
 514						} // if
 515
 516						// Get the width of the current glyph that is used know the
 517						// required distance to the next character
 518						int glyphWidth = characterGlyph.GetDrawWidth(nextChar, Tracking);
 519
 520						// and build the drawing info for the current character now
 521						GlyphDrawInfo newDrawInfo = new GlyphDrawInfo
 522						{
 523							DrawArea = new Rectangle(lastDrawInfo.DrawArea.TopLeft,
 524								characterGlyph.UV.Size),
 525							UV = characterGlyph.FontMapUV,
 526							FontMapId = characterGlyph.FontMapId,
 527						};
 528
 529						// that we add the glyph info into the list
 530						glyphInfos.Add(newDrawInfo);
 531						// and update the last glyph drawing info with it
 532						lastDrawInfo = newDrawInfo;
 533						// with the position for the next character
 534						lastDrawInfo.DrawArea.X += glyphWidth;
 535					} // for
 536				} // if
 537
 538				// Go to the next text line
 539				lastDrawInfo.DrawArea.Y += finalLineHeight;
 540			} // for
 541
 542			return glyphInfos.ToArray();
 543		}
 544		#endregion
 545
 546		#region ToString (Public)
 547		/// <summary>
 548		/// To String method, will just extend the Content.ToString method by
 549		/// some extra font meta information.
 550		/// </summary>
 551		/// <returns>A info string about this object instance.</returns>
 552		public override string ToString()
 553		{
 554			return base.ToString() + ", Font Family Name=" + data.FontFamilyName +
 555			       ", Font Size=" + data.FontSize + ", Font Style=" + data.FontStyle;
 556		}
 557		#endregion
 558
 559		#region Methods (Private)
 560
 561		#region Load
 562		/// <summary>
 563		/// Native load method, will just load the xml data.
 564		/// </summary>
 565		/// <param name="alreadyLoadedNativeData">
 566		/// The first instance that has already loaded the required content data
 567		/// of this content class or just 'null' if there is none loaded yet (or
 568		/// anymore).
 569		/// </param>
 570		protected override void Load(Content alreadyLoadedNativeData)
 571		{
 572			// Make sure FontMapNames is not null, else we might crash later.
 573			FontMapNames = new string[0];
 574
 575			// If this entry has no RelativeFilePath, it should have children.
 576			if (String.IsNullOrEmpty(RelativeFilePath))
 577			{
 578				// Just load all the children entries (only up to 4, ignore the rest)
 579				List<FontData> childFonts = new List<FontData>();
 580				foreach (ContentMetaData child in data.Children)
 581				{
 582					// Grab all Font children content entries
 583					if (child.Type == ContentType.Font)
 584					{
 585						childFonts.Add(Get(child.Name));
 586					}
 587				}
 588
 589				// If we have none, there is something wrong with the content!
 590				if (childFonts.Count == 0)
 591				{
 592					// Only warn if the application is still running and was not aborted.
 593					if (Application.IsShuttingDown == false)
 594					{
 595						Log.Warning(
 596							"Unable to load font data, because the font content '" +
 597							Name + "' has no file data and also no font children!", false);
 598					}
 599					return;
 600				}
 601
 602				// Warnings if we have too many fonts are in the Font constructor!
 603				ResolutionFonts = childFonts.ToArray();
 604				// We don't need to load anything more
 605				return;
 606			} // if
 607
 608			// First load all the xml font data.
 609			base.Load(alreadyLoadedNativeData);
 610
 611			// If that failed, we can't do anything, font rendering won't work.
 612			if (RootNode == null ||
 613			    String.IsNullOrEmpty(RootNode.Name))
 614			{
 615				Log.Warning(
 616					"Unable to load font data '" + Name + "', because the content " +
 617					"couldn't be found and it seems that there is no working fallback " +
 618					"either. Tried to load file: " + RelativeFilePath, false);
 619				return;
 620			} // if
 621
 622			// Okay, do all the loading now!
 623			try
 624			{
 625				#region Validation
 626				// First find out if this is a font at all
 627				if (RootNode.Name != "Font")
 628				{
 629					// Note: Exceptions are handled below. This will not crash caller.
 630					Log.Warning(
 631						"This content file '" + RootNode.FilePath + "' is not a font, " +
 632						"unable to load the font data! (RootNode=" + RootNode.Name + ")",
 633						false);
 634					return;
 635				} // if
 636				#endregion
 637
 638				// Grab the version to determinate what data we can expect.
 639				Version version = new Version(0, 9, 0);
 640				string versionText = RootNode.GetAttribute("Version");
 641				if (String.IsNullOrEmpty(versionText) == false)
 642				{
 643					Version.TryParse(versionText, out version);
 644				}
 645
 646				string bitmapNameAttribute = "Name";
 647				string characterAttribute = "Character";
 648				string bitmapIndexAttribute = "BitmapIndex";
 649				string uvAttribute = "UV";
 650				string advanceWidthAttribute = "AdvanceWidth";
 651				string leftBearingAttribute = "LeftBearing";
 652				string rightBearingAttribute = "RightBearing";
 653				string firstAttribute = "First";
 654				string secondAttribute = "Second";
 655				string distanceAttribute = "Distance";
 656
 657				// Still support fonts from v0.9.0, which used the version 0.1. Skip
 658				// all the validation checks for v0.1
 659				if (version > new Version(0, 1))
 660				{
 661					// Load some global data for this font and validate in debug mode
 662					LeftOffset = RootNode.GetAttributeAs("LeftOffset", 0);
 663					TopOffset = RootNode.GetAttributeAs("TopOffset", 0);
 664				}
 665				else
 666				{
 667					// The old version had slightly different names for some attributes
 668					bitmapNameAttribute = "Filename";
 669					characterAttribute = "char";
 670					uvAttribute = "uv";
 671					advanceWidthAttribute = "advanceWidth";
 672					leftBearingAttribute = "leftBearing";
 673					rightBearingAttribute = "rightBearing";
 674					bitmapIndexAttribute = "bitmap";
 675					firstAttribute = "first";
 676					secondAttribute = "second";
 677					distanceAttribute = "distance";
 678				}
 679
 680				PixelLineHeight = RootNode.GetAttributeAs("LineHeight", SizeInPoints);
 681				// and make sure we have no other data loaded yet
 682				glyphDictionary = new Dictionary<char, Glyph>();
 683				kernDictionary = new Dictionary<char, Dictionary<char, int>>();
 684
 685				List<string> fontMapNames = new List<string>();
 686				// Little helper to remember bitmap sizes, which are also known
 687				// by the material once it is loaded, but to keep delayed loading
 688				// we do not need to load it here right away (only when it is used
 689				// for the first time and we actually need the material for drawing).
 690				List<Size> fontMapSizes = new List<Size>();
 691				// Helper to make xml attribute warnings more descriptive.
 692				string fontWarningText = "Font " + Name;
 693
 694				// Get the used bitmaps, glyphs and kernings, thats all we need!
 695				foreach (XmlNode childNode in RootNode.Children)
 696				{
 697					switch (childNode.Name)
 698					{
 699							#region 1. step - load all fontmaps
 700						case "Bitmap":
 701							// Load the font map data, currently we only support a single one
 702							string fontMapName = childNode.GetAttribute(bitmapNameAttribute);
 703							int fontMapWidth = childNode.GetAttributeAs("Width", 1);
 704							int fontMapHeight = childNode.GetAttributeAs("Height", 1);
 705							fontMapNames.Add(fontMapName);
 706							fontMapSizes.Add(new Size(fontMapWidth, fontMapHeight));
 707							break;
 708							#endregion
 709
 710							#region 2. step - load all glyphs
 711						case "Glyphs":
 712							// Load all glyphs
 713							foreach (XmlNode glyphNode in childNode.Children)
 714							{
 715								#region Validation
 716								// We cannot load glyphs without having font maps!
 717								if (fontMapNames.Count == 0)
 718								{
 719									throw new NotSupportedException(
 720										"We cannot add and calculate font glyphs with no bitmap " +
 721										"fonts defined. The font xml data is not complete!");
 722								} // if
 723								#endregion
 724
 725								char character =
 726									glyphNode.GetAttributeAs(characterAttribute, ' ');
 727								Glyph glyph = new Glyph
 728								{
 729									FontMapId =
 730										glyphNode.GetAttributeAs(bitmapIndexAttribute, 0),
 731									UV = Rectangle.FromColladaString(
 732										glyphNode.GetAttribute(uvAttribute)),
 733									LeftSideBearing = (int)Math.Round(glyphNode.GetAttributeAs(
 734										leftBearingAttribute, 0.0f, fontWarningText)),
 735									RightSideBearing = (int)Math.Round(glyphNode.GetAttributeAs(
 736										rightBearingAttribute, 0.0f, fontWarningText)),
 737									Kernings = null,
 738								};
 739
 740								if (glyph.FontMapId >=
 741								    fontMapNames.Count)
 742								{
 743									Log.Warning(
 744										"Unable to use FontMapNumber '" + glyph.FontMapId +
 745										"' for this glyph because we only have '" +
 746										fontMapNames.Count + "' font maps. The FontMapNumber " +
 747										"will resseted now to '" + (fontMapNames.Count - 1) + "'");
 748									glyph.FontMapId = fontMapNames.Count - 1;
 749								} // if
 750
 751								// Default fallback is width minus some small offset,
 752								// this will never be used except advanceWidth is missing
 753								glyph.AdvanceWidth =
 754									glyphNode.GetAttributeAs(advanceWidthAttribute,
 755										glyph.UV.Width - 2.0f, fontWarningText);
 756
 757								// Calculate the FontMapUV with help of font map sizes
 758								// remembered above. Error checking is done above already.
 759								Size fontMapSize = fontMapSizes[glyph.FontMapId];
 760								// Build the UV's with the usual halfpixel offset, etc.
 761								glyph.FontMapUV = Rectangle.BuildUVRectangle(glyph.UV,
 762									fontMapSize);
 763
 764								// And finally add it to our glyph dictionary
 765								glyphDictionary.Add(character, glyph);
 766							} // foreach
 767							break;
 768							#endregion
 769
 770							#region 3. step - load all kernings
 771						case "Kernings":
 772							// Load all kernings
 773							foreach (XmlNode kernNode in childNode.Children)
 774							{
 775								char firstChar = kernNode.GetAttributeAs(firstAttribute, ' ',
 776									fontWarningText);
 777								char secondChar = kernNode.GetAttributeAs(secondAttribute, ' ',
 778									fontWarningText);
 779								int kerningDistance = kernNode.GetAttributeAs(distanceAttribute,
 780									0, fontWarningText);
 781
 782								// Try to find the character for that a kerning with an other
 783								// exists
 784								Glyph glyph;
 785								if (glyphDictionary.TryGetValue(firstChar, out glyph))
 786								{
 787									// If it exists (what should be usually the case)
 788									Dictionary<char, int> glyphKernings;
 789									// look if we have already added some kernings
 790									if (glyph.Kernings != null)
 791									{
 792										glyphKernings = glyph.Kernings;
 793									} // if
 794
 795										// or do we add the first one
 796									else
 797									{
 798										// in that case initialize the dictionary
 799										glyphKernings = new Dictionary<char, int>();
 800										// and link it to the glyph
 801										glyph.Kernings = glyphKernings;
 802									} // else
 803
 804									// finally still add the new distance value
 805									glyphKernings.Add(secondChar, kerningDistance);
 806								} // if
 807								else
 808								{
 809									Log.Warning(
 810										"The font content data contains a kerning for the " +
 811										"character '" + firstChar + "' to the character '" +
 812										secondChar + "' but there is no glyph information for '" +
 813										firstChar + "'.", false);
 814								} // else
 815							} // foreach
 816							break;
 817							#endregion
 818					} // switch
 819				} // foreach
 820
 821				FontMapNames = fontMapNames.ToArray();
 822			} // try
 823			catch (Exception ex)
 824			{
 825				Log.Warning("Failed to load font data for '" + Name + "': " + ex);
 826				// Loading failed (for Font to know), report new error for every font
 827				FailedToLoad = true;
 828			} // catch
 829		}
 830		#endregion
 831
 832		#region ParseText
 833		/// <summary>
 834		/// Parses the given text by analyzing every character to decide if the
 835		/// current is supported or not. Every character that is not drawable will
 836		/// be replaced by the "fallback character" (or skipped if the font doesn't
 837		/// support even that). Addtionally by checking the characters also (every
 838		/// tyoe of) line breaks will be detected and teh text splitted by them.
 839		/// </summary>
 840		/// <param name="text">Text</param>
 841		/// <remarks>
 842		/// This method has no validation checks because every caller makes already
 843		/// sure that the text is valid.
 844		/// </remarks>
 845		internal List<List<char>> ParseText(string text)
 846		{
 847			// Our text lines we will return after parsing
 848			List<List<char>> finalTextlines = new List<List<char>>();
 849
 850			// The parsed characters of the current text line
 851			List<char> textline = new List<char>();
 852
 853			char[] textChars = text.ToCharArray();
 854			for (int charIndex = 0; charIndex < textChars.Length; charIndex++)
 855			{
 856				// Grab the current character we have to check now
 857				char textChar = textChars[charIndex];
 858
 859				// Also compute the index of the next char which is used to "detect"
 860				// a Windows line break and when the text has ended
 861				int nextCharIndex = charIndex + 1;
 862				// and indicate if we have find a line break
 863				bool isLineBreak = false;
 864
 865				#region Newlines
 866				// At first check for new-lines, possible values are:
 867				// - Windows = \r\n
 868				// - Unix = \n
 869				// - Macintosh = \r
 870
 871				// Unix
 872				if (textChar == '\n')
 873				{
 874					isLineBreak = true;
 875				} // if
 876
 877					// Macintosh or the first part of the Windows new-line
 878				else if (textChar == '\r')
 879				{
 880					// In both cases it's we need to go to the next line
 881					isLineBreak = true;
 882
 883					// but also need to check for the second part of the Windows new-line
 884					if (nextCharIndex < textChars.Length &&
 885					    textChars[nextCharIndex] == '\n')
 886					{
 887						// that we can ignore for the next loop-iteration
 888						charIndex++;
 889					} // if
 890				} // else if
 891					#endregion
 892
 893					#region Tabs
 894					// Just convert a tab into 2 spaces
 895				else if (textChar == '\t')
 896				{
 897					for (int num = 0; num < 2; num++)
 898					{
 899						textline.Add(' ');
 900					} // for
 901				} // else if
 902					#endregion
 903
 904					#region All normal characters (including space)
 905					// Ignore all other special characters (EOT, BackSpace, etc.)
 906					// and the newline that we maybe have handled above already
 907				else if (textChar >= ' ')
 908				{
 909					// Check now if the current text character is supported by the font
 910					// so we can allow it
 911					if (glyphDictionary.ContainsKey(textChar))
 912					{
 913						textline.Add(textChar);
 914					} // if
 915
 916						// in the case it isn't supported then log a warning and use the
 917						// "fallback" character to indicate the character in the text which
 918						// is wrong
 919					else if (glyphDictionary.ContainsKey(FallbackChar))
 920					{
 921						Log.Warning("Sorry the current font '" + this + "' doesn't " +
 922						            "support the character '" + textChar + "', so will use the '" +
 923						            FallbackChar + "' as fallback character instead.");
 924						textline.Add(FallbackChar);
 925					} // else if
 926
 927						// if even the "fallback" character isn't supported by the font then
 928						// log too but we have to skip this character then
 929					else
 930					{
 931						Log.Warning("Oops, can't even use a '?' in the text to indicate" +
 932						            " that the character '" + textChar + "' is not available in" +
 933						            " the font '" + this + "', the character will be skipped now.");
 934						continue;
 935					} // else
 936				} // else if
 937				#endregion
 938
 939				// If we have detected a line break now
 940				if (isLineBreak ||
 941				    // or have reached the last character of the text
 942				    nextCharIndex == textChars.Length)
 943				{
 944					// then the parsing of the current text line is complete and we can
 945					// go to the next one
 946					finalTextlines.Add(textline);
 947					textline = new List<char>();
 948				} // if
 949			} // for
 950
 951			return finalTextlines;
 952		}
 953		#endregion
 954
 955		#region GetTextLines
 956		/// <summary>
 957		/// Gets the single text lines of multi line text by determining the line
 958		/// breaks. Each of the detected text line will be represented as a list of
 959		/// chars whereby only "known" characters will be listed here or at least
 960		/// represented by a question mark. All other characters will skipped and
 961		/// logged out.
 962		/// </summary>
 963		/// <remarks>
 964		/// This method will also handle clipping and word-wrapping.
 965		/// </remarks>
 966		/// <param name="text">Text</param>
 967		/// <param name="lineSpacing">Line spacing</param>
 968		/// <param name="maxTextSize">Maximum text size</param>
 969		/// <param name="textAlignment">Text alignment</param>
 970		/// <param name="isClippingOn">Is clipping on</param>
 971		/// <param name="isWordWrapOn">Is word wrap on</param>
 972		/// <param name="textlineWidths">Textline widths</param>
 973		/// <param name="maxTextlineWidth">Maximum textline width</param>
 974		/// <returns>
 975		/// The list of final text lines where the words contains only allowed
 976		/// characters.
 977		/// </returns>
 978		internal List<List<char>> GetTextLines(string text, float lineSpacing,
 979			Size maxTextSize, HorizontalAlignment textAlignment, bool isClippingOn,
 980			bool isWordWrapOn,
 981			out List<float> textlineWidths, out float maxTextlineWidth)
 982		{
 983			// At first initialize the 'out' values
 984			textlineWidths = new List<float>();
 985			maxTextlineWidth = 0.0f;
 986
 987			// Next the list which contains the final text lines with characters for
 988			// each. 
 989			List<List<char>> finalTextlines = new List<List<char>>();
 990
 991			// Without a valid text we immediately stop here
 992			if (String.IsNullOrEmpty(text))
 993			{
 994				return finalTextlines;
 995			} // if
 996
 997			// Compute now the total height which the font would need for every text
 998			// line
 999			float maxLineHeight = PixelLineHeight * lineSpacing;
1000
1001			// In the case text clipping is enabled we check now if the available the
1002			// height of given text area is smaller than a character of the font
1003			// because then no text line will fit into it and we can also stop here
1004			// directly
1005			if (isClippingOn &&
1006			    maxTextSize.Height < maxLineHeight)
1007			{
1008				return finalTextlines;
1009			} // if
1010
1011			// Parse the given text now and get the text lines of it with all
1012			// supported characters
1013			List<List<char>> textlines = ParseText(text);
1014			// and iterate them all now
1015			for (int lineIndex = 0; lineIndex < textlines.Count; lineIndex++)
1016			{
1017				List<char> textlineChars = textlines[lineIndex];
1018
1019				// Initialize here the list that will contain all characters of the
1020				// current text line
1021				List<char> textline = new List<char>();
1022				// and width (in pixels) of it which needs to start at 0
1023				float textlineWidth = 0.0f;
1024
1025				// For the word-wrap feature we also need a container which collects
1026				// the characters of the current word we are building
1027				List<char> word = new List<char>();
1028				// and its width (in pixels)
1029				float wordWidth = 0.0f;
1030
1031				// Last we still need the number of words we have already parsed to
1032				// "detect" if a single word exceeds the max. allowed text width and we
1033				// need to put it into the next text line separately
1034				int wordNumber = 0;
1035
1036				for (int charId = 0; charId < textlineChars.Count; charId++)
1037				{
1038					// Get the current char of the current text line
1039					char textChar = textlineChars[charId];
1040
1041					// and also figure out which is the next character which we need for
1042					// kerning, bearing and word- or line-ending detection
1043					int nextCharId = charId + 1;
1044					char nextChar = (nextCharId < textlineChars.Count)
1045					                	? textlineChars[nextCharId]
1046					                	: NoChar;
1047
1048					// Get now the width of the character which it will need to draw it
1049					// Note:
1050					// There is no need to check if a glyph exists because the method
1051					// "ParseText()" makes already sure that our text lines here contain
1052					// only supported characters (unsupported are converted in '?' or
1053					// skipped completely)
1054					Glyph glyph = glyphDictionary[textChar];
1055					int glyphWidth = glyph.GetDrawWidth(nextChar, Tracking);
1056
1057					// and look if it's the first one of the current text line
1058					bool isFirstChar = charId == 0;
1059					// or the last one
1060					bool isLastChar = nextCharId == textlineChars.Count;
1061
1062					// Unlike GetGlyphDrawInfos above we do not care about positioning,
1063					// just the width needed for this line (bearing reduces width).
1064					// But only take care about the bearing if we have here still a
1065					// character at the right of us
1066					if (nextChar != NoChar)
1067					{
1068						if (isFirstChar &&
1069						    textAlignment == HorizontalAlignment.Left)
1070						{
1071							glyphWidth -= MathHelper.Round(
1072								glyphDictionary[nextChar].LeftSideBearing);
1073						} // if
1074
1075						if (isLastChar &&
1076						    textAlignment == HorizontalAlignment.Right)
1077						{
1078							glyphWidth += MathHelper.Round(
1079								glyphDictionary[nextChar].RightSideBearing);
1080						} // if
1081					} // if
1082
1083					#region Word-Wrap based clipping
1084					// The word-wrap feature makes only sense with clipping otherwise
1085					// there would be no need to wrap text lines
1086					if (isClippingOn &&
1087					    isWordWrapOn)
1088					{
1089						bool isSpace = textChar == ' ';
1090						// "Flag" to know when a word ist fully parsed
1091						bool isWordFinished = false;
1092
1093						#region Word separating by a white space
1094						if (isSpace)
1095						{
1096							// Currently add the space character always at the end of the
1097							// word independend by the set text alignment
1098							// Decide if and where to add the space depending on the current
1099							// set text alignment
1100							word.Add(textChar);
1101							// We will add the width of the space later after checking if the
1102							// word has still fit in the currently available text line space
1103							// because it's only a whitespace and doesn't belongs to the word
1104							//wordLength += glyphWidth;
1105							isWordFinished = true;
1106						} // if
1107							#endregion
1108
1109							#region Last word of the current line has ended
1110						else if (isLastChar)
1111						{
1112							word.Add(textChar);
1113							wordWidth += glyphWidth;
1114							isWordFinished = true;
1115						} // else if
1116							#endregion
1117
1118							#region Building the current word
1119						else
1120						{
1121							word.Add(textChar);
1122							wordWidth += glyphWidth;
1123						} // else
1124						#endregion
1125
1126						// If a word is fully parsed now
1127						if (isWordFinished)
1128						{
1129							// then check if the current text line has still enough space to
1130							// add it
1131							if (textlineWidth + wordWidth <= maxTextSize.Width ||
1132							    // and ignore clipping if even the first word of the current
1133							    // text line wouldn't fit into it (to avoid that the word would
1134							    // "push" the rest of the text down and out of the text area
1135							    wordNumber == 0)
1136							{
1137								// Update the parsed result of the current text line now
1138								textline.AddRange(word);
1139								textlineWidth += wordWidth;
1140								// and also add the width of the space now that we have skipped
1141								// above
1142								if (isSpace)
1143								{
1144									textlineWidth += glyphWidth;
1145								} // if
1146
1147								wordNumber++;
1148							} // if
1149
1150								// In the case the next word would not fit anymore
1151							else
1152							{
1153								// we also grab the remaining text line part
1154								while (nextCharId < textlineChars.Count)
1155								{
1156									word.Add(textlineChars[nextCharId]);
1157									nextCharId++;
1158								} // while
1159								// and insert it as next text line (-> wrapping)
1160								textlines.Insert(lineIndex + 1, word);
1161
1162								// Last indicate to the character loop of the current text line
1163								// that we have "jumped" to the end
1164								charId = textlineChars.Count;
1165							} // else
1166
1167							// before setting the initial values again for the word building
1168							// so that parsing of the next text line can be started now
1169							word = new List<char>();
1170							wordWidth = 0.0f;
1171						} // if
1172					} // if
1173						#endregion
1174
1175						#region Character based clipping and non-clipping
1176						// If no clipping is required
1177					else if (isClippingOn == false ||
1178					         // or in the case that (horizontal) clipping is enabled and we
1179					         // check if the current character would still fit in the remaining
1180					         // space of the text line
1181					         // (-> vertical clipping will be handled more below)
1182					         (textlineWidth + glyphWidth) <= maxTextSize.Width)
1183					{
1184						// Add the character to the current text line characters
1185						textline.Add(textChar);
1186						// increase the width of current text line by it
1187						textlineWidth += glyphWidth;
1188					} // else if
1189
1190						// if the available space limit of the current text line is reached
1191					else
1192					{
1193						// we can abort the parsing of this line and go to the next one
1194						break;
1195					} // else
1196					#endregion
1197				} // for
1198
1199				// Every time a text line is parsed add it as final one
1200				finalTextlines.Add(textline);
1201				// inclusive the measured width of it
1202				textlineWidths.Add(textlineWidth);
1203
1204				// and additionally check if the current text line was the longest
1205				// that we had so far
1206				if (maxTextlineWidth < textlineWidth)
1207				{
1208					maxTextlineWidth = textlineWidth;
1209				} // if
1210
1211				#region Vertical text clipping check
1212				// Now check if there is still enough space for the next text line if
1213				// (vertical) clipping is wished
1214				if (isClippingOn)
1215				{
1216					float requiredHeightForNextLine =
1217						(finalTextlines.Count + 1) * maxLineHeight;
1218					// If no more text line would fit
1219					if (requiredHeightForNextLine > maxTextSize.Height)
1220					{
1221						// we can stop here completely and are done now with parsing of
1222						// the text lines
1223						break;
1224					} // if
1225
1226					// Note:
1227					// Horizontal clipping was already handled above
1228				} // if
1229				#endregion
1230
1231				// start the text line parsing at the beginning (again)
1232				textline = new List<char>();
1233				textlineWidth = 0.0f;
1234			} // for
1235
1236			return finalTextlines;
1237		}
1238		#endregion
1239
1240		#endregion
1241
1242		/// <summary>
1243		/// Tests
1244		/// </summary>
1245		internal class FontDataTests
1246		{
1247			#region Helpers
1248			/// <summary>
1249			/// Get font data which is used for all tests where the tests values are
1250			/// tweaked for.
1251			/// </summary>
1252			private static FontData GetTestFont()
1253			{
1254				// We use the Verdana 12 pt font
1255				return FontData.Get("Verdana", 12, FontStyle.AddOutline).
1256					ResolutionFonts[2];
1257			}
1258			#endregion
1259
1260			#region LoadDefaultFontData (LongRunning)
1261			/// <summary>
1262			/// Load default font data
1263			/// </summary>
1264			[Test, Category("LongRunning")]
1265			public void LoadDefaultFontData()
1266			{
1267				FontData fontData = GetTestFont();
1268
1269				Assert.NotNull(fontData.data);
1270				Assert.Equal(fontData.glyphDictionary.Count, 95);
1271				Assert.Equal(fontData.FontMapNames.Length, 1);
1272				Assert.Equal(fontData.FontMapNames[0], "Verdana_12_16");
1273			}
1274			#endregion
1275
1276			#region ParseTextLines (LongRunning)
1277			/// <summary>
1278			/// Parse text lines
1279			/// </summary>
1280			[Test, Category("LongRunning")]
1281			public void ParseTextLines()
1282			{
1283				FontData fontData = GetTestFont();
1284
1285				List<List<char>> lines = fontData.ParseText(
1286					"long loong looong loooong text");
1287				Assert.NotNull(lines);
1288				Assert.Equal(lines.Count, 1);
1289				Assert.Equal(lines[0].ToText(), "long loong looong loooong text");
1290
1291				lines = fontData.ParseText(
1292					"long loong looong loooong text" + Environment.NewLine +
1293					"Newline");
1294				Assert.NotNull(lines);
1295				Assert.Equal(lines.Count, 2);
1296				Assert.Equal(lines[0].ToText(), "long loong looong loooong text");
1297				Assert.Equal(lines[1].ToText(), "Newline");
1298			}
1299			#endregion
1300
1301			#region GetTextLines (LongRunning)
1302			/// <summary>
1303			/// Get text lines
1304			/// </summary>
1305			[Test, Category("LongRunning")]
1306			public void GetTextLines()
1307			{
1308				string multilineText =
1309					"This" + "\n" +
1310					"is a" + "\r\n" +
1311					"multiline" + "\r" +
1312					"text.";
1313
1314				FontData fontData = GetTestFont();
1315
1316				List<float> textlineWidths;
1317				float maxTextlineWidth;
1318				List<List<char>> textlines = fontData.GetTextLines(multilineText,
1319					1.0f, Size.Zero, HorizontalAlignment.Left, false, false,
1320					out textlineWidths, out maxTextlineWidth);
1321
1322				Assert.Equal(textlineWidths.Count, 4);
1323				Assert.Equal(textlines[0].ToText(), "This");
1324				Assert.Equal(textlines[1].ToText(), "is a");
1325				Assert.Equal(textlines[2].ToText(), "multiline");
1326				Assert.Equal(textlines[3].ToText(), "text.");
1327
1328				Assert.Equal(textlines.Count, textlineWidths.Count);
1329				Dictionary<char, Glyph> glyphs = fontData.glyphDictionary;
1330				// (8-0) + 8 + 3 + 6 = 25
1331				//Assert.Equal(textlineWidths[0],
1332				Assert.Equal(textlineWidths[0] + 1,
1333					MathHelper.Round(glyphs['T'].AdvanceWidth -
1334													 glyphs['T'].LeftSideBearing) +
1335					MathHelper.Round(glyphs['h'].AdvanceWidth) +
1336					MathHelper.Round(glyphs['i'].AdvanceWidth) +
1337					MathHelper.Round(glyphs['s'].AdvanceWidth));
1338				Assert.Equal(textlineWidths[0], 24);
1339
1340				// (3-1) + 6 + 4 + 7 = 19
1341				Assert.Equal(textlineWidths[1],
1342					MathHelper.Round(glyphs['i'].AdvanceWidth -
1343													 glyphs['i'].LeftSideBearing) +
1344					MathHelper.Round(glyphs['s'].AdvanceWidth) +
1345					MathHelper.Round(glyphs[' '].AdvanceWidth) +
1346					MathHelper.Round(glyphs['a'].AdvanceWidth));
1347				Assert.Equal(textlineWidths[1], 19);
1348
1349				// (12-1) + 8 + 3 + 5 + 3 + 3 + 3 + 8 + 7 = 51
1350				Assert.Equal(textlineWidths[2],
1351					MathHelper.Round(glyphs['m'].AdvanceWidth -
1352													 glyphs['m'].LeftSideBearing) +
1353					MathHelper.Round(glyphs['u'].AdvanceWidth) +
1354					MathHelper.Round(glyphs['l'].AdvanceWidth) +
1355					MathHelper.Round(glyphs['t'].AdvanceWidth) +
1356					MathHelper.Round(glyphs['i'].AdvanceWidth) +
1357					MathHelper.Round(glyphs['l'].AdvanceWidth) +
1358					MathHelper.Round(glyphs['i'].AdvanceWidth) +
1359					MathHelper.Round(glyphs['n'].AdvanceWidth) +
1360					MathHelper.Round(glyphs['e'].AdvanceWidth));
1361				Assert.Equal(textlineWidths[2], 51);
1362
1363				// (4 - 0) + 7 + 7 + 5 + 4 = 27
1364				Assert.Equal(textlineWidths[3],
1365					MathHelper.Round(glyphs['t'].AdvanceWidth -
1366													 glyphs['t'].LeftSideBearing) +
1367					MathHelper.Round(glyphs['e'].AdvanceWidth) +
1368					MathHelper.Round(glyphs['x'].AdvanceWidth) +
1369					MathHelper.Round(glyphs['t'].AdvanceWidth) +
1370					MathHelper.Round(glyphs['.'].AdvanceWidth));
1371				Assert.Equal(textlineWidths[3], 27);
1372
1373				Assert.Equal(maxTextlineWidth, 51);
1374			}
1375			#endregion
1376
1377			#region GetTextlineWrapped (LongRunning)
1378			/// <summary>
1379			/// Get text line wrapped
1380			/// </summary>
1381			/// Caution: For this unit test are the following modules required:
1382			/// - Graphic
1383			/// - Platforms.IWindow
1384			/// - ContentManager.Client
1385			[Test, Category("LongRunning")]
1386			public void GetTextlineWrapped()
1387			{
1388				string text =
1389					"long loong looong loooong text" + Environment.NewLine + "Newline";
1390
1391				FontData fontData = GetTestFont();
1392				Size textAreaSize = new Size(138, fontData.PixelLineHeight * 3);
1393
1394				List<float> textlineWidths;
1395				float maxTextlineWidth;
1396				List<List<char>> lines = fontData.GetTextLines(text, 1.0f,
1397					textAreaSize, HorizontalAlignment.Left, true, true,
1398					out textlineWidths, out maxTextlineWidth);
1399
1400				Assert.NotNull(lines);
1401				Assert.Equal(lines.Count, 3);
1402				Assert.Equal(lines[0].ToText(), "long loong looong ");
1403				Assert.Equal(lines[1].ToText(), "loooong text");
1404				Assert.Equal(lines[2].ToText(), "Newline");
1405			}
1406			#endregion
1407
1408			#region GetGlyphDrawInfos
1409			/// <summary>
1410			/// Get glyph draw infos
1411			/// </summary>
1412			[Test, Category("LongRunning")]
1413			public void GetGlyphDrawInfos()
1414			{
1415				FontData testFont = FontData.Default;
1416
1417				GlyphDrawInfo[] drawGlyphs = testFont.GetGlyphDrawInfos("", 1.0f,
1418					HorizontalAlignment.Left);
1419				Assert.Equal(0, drawGlyphs.Length);
1420
1421				drawGlyphs = testFont.GetGlyphDrawInfos("\n", 1.0f,
1422					HorizontalAlignment.Left);
1423				Assert.Equal(drawGlyphs.Length, 1);
1424				Rectangle newLineArea = drawGlyphs[0].DrawArea;
1425				Assert.Equal(newLineArea.X, 0);
1426				Assert.Equal(newLineArea.Y, testFont.PixelLineHeight);
1427				Assert.Equal(newLineArea.Width, 0);
1428				Assert.Equal(newLineArea.Height, testFont.PixelLineHeight);
1429
1430				drawGlyphs = testFont.GetGlyphDrawInfos("A", 1.0f,
1431					HorizontalAlignment.Left);
1432				Assert.Equal(drawGlyphs.Length, 1);
1433				GlyphDrawInfo glyphA = drawGlyphs[0];
1434				Assert.Equal(glyphA.UV, Rectangle.BuildUVRectangle(
1435					new Rectangle(67, 32, 9, 16), new Size(128)));
1436				Assert.Equal(glyphA.FontMapId, 0);
1437	

Large files files are truncated, but you can click here to view the full file