/Rendering/Basics/Fonts/Font.cs
C# | 1334 lines | 702 code | 120 blank | 512 comment | 59 complexity | cb2eaa4acbbacbc3d2ff30c85a1c138a MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.IO;
- using Delta.ContentSystem.Rendering;
- using Delta.ContentSystem.Rendering.Helpers;
- using Delta.Engine;
- using Delta.Engine.Dynamic;
- using Delta.Engine.SettingsNodes;
- using Delta.Graphics.Basics;
- using Delta.Rendering.Basics.Drawing;
- using Delta.Rendering.Basics.Materials;
- using Delta.Rendering.Enums;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Datatypes.Advanced;
- using Delta.Utilities.Graphics;
- using Delta.Utilities.Helpers;
-
- namespace Delta.Rendering.Basics.Fonts
- {
- /// <summary>
- /// That class contains all the logic to render a font <see cref="FontData"/>
- /// and will try to request the specified font data (in the constructor). As
- /// an extra feature the font will automatically choose one of the available
- /// font sizes depending on the current resolution to support a more sharp
- /// down- or up-scaling of the originally wished font size. Most fonts you
- /// can load from the content system will already provide 4 resolution based
- /// fonts fine tuned to work in all possible resolutions (see constructor).
- /// <para />
- /// Note: Performance is heavily optimized for render speed. The main idea is
- /// to use cached glyphs to avoid recalculating them for each text. This also
- /// means all of the properties of this class are read-only. To change
- /// anything (including character distances, line spacing, the render color,
- /// etc.) you need to create a new Font instance. This way the Geometry
- /// can be cached and only calculated once for a text, which then can be
- /// rendered many times (even with different positioning, rotation, etc.).
- /// In release mode a font rendering can be done up to 1.5 million times/sec
- /// (when swapbuffer, clear, geometry rendering is turned off for measuring.)
- /// </summary>
- public class Font : IDisposable, ISaveLoadBinary
- {
- #region TextParameters Struct
- /// <summary>
- /// Helper for the second input value of glyphCache. These values should
- /// not change much, but we still need to check them for every Get.
- /// </summary>
- private struct TextParameters
- {
- #region Width (Public)
- /// <summary>
- /// The available width (in Quadratic Space) to draw the related text.
- /// </summary>
- public float Width;
- #endregion
-
- #region Height (Public)
- /// <summary>
- /// The available height (in Quadratic Space) to draw the related text.
- /// </summary>
- public float Height;
- #endregion
-
- #region HorizontalAlignment (Public)
- /// <summary>
- /// The horizontal alignment of the related text.
- /// </summary>
- public HorizontalAlignment HorizontalAlignment;
- #endregion
-
- #region LineSpacing (Public)
- /// <summary>
- /// The set spacing for the text lines of the related text (if there are
- /// some lines). This value is FontData.LineHeight * Font.LineHeight.
- /// </summary>
- public float LineSpacing;
- #endregion
- }
- #endregion
-
- #region TextDrawInfo Struct
- /// <summary>
- /// Helper struct for the cache result of glyphCache.
- /// </summary>
- private struct TextDrawInfo
- {
- #region GlyphInfos (Public)
- /// <summary>
- /// That array of glyph info represents the drawing area's of each
- /// character in relation to the text. In other words that are NOT the
- /// final areas where the text will be drawn on the screen !
- /// </summary>
- public GlyphDrawInfo[] GlyphInfos;
- #endregion
-
- #region TextSize (Public)
- /// <summary>
- /// The measured size of the glyphs (or in other words the text) that
- /// will be needed to show it fully on the screen.
- /// </summary>
- public Size TextSize;
- #endregion
-
- #region Material (Public)
- /// <summary>
- /// Material for rendering the Geometry.
- /// </summary>
- public Material2DColored Material;
- #endregion
-
- #region Geometry (Public)
- /// <summary>
- /// And finally the pre-calculated geometry to render out text quickly.
- /// Usually text does not change, thus we can calculate the geometry
- /// once and render it many times.
- /// </summary>
- public Geometry Geometry;
- #endregion
- }
- #endregion
-
- #region Constants
- /// <summary>
- /// The current version of the implementation of this font class.
- /// </summary>
- private const int VersionNumber = 1;
-
- /// <summary>
- /// The default fallback font name is "DefaultFont". Used in Font.Default!
- /// </summary>
- private const string DefaultFontName = "DefaultFont";
- #endregion
-
- #region Default (Static)
- /// <summary>
- /// Get the default fallback font, that is usually used for info and
- /// debug texts, the FPS counter, profiler and other stuff that does not
- /// have a valid font set (or does not want to handle own fonts).
- /// All text is displayed
- /// UI has its own fallback and is much more sophisticated because
- /// we usually need different font sizes depending on the resolution!
- /// </summary>
- public static Font Default
- {
- get
- {
- if (defaultFont == null)
- {
- defaultFont = new Font(DefaultFontName);
- } // if
-
- return defaultFont;
- } // get
- }
- #endregion
-
- #region DrawTopLeftInformation (Static)
- /// <summary>
- /// Draws information text on the top left corner of the screen via the
- /// default font. Should only be used to display profiling and debug
- /// information.
- /// </summary>
- /// <param name="text">Text to display</param>
- public static void DrawTopLeftInformation(string text)
- {
- if (informationFont == null)
- {
- informationFont = new Font(Default, HorizontalAlignment.Left,
- VerticalAlignment.Top);
- } // if
-
- informationFont.Draw(text, ScreenSpace.DrawArea);
- }
- #endregion
-
- #region FamilyName (Public)
- /// <summary>
- /// Get the family name of the font (e.g. "Verdana", "Arial", etc.)
- /// </summary>
- public string FamilyName
- {
- get
- {
- return activeFontData.FamilyName;
- }
- }
- #endregion
-
- #region CurrentFontSize (Public)
- /// <summary>
- /// Get the current selected font size based on the current resolution.
- /// <para/>
- /// Note: This will only change if different font data values were
- /// specified in the constructor, else the size will always be the same
- /// for the current font instance.
- /// </summary>
- public float CurrentFontSize
- {
- get
- {
- return activeFontData.SizeInPoints;
- }
- }
- #endregion
-
- #region LineSpacing (Public)
- /// <summary>
- /// The scale factor which finally defines the total line height based on
- /// the font content size (default is '1.0'). Must be set in constructor
- /// and is usually set from the font content data. Can be adjusted in the
- /// constructor (e.g. 1.5) and will be multiplied with fontData.LineHeight
- /// for rendering.
- /// <para />
- /// LineHeight = 1.0: Normal font height for the characters.
- /// <para />
- /// LineHeight = 1.5: Bigger font height for the characters with an extra
- /// space of 50% between lines.
- /// </summary>
- public float LineSpacingMultiplier
- {
- get
- {
- return lineSpacingMultiplier;
- } // get
- }
- #endregion
-
- #region CharacterSpacing (Public)
- /// <summary>
- /// Similar to LineSpacing you can also define how far each font
- /// character should be apart. The default value is '1.0' and it already
- /// uses the Tracking defined in each of the FontDatas used by this font.
- /// 2.0 means the same distance a space character holds is added between
- /// each letter, but you can use whatever value you like (1.1, 5.0, 0.8).
- /// </summary>
- public float CharacterSpacingMultiplier
- {
- get
- {
- return characterSpacingMultiplier;
- }
- }
- #endregion
-
- #region LineHeight (Public)
- /// <summary>
- /// The total height of a text line (in quadratic space) based on the set
- /// font height (in pixel) and the line spacing multiplier.
- /// <para />Note: The value is depending on the current resolution.
- /// </summary>
- public float LineHeight
- {
- get;
- private set;
- }
- #endregion
-
- #region Color (Public)
- /// <summary>
- /// Get the color of the displayed text (default is 'White'). Can only be
- /// set in the constructor like most of the important font render states.
- /// </summary>
- public Color Color
- {
- get
- {
- return renderColor;
- }
- }
- #endregion
-
- #region DrawLayer (Public)
- /// <summary>
- /// Get the render layer where a text will be drawn. Can only be set in the
- /// constructor.
- /// </summary>
- public RenderLayer DrawLayer
- {
- get
- {
- return drawLayer;
- } // get
- }
- #endregion
-
- #region IsWordWrappingOn (Public)
- /// <summary>
- /// Is word wrapping on (default is 'false').
- /// <para />
- /// Note: This feature is still not fully supported yet.
- /// </summary>
- public bool IsWordWrappingOn
- {
- get;
- private set;
- }
- #endregion
-
- #region HorizontalAlignment (Public)
- /// <summary>
- /// Horizontal text alignment (default is 'Centered').
- /// </summary>
- public HorizontalAlignment HorizontalAlignment
- {
- get;
- private set;
- }
- #endregion
-
- #region VerticalTextAlignment (Public)
- /// <summary>
- /// Vertical text alignment (default is 'Centered').
- /// </summary>
- public VerticalAlignment VerticalAlignment
- {
- get;
- private set;
- }
- #endregion
-
- #region Private
-
- #region defaultFont (Private)
- /// <summary>
- /// Default font to render text centered.
- /// </summary>
- private static Font defaultFont;
- #endregion
-
- #region informationFont (Private)
- /// <summary>
- /// Information font for the DrawTopLeftInformation method.
- /// </summary>
- private static Font informationFont;
- #endregion
-
- #region renderColor (Private)
- /// <summary>
- /// Text render color
- /// </summary>
- private Color renderColor;
- #endregion
-
- #region lineSpacing (Private)
- /// <summary>
- /// Line spacing multiplier
- /// </summary>
- private float lineSpacingMultiplier;
- #endregion
-
- #region characterSpacing (Private)
- /// <summary>
- /// Character spacing
- /// </summary>
- private float characterSpacingMultiplier;
- #endregion
-
- #region textDrawLayer (Private)
- /// <summary>
- /// Render layer for this font
- /// </summary>
- private RenderLayer drawLayer;
- #endregion
-
- #region fontDatas (Private)
- /// <summary>
- /// The available FontData's for the resolutions 480x320, 800x480,
- /// 1024x768 and 1920x1080, started with the smallest resolution. The
- /// correct one will be selected by the 'DetermineBestFont()'.
- /// <para/>
- /// Note: If only one entry is set, that "auto-selecting" feature is
- /// disabled. If more that one is set, every entry is valid (is made sure
- /// in the constructor).
- /// </summary>
- private FontData[] fontDatas;
- #endregion
-
- #region activeFontData (Private)
- /// <summary>
- /// The reference to the current selected 'FontData' (from the 'fontDatas'
- /// list and based on the current screen resolution) which is used for
- /// drawing the text on the screen.
- /// </summary>
- private FontData activeFontData;
- #endregion
-
- #region fontMaps (Private)
- /// <summary>
- /// Font maps, will be updated as the activeFontData changes (each font can
- /// have multiple font maps). Often just has one material that we use for
- /// rendering. Each Glyph links to this list and has the UV coordinates for
- /// rendering that glyph.
- /// </summary>
- private Material2DColored[] fontMaps;
- #endregion
-
- #region glyphCache (Private)
- /// <summary>
- /// The internal cache of drawing information for each text that needs to
- /// be handled in a "Draw(...)" or in the <see cref="Measure(String)"/>
- /// method by the current font instance.
- /// <para/>
- /// <b>Note:</b> The cache is initialized in the constructor. It is also
- /// cleared every time we change something important (resolution changed).
- /// </summary>
- private readonly Cache<string, TextParameters, TextDrawInfo> glyphCache;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create a new font for drawing a text from a font content name.
- /// Note: You can also search for fonts with the FontData.Get method,
- /// but you should use this method normally because it lets you setup
- /// and change font settings without having to change the code.
- /// </summary>
- /// <param name="setFontContentName">Set font content name</param>
- public Font(string setFontContentName)
- : this(FontData.Get(setFontContentName))
- {
- }
-
- /// <summary>
- /// Create a new font for drawing a text from FontData, which can be loaded
- /// from content via FontData.Get or created customly. Please note that
- /// each FontData can have children entries for additional font sizes (four
- /// children for the 4 default resolutions, see DetermineBestFont, which
- /// switches fonts at 480x320, 800x480, 1024x768 and 1920x1080).
- /// </summary>
- /// <param name="setFontData">
- /// Set font data, which can contain 4 children for the other resolutions
- /// (always in the same order). If there are no children just one font data
- /// will be set to one font size.
- /// </param>
- /// <param name="setHorizontalAlignment">
- /// Set horizontal alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setVerticalAlignment">
- /// Set vertical alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setWordWrapping">Word wrapping mode (usually off).</param>
- /// <param name="setLineSpacingMultiplier">
- /// Set line spacing multiplier (default to 1.0). Multiplied with the
- /// FontData.LineHeight.
- /// </param>
- /// <param name="setCharacterSpacingMultiplier">
- /// Set character spacing multiplier (defaults to 1.0). Multiplied with
- /// FontData character render distances.
- /// </param>
- /// <param name="setDrawLayer">Set draw layer to use for rendering.</param>
- public Font(FontData setFontData,
- HorizontalAlignment setHorizontalAlignment =
- HorizontalAlignment.Centered,
- VerticalAlignment setVerticalAlignment = VerticalAlignment.Centered,
- bool setWordWrapping = false,
- float setLineSpacingMultiplier = 1.0f,
- float setCharacterSpacingMultiplier = 1.0f,
- RenderLayer setDrawLayer = RenderLayer.Text)
- : this(setFontData, Color.White, setHorizontalAlignment,
- setVerticalAlignment, setWordWrapping, setLineSpacingMultiplier,
- setCharacterSpacingMultiplier, setDrawLayer)
- {
- }
-
- /// <summary>
- /// Create a new font for drawing a text from FontData, which can be loaded
- /// from content via FontData.Get or created customly. Please note that
- /// each FontData can have children entries for additional font sizes (four
- /// children for the 4 default resolutions, see DetermineBestFont, which
- /// switches fonts at 480x320, 800x480, 1024x768 and 1920x1080).
- /// </summary>
- /// <param name="setFontData">
- /// Set font data, which can contain 3 children for the other resolutions
- /// (always in the same order). If there are no children all 4 fontDatas
- /// will be set to the same font size.
- /// </param>
- /// <param name="newFontColor">New font text render color to use.</param>
- /// <param name="setHorizontalAlignment">
- /// Set horizontal alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setVerticalAlignment">
- /// Set vertical alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setWordWrapping">Word wrapping mode (usually off).</param>
- /// <param name="setLineSpacingMultiplier">
- /// Set line spacing multiplier (default to 1.0). Multiplied with the
- /// FontData.LineHeight.
- /// </param>
- /// <param name="setCharacterSpacingMultiplier">
- /// Set character spacing multiplier (defaults to 1.0). Multiplied with
- /// FontData character render distances.
- /// </param>
- /// <param name="setDrawLayer">Set draw layer to use for rendering.</param>
- public Font(FontData setFontData, Color newFontColor,
- HorizontalAlignment setHorizontalAlignment =
- HorizontalAlignment.Centered,
- VerticalAlignment setVerticalAlignment = VerticalAlignment.Centered,
- bool setWordWrapping = false,
- float setLineSpacingMultiplier = 1.0f,
- float setCharacterSpacingMultiplier = 1.0f,
- RenderLayer setDrawLayer = RenderLayer.Text)
- : this()
- {
- #region Validation
- // We always need a valid FontData
- if (setFontData == null)
- {
- Log.Warning(
- "You always have to specify a valid 'FontData' to make " +
- "a font work, will use now the 'DefaultFont' content instead.");
- setFontData = FontData.Get("DefaultFont");
- }
-
- // If we have more data because the font should be resolution-based
- // then we need to check if we have the correct amount of entries
- if (setFontData.ResolutionFonts != null)
- {
- fontDatas = setFontData.ResolutionFonts;
- // If we have less entries than allowed
- if (setFontData.ResolutionFonts.Length < 4)
- {
- // Then we fill it up with the default value (later) inclusive
- // keeping the already set entries
- fontDatas = new FontData[4];
- for (int num = 0; num < setFontData.ResolutionFonts.Length; num++)
- {
- fontDatas[num] = setFontData.ResolutionFonts[num];
- } // for
- } // if
-
- // If we have more than allowed
- else if (fontDatas.Length > 4)
- {
- Log.Warning(
- "The font data has not too many ResolutionFonts, we only " +
- "support exactly 4 font data resolutions, but we got " +
- setFontData.ResolutionFonts.Length);
- // Then we "clamp" them
- fontDatas = new FontData[4];
- for (int num = 0; num < fontDatas.Length; num++)
- {
- fontDatas[num] = setFontData.ResolutionFonts[num];
- } // for
- } // else if
-
- // Finally make sure that every entry is valid
- for (int index = 0; index < fontDatas.Length; index++)
- {
- if (fontDatas[index] == null)
- {
- // Copy from last, or if this is the first, use the default
- fontDatas[index] =
- index == 0
- ? FontData.Default
- : fontDatas[index - 1];
- } // if
- } // for
- } // if
- else
- {
- // Otherwise we have no resolution based fonts, just set one font!
- fontDatas = new[]
- {
- setFontData
- };
- }
- #endregion
-
- // Also set the render state to the given (mostly default) values
- renderColor = newFontColor;
- drawLayer = setDrawLayer;
- lineSpacingMultiplier = setLineSpacingMultiplier;
- characterSpacingMultiplier = setCharacterSpacingMultiplier;
- HorizontalAlignment = setHorizontalAlignment;
- VerticalAlignment = setVerticalAlignment;
- IsWordWrappingOn = setWordWrapping;
-
- // Finally determine the best matching font for the current resolution
- // (including updating/syncing our properties). This also sets the
- // important activeFontData and fontMaps values.
- DetermineBestFont();
- }
-
- /// <summary>
- /// Creates a new font based on an existing font, required for cloning
- /// fonts and allows us to change the font render parameters like Color,
- /// DrawLayer and Alignment.
- /// </summary>
- /// <param name="fontToCopy">Font to copy all data from.</param>
- /// <param name="newFontColor">New font text render color to use.</param>
- /// <param name="setHorizontalAlignment">
- /// Set horizontal alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setVerticalAlignment">
- /// Set vertical alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setWordWrapping">Word wrapping mode (usually off).</param>
- /// <param name="setLineSpacingMultiplier">
- /// Set line spacing multiplier (default to 1.0). Multiplied with the
- /// FontData.LineHeight.
- /// </param>
- /// <param name="setCharacterSpacingMultiplier">
- /// Set character spacing multiplier (defaults to 1.0). Multiplied with
- /// FontData character render distances.
- /// </param>
- /// <param name="setDrawLayer">Set draw layer to use for rendering.</param>
- public Font(Font fontToCopy, Color newFontColor,
- HorizontalAlignment setHorizontalAlignment =
- HorizontalAlignment.Centered,
- VerticalAlignment setVerticalAlignment = VerticalAlignment.Centered,
- bool setWordWrapping = false,
- float setLineSpacingMultiplier = 1.0f,
- float setCharacterSpacingMultiplier = 1.0f,
- RenderLayer setDrawLayer = RenderLayer.Text)
- : this()
- {
- // Copy all data over
- fontDatas = fontToCopy.fontDatas;
- fontMaps = fontToCopy.fontMaps;
- activeFontData = fontToCopy.activeFontData;
-
- // Set new values
- renderColor = newFontColor;
- HorizontalAlignment = setHorizontalAlignment;
- VerticalAlignment = setVerticalAlignment;
- drawLayer = setDrawLayer;
- lineSpacingMultiplier = setLineSpacingMultiplier;
- characterSpacingMultiplier = setCharacterSpacingMultiplier;
- IsWordWrappingOn = setWordWrapping;
-
- // Finally determine the best matching font for the current resolution
- // (including updating/syncing our properties). This also sets the
- // important activeFontData and fontMaps values.
- DetermineBestFont();
- }
-
- /// <summary>
- /// Creates a new font based on an existing font, required for cloning
- /// fonts and allows us to change the font Alignment parameters.
- /// </summary>
- /// <param name="fontToCopy">Font to copy all data from.</param>
- /// <param name="setHorizontalAlignment">
- /// Set horizontal alignment mode for font rendering, defaults to centered.
- /// </param>
- /// <param name="setVerticalAlignment">
- /// Set vertical alignment mode for font rendering, defaults to centered.
- /// </param>
- public Font(Font fontToCopy,
- HorizontalAlignment setHorizontalAlignment =
- HorizontalAlignment.Centered,
- VerticalAlignment setVerticalAlignment = VerticalAlignment.Centered)
- : this()
- {
- // Copy all data over
- fontDatas = fontToCopy.fontDatas;
- fontMaps = fontToCopy.fontMaps;
- activeFontData = fontToCopy.activeFontData;
- renderColor = fontToCopy.renderColor;
- drawLayer = fontToCopy.drawLayer;
- lineSpacingMultiplier = fontToCopy.lineSpacingMultiplier;
- characterSpacingMultiplier = fontToCopy.characterSpacingMultiplier;
- IsWordWrappingOn = fontToCopy.IsWordWrappingOn;
-
- // Set new values
- HorizontalAlignment = setHorizontalAlignment;
- VerticalAlignment = setVerticalAlignment;
-
- // Finally determine the best matching font for the current resolution
- // (including updating/syncing our properties). This also sets the
- // important activeFontData and fontMaps values.
- DetermineBestFont();
- }
-
- /// <summary>
- /// Creates an empty font, required for the <see cref="Factory"/> if the
- /// font will be loaded there by the <see cref="ISaveLoadBinary"/>
- /// interface (see StreamHelper.LoadWithLength). Note: All values need to
- /// be filled in via the Load method.
- /// </summary>
- private Font()
- {
- // Make to sure that the glyph cache is initialized.
- // Note: Usually it's not allowed to use instance members like the
- // 'activeFontData', but here it will be reset in 'DetermineBestFont()'
- // together with the glyph cache which allows us to ignore that rule.
- glyphCache = new Cache<string, TextParameters, TextDrawInfo>(
- delegate(string inputText, TextParameters param)
- {
- // The result of our glyph info request from the currently active
- // font based on the current line-spacing
- GlyphDrawInfo[] glyphInfos;
-
- // Clipping is only set if a useful clipping size is specified
- bool isClippingOn =
- param.Width != 0.0f &&
- param.Height != 0.0f;
- if (isClippingOn)
- {
- // In the case we have clipping we need to convert the available
- // drawing size convert to Pixel Space first, because that's the
- // space the FontData uses.
- Size availableSizeForText = ScreenSpace.ToPixelSpace(
- new Size(param.Width, param.Height));
-
- // Request glyph info with clipping and optionally word wrapping
- glyphInfos = activeFontData.GetGlyphDrawInfos(inputText,
- param.LineSpacing, param.HorizontalAlignment,
- true, IsWordWrappingOn, ref availableSizeForText);
- } // if
- else
- {
- // If we have no clipping we can just request glyph info directly
- glyphInfos = activeFontData.GetGlyphDrawInfos(inputText,
- param.LineSpacing, param.HorizontalAlignment);
- } // else
-
- for (int index = 0; index < glyphInfos.Length; index++)
- {
- // Convert now the font (pixel space) into our quadratic space but
- // we don't use the "ScreenSpace.ToQuadraticSpace()" method (which we
- // usually would use for that case, because we have here
- // "relative space" that have to start always at (0,0) and the
- // "Screen" methods would returns us the current screen pixel
- // (which are usually not quadratic) "mapped" centered into the
- // Quadratic Space
- // e.g. for Position (0,0) of a 1024x768 resolution we would get
- // the quadratic position (0, 0.125), but we want (0, 0),
- // so we call the method that don't adds the
- // "offset for centering"
- glyphInfos[index].DrawArea =
- ScreenSpace.ToQuadraticSpaceTopLeft(glyphInfos[index].DrawArea);
- } // for
-
- // Finally return the result now
- return new TextDrawInfo
- {
- GlyphInfos = glyphInfos,
- TextSize = Measure(glyphInfos),
- Material =
- fontMaps.Length > 0
- ? fontMaps[0]
- : null,
- Geometry = CreateGeometryFromGlyph(glyphInfos, inputText),
- };
- });
-
- // and we will determine the best font (including glyph cache clearing)
- // every time the resolution changes
- Application.Window.ResizeEvent += DetermineBestFont;
-
- // All other values will be either initialized by the other constructors
- // or set via the "Load" method which is initiated by "Factory.Create".
- }
- #endregion
-
- #region IDisposable Members
- /// <summary>
- /// Dispose
- /// </summary>
- public void Dispose()
- {
- // We need do remove this event, for GC to be able to release this object
- Application.Window.ResizeEvent -= DetermineBestFont;
-
- // The created geometry needs to be disposed again!
- foreach (TextDrawInfo drawInfo in glyphCache.GetAllEntries())
- {
- drawInfo.Geometry.Dispose();
- }
- glyphCache.Clear();
-
- // Also "unregister" from the current 'FontData.ContentChanged' event
- if (activeFontData != null)
- {
- activeFontData.ContentChanged = null;
- activeFontData = null;
- } // if
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Loads all data of the object again which were previously saved.
- /// </summary>
- /// <param name="dataReader">The data reader.</param>
- public void Load(BinaryReader dataReader)
- {
- // We currently only support our version, if more versions are added,
- // we need to do different loading code depending on the version here.
- int version = dataReader.ReadInt32();
- switch (version)
- {
- // Version 1
- case VersionNumber:
- // Now load all previously saved data of the font
- int fontDataCount = dataReader.ReadInt32();
- fontDatas = new FontData[fontDataCount];
- for (int index = 0; index < fontDataCount; index++)
- {
- // Reconstruct the FontData that is required
- string fontName = dataReader.ReadString();
- int fontSize = dataReader.ReadInt32();
- FontStyle fontStyle = (FontStyle)dataReader.ReadByte();
- // for the FontData
- fontDatas[index] = FontData.Get(fontName, fontSize, fontStyle);
- } // for
-
- // and call the DetermineBestFont() the select the current
- // active font based on the current screen resolution
- DetermineBestFont();
-
- // Finally set the specified draw options
- lineSpacingMultiplier = dataReader.ReadSingle();
- renderColor = new Color(dataReader);
- IsWordWrappingOn = dataReader.ReadBoolean();
- characterSpacingMultiplier = dataReader.ReadSingle();
- HorizontalAlignment = (HorizontalAlignment)dataReader.ReadByte();
- VerticalAlignment = (VerticalAlignment)dataReader.ReadByte();
- break;
-
- default:
- Log.InvalidVersionWarning(GetType().Name, version, VersionNumber);
- break;
- } // switch
- }
-
- /// <summary>
- /// Saves all necessary data of the object to a byte array.
- /// </summary>
- /// <param name="dataWriter">The data writer.</param>
- public void Save(BinaryWriter dataWriter)
- {
- // First we write the current version number of the class data format
- dataWriter.Write(VersionNumber);
-
- // Now we save all internal FontData's (that we need to support
- // several resolutions)
- dataWriter.Write(fontDatas.Length);
- foreach (FontData data in fontDatas)
- {
- dataWriter.Write(data.FamilyName);
- dataWriter.Write(data.SizeInPoints);
- dataWriter.Write((byte)data.Style);
- } // foreach
-
- // And finally the specified draw options
- dataWriter.Write(LineSpacingMultiplier);
- renderColor.Save(dataWriter);
- dataWriter.Write(IsWordWrappingOn);
- dataWriter.Write(CharacterSpacingMultiplier);
- dataWriter.Write((byte)HorizontalAlignment);
- dataWriter.Write((byte)VerticalAlignment);
- }
- #endregion
-
- #region Draw (Public)
- /// <summary>
- /// Draws a text without any rotation and (scrolling) offset at the
- /// specified area.
- /// </summary>
- /// <param name="text">The text that should be drawn.</param>
- /// <param name="drawArea">
- /// The area (in Quadratic Space) where the text should be drawn.
- /// </param>
- public void Draw(string text, Rectangle drawArea)
- {
- Draw(text, drawArea, 0.0f, Point.Zero);
- }
-
- /// <summary>
- /// Draws a text with the specified rotation (and scrolling offset) at the
- /// given area whereby the area defines the available space for the
- /// clipping too. Note: Rotation and scroll offset is not supported yet.
- /// </summary>
- /// <param name="text">The text that should be drawn.</param>
- /// <param name="drawArea">
- /// The area (in Quadratic Space) where the text should be drawn.
- /// </param>
- /// <param name="rotation">The rotation the text should have.</param>
- /// <param name="scrollOffset">
- /// The offset where the text should begin inside the text area which is
- /// necessary for scrolled text elements that needs a partial clipping.
- /// </param>
- public void Draw(string text, Rectangle drawArea, float rotation,
- Point scrollOffset)
- {
- #region Validation
- if (String.IsNullOrEmpty(text))
- {
- Log.Warning("You shouldn't call Font.Draw without any text!");
- return;
- } // if
- #endregion
-
- #region Grab glyphs from cache
- // If glyph cache has grown too big, just kill it and restart
- if (glyphCache.Count > 100)
- {
- foreach (TextDrawInfo drawInfo in glyphCache.GetAllEntries())
- {
- // The created geometry needs to be disposed again!
- drawInfo.Geometry.Dispose();
- }
-
- glyphCache.Clear();
- }
-
- TextDrawInfo cache = glyphCache.Get(text,
- new TextParameters
- {
- Width = drawArea.Width,
- Height = drawArea.Height,
- LineSpacing = lineSpacingMultiplier,
- HorizontalAlignment = HorizontalAlignment
- });
-
- // Sanity check for the case that an error has occurred due loading the
- // font data
- if (cache.GlyphInfos.Length == 0)
- {
- // In that case a warning was already logged and there is no need to
- // log again
- return;
- } // if
- #endregion
-
- #region Compute alignment positions
- // Initialize the text start position for rendering
- Point startPos = drawArea.TopLeft;
- //unused: Point rotatedStartPos = drawArea.Center;
-
- // Now we need to know the size of the text that we want to show
- Size textSize = cache.TextSize;
- // then use that information to align it horizontally
- switch (HorizontalAlignment)
- {
- case HorizontalAlignment.Left:
- // Already initialized
- //unused: rotatedStartPos.X = drawArea.Left;
- break;
-
- case HorizontalAlignment.Centered:
- startPos.X = drawArea.Center.X - textSize.WidthHalf;
- //unused: rotatedStartPos.X = drawArea.Center.X - textSize.WidthHalf;
- break;
-
- case HorizontalAlignment.Right:
- startPos.X = drawArea.Right - textSize.Width;
- break;
-
- default:
- // Log and reuse the 'Left' mode
- Log.Warning(
- "The set HorizontalAlignment mode '" + HorizontalAlignment +
- "' isn't supported (yet).");
- break;
- } // switch
-
- // and align it vertically
- switch (VerticalAlignment)
- {
- case VerticalAlignment.Top:
- // Already initialized
- break;
-
- case VerticalAlignment.Centered:
- startPos.Y = drawArea.Center.Y - textSize.HeightHalf;
- break;
-
- case VerticalAlignment.Bottom:
- startPos.Y = drawArea.Bottom - textSize.Height;
- break;
-
- default:
- // Log
- Log.Warning(
- "The set VerticalTextAlignment mode '" + VerticalAlignment +
- "' isn't supported (yet).");
- // and reuse the 'Left' mode
- break;
- } // switch
-
- // finally step we still have to make sure that computed Quadratic Space
- // is pixel accurate else we can would get a blurry text
- startPos = ScreenSpace.MakePositionPixelAccurate(startPos);
-
- if (Settings.Debug.IsDrawDebugInfoModeOn(ProfilingMode.Text))
- {
- Rect.DrawOutline(drawArea, Color.Red, rotation);
- } // if
- #endregion
-
- // In the case we have a rotation
- if (rotation != 0.0f)
- {
- // Apply this aspect ratio, because we want the quadratic space
- // coordinates to work and not 0-1 for top-bottom, top is usually ~0.15
- Vector aspectRatioScale = Vector.One;
- if (ScreenSpace.AspectRatio < 1.0f)
- {
- aspectRatioScale.X = 1.0f / ScreenSpace.AspectRatio;
- } // if
- else if (ScreenSpace.AspectRatio > 1.0f)
- {
- aspectRatioScale.Y = ScreenSpace.AspectRatio;
- } // else if
-
- // Note: Slow way to calculate the full font position matrix, see
- // ScreenSpace.ViewProjection2D for details on the aspect ratio stuff.
- // This are 4 matrix multiplies, which are fairly slow, see the code
- // below which does it all in one swoop because most matrices are easy.
- //Matrix fontPositionMatrix =
- // Matrix.CreateScale(new Vector(2, -2, 1)) *
- // Matrix.CreateTranslation(-1, 1, 0) *
- // Matrix.CreateRotationZ(rotation) *
-
- // Directly calculate the matrix :)
- float cosValue = MathHelper.Cos(rotation);
- float sinValue = MathHelper.Sin(rotation);
- Matrix fontPositionMatrix = new Matrix(
- // Everything needs to be scaled by the aspect ratio!
- aspectRatioScale.X * 2 * cosValue,
- aspectRatioScale.Y * 2 * sinValue, 0f, 0f,
- aspectRatioScale.X * 2 * sinValue,
- aspectRatioScale.Y * (-2) * cosValue, 0f, 0f,
- 0f, 0f, 1f, 0f,
- // Calculate X offset directly including the rotation.
- aspectRatioScale.X *
- (1.0f * (-1) * cosValue +
- -1.0f * sinValue),
- // Same with Y offset (see CreateRotationZ for details)
- aspectRatioScale.Y *
- (1.0f * (-1) * sinValue +
- -1.0f * (-1) * cosValue),
- 0.0f, 1.0f);
-
- // Helper to quickly move the matrix by the specified 2d amount
- fontPositionMatrix.Move2D(startPos);
- cache.Material.Draw(cache.Geometry, ref fontPositionMatrix);
- }
- else
- {
- // We need to build a matrix for positioning the font.
- // This time there is no need for any complicated matrix calculations.
- Matrix fontPositionMatrix = ScreenSpace.InternalViewProjection2D;
- // Helper to quickly move the matrix by the specified 2d amount
- fontPositionMatrix.Move2D(startPos);
- cache.Material.Draw(cache.Geometry, ref fontPositionMatrix);
- } // else
- }
- #endregion
-
- #region Measure (Public)
- /// <summary>
- /// Measures the given text and returns the size that it would need for
- /// drawing it in the current resolution of the screen.
- /// </summary>
- /// <param name="text">Text</param>
- /// <returns>
- /// Size
- /// </returns>
- public Size Measure(string text)
- {
- #region Validation
- if (String.IsNullOrEmpty(text))
- {
- Log.Info("It doesn't make sense to measure an empty text");
- return Size.Zero;
- } // if
- #endregion
-
- return glyphCache.Get(text,
- new TextParameters
- {
- Width = 0,
- Height = 0,
- LineSpacing = lineSpacingMultiplier,
- HorizontalAlignment = HorizontalAlignment
- }).TextSize;
- }
- #endregion
-
- #region Methods (Private)
-
- #region Measure
- /// <summary>
- /// Measures the given text and returns the size that it would need for
- /// drawing it in the current resolution of the screen.
- /// </summary>
- /// <param name="glyphDrawInfos">Glyph draw info</param>
- /// <returns>Size</returns>
- private static Size Measure(GlyphDrawInfo[] glyphDrawInfos)
- {
- // Nothing to measure, then abort.
- if (glyphDrawInfos.Length == 0)
- {
- return Size.Zero;
- }
-
- // Init the total measured size with the size of the first character
- Size textSize = glyphDrawInfos[0].DrawArea.Size;
-
- // then we iterate through all other characters to build the
- // "bounding box" of the text block represented by the glyph info
- // Note:
- // The bounding box will here a little bigger than the real text size
- // because a draw area of a glyph is as wide as in the font map to be
- // pixel-accurate at rendering time, but we would need the so-called
- // "AdvanceWidth" and would have to subtract the "RighSideBearing" of it
- // for the most-right character
- for (int index = 1; index < glyphDrawInfos.Length; index++)
- {
- // Horizontal dimension
- if (glyphDrawInfos[index].DrawArea.Right >
- textSize.Width)
- {
- textSize.Width = glyphDrawInfos[index].DrawArea.Right;
- } // if
-
- // Vertical dimension
- if (glyphDrawInfos[index].DrawArea.Bottom >
- textSize.Height)
- {
- textSize.Height = glyphDrawInfos[index].DrawArea.Bottom;
- } // if
- } // for
-
- return textSize;
- }
- #endregion
-
- #region CreateGeometryFromGlyph
- /// <summary>
- /// Helper method to create geometry per font request, called by the
- /// glyphCache delegate inside InitializeGlyphCache.
- /// </summary>
- /// <param name="glyphs">Glyphs with all the letters</param>
- /// <param name="text">Text, just for the geometry name</param>
- /// <returns>Geometry that will be used for drawing.</returns>
- private Geometry CreateGeometryFromGlyph(GlyphDrawInfo[] glyphs,
- string text)
- {
- // Now create the geometry for the text we want to draw
- GeometryData textGeomData = new GeometryData("<Font_" + text + ">",
- // Where we need 4 vertices for a glyph quad
- glyphs.Length * 4, VertexFormat.Position2DColorTextured,
- // and 6 indices to describe 2 triangles by the 4 vertices
- // Note: Using indices is ~7% faster instead of pure vertex rendering
- glyphs.Length * 6, true, false);
-
- // For easier geometry creation, we still cache here the elements of
- // the vertex declaration
- VertexElement positionElement = textGeomData.Format.Elements[0];
- VertexElement colorElement = textGeomData.Format.Elements[1];
- VertexElement uvElement = textGeomData.Format.Elements[2];
- // before we iterate through all glyphs and the quads for it
- for (int glyphId = 0; glyphId < glyphs.Length; glyphId++)
- {
- //GlyphDrawInfo glyphInfo = glyphs[glyphId];
-
- // Grab the relative draw area (which starts at (0,0))
- Rectangle glyphArea = glyphs[glyphId].DrawArea;
- // Cache the UV coordinates too
- Rectangle glyphUV = glyphs[glyphId].UV;
-
- // We store now the information of the quad into the geometry
- // (Counter-Clockwise). Please note that the character and line
- // distances, all the UVs and glyph positions and also the render color
- // are baked into the geometry for the best render speed.
-
- // TopLeft
- positionElement.SaveData(textGeomData.writer, glyphArea.TopLeft);
- colorElement.SaveData(textGeomData.writer, renderColor);
- uvElement.SaveData(textGeomData.writer, glyphUV.TopLeft);
- // BottomLeft
- positionElement.SaveData(textGeomData.writer, glyphArea.BottomLeft);
- colorElement.SaveData(textGeomData.writer, renderColor);
- uvElement.SaveData(textGeomData.writer, glyphUV.BottomLeft);
- // BottomRight
- positionElement.SaveData(textGeomData.writer, glyphArea.BottomRight);
- colorElement.SaveData(textGeomData.writer, renderColor);
- uvElement.SaveData(textGeomData.writer, glyphUV.BottomRight);
- // TopRight
- positionElement.SaveData(textGeomData.writer, glyphArea.TopRight);
- colorElement.SaveData(textGeomData.writer, renderColor);
- uvElement.SaveData(textGeomData.writer, glyphUV.TopRight);
-
- // and last still define the triangle render order of the quad
- textGeomData.Indices[glyphId * 6] = (ushort)(glyphId * 4);
- textGeomData.Indices[glyphId * 6 + 1] = (ushort)(glyphId * 4 + 1);
- textGeomData.Indices[glyphId * 6 + 2] = (ushort)(glyphId * 4 + 2);
- textGeomData.Indices[glyphId * 6 + 3] = (ushort)(glyphId * 4);
- textGeomData.Indices[glyphId * 6 + 4] = (ushort)(glyphId * 4 + 2);
- textGeomData.Indices[glyphId * 6 + 5] = (ushort)(glyphId * 4 + 3);
- } // for
-
- // Finally return the geometry, which can be rendered with the FontMaps
- return Geometry.Create(textGeomData);
- }
- #endregion
-
- #region DetermineBestFont
- /// <summary>
- /// Determines the best matching font depending on the current screen
- /// resolution and clears the current glyph cache too. Note: This supports
- /// both landscape (4:3, 16:9, etc.) resolutions, but also portrait mode
- /// </summary>
- private void DetermineBestFont()
- {
- if (fontDatas == null)
- {
- if (activeFontData == null)
- {
- Log.Warning(
- "Unable to determine best font, which is needed to set " +
- "the active font. Error: No content font data is available!");
- }
- return;
- }
-
- #region Select the best matching font
- // We are only interested in the resolution height, because only that
- // important for the current font-scaling
- int resHeight = Application.Window.ViewportPixelHeight;
-
- FontData bestFontData;
- if (fontDatas.Length > 1)
- {
- // 480x320
- if (resHeight <= 320)
- {
- bestFontData = fontDatas[3];
- } // if
-
- // 800x480, 800x600, 800x480, 854x480 320x480
- else if (resHeight <= 480)
- {
- bestFontData = fontDatas[2];
- } // else if
-
- // 1024x768, 1280x720, 960x640
- else if (resHeight <= 856)//640)//720//768)
- {
- bestFontData = fontDatas[1];
- } // else if
-
- // everything bigger like 1920x1080, 1600x1200, 640x960, etc.
- else
- {
- bestFontData = fontDatas[0];
- } // else if
- } // if
- else
- {
- // Just use the only entry that we have. Note: That call is only
- // triggered by the constructor and will happen once.
- bestFontData = fontDatas[0];
- } // else
- #endregion
-
- // Always update the new line height (because the selected FontData could
- // be same as before but the resolution has changed which means that
- // line height value (in Quadratic Space) has changed at least)
- ComputeLineHeight(bestFontData);
-
- // At least clear the cache in any case because it needs to be updated
- // for the new resolution. This is important because fonts are pixel
- // based. Normal geometry does not matter and scales nicely usually.
- glyphCache.Clear();
-
- // If the font data is still the same then there is nothing to do
- if (activeFontData == bestFontData)
- {
- return;
- } // if
-
- // "Delete" the notify from the 'ContentChanged' event again
- // (if the call doesn't come from constructor itself)
- if (activeFontData != null)
- {
- activeFontData.ContentChanged = null;
- } // if
-
- // Before we can set the reference to the new one
- activeFontData = bestFontData;
-
- // rebuild the font maps from the new data
- UpdateFontMaps();
-
- // And finally register at the 'ContentChanged' event in case any
- // font image gets automatically reloaded.
- activeFontData.ContentChanged = UpdateFontMaps;
- }
- #endregion
-
- #region ComputeLineHeight
- /// <summary>
- /// Computes the current line height in quadratic space, needs to be
- /// updated once the active font instance or resolution changes.
- /// </summary>
- /// <param name="fontData">
- /// The <see cref="FontData"/> to compute height from.
- /// </param>
- private void ComputeLineHeight(FontData fontData)
- {
- // At first we need to get the font data that is currently selected
- LineHeight = ScreenSpace.ToQuadraticSpace(
- // to get the font line height in pixel for computing the line height
- // for rendering a text on the screen in the current resolution
- new Size(0, fontData.PixelLineHeight * LineSpacingMultiplier)).Height;
- }
- #endregion
-
- #region UpdateFontMaps
- /// <summary>
- /// Update font maps
- /// </summary>
- private void UpdateFontMaps()
- {
- fontMaps = new Material2DColored[activeFontData.FontMapNames.Length];
- for (int index = 0; index < activeFontData.FontMapNames.Length; index++)
- {
- fontMaps[index] = new Material2DColored(
- activeFontData.FontMapNames[index])
- {
- DrawLayer = DrawLayer,
- };
- } // for
- }
- #endregion
-
- #endregion
- }
- }
-