/// <summary> /// Function to build out the font data. /// </summary> /// <param name="graphics">The graphics context to use.</param> /// <param name="fontInfo">The information used to generate the font.</param> /// <param name="externalFonts">The external fonts provided by an application.</param> /// <returns>A new <see cref="GdiFontData"/> object.</returns> public static GdiFontData GetFontData(System.Drawing.Graphics graphics, IGorgonFontInfo fontInfo, IEnumerable <PrivateFontCollection> externalFonts) { var result = new GdiFontData(); CharacterRange[] range = { new CharacterRange(0, 1) }; System.Drawing.FontStyle style = System.Drawing.FontStyle.Regular; switch (fontInfo.FontStyle) { case FontStyle.Bold: style = System.Drawing.FontStyle.Bold; break; case FontStyle.Italics: style = System.Drawing.FontStyle.Italic; break; case FontStyle.BoldItalics: style = System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic; break; } FontFamily fontFamily = (externalFonts != null ? (externalFonts.SelectMany(item => item.Families).Concat(FontFamily.Families)) : FontFamily.Families) .FirstOrDefault(item => string.Equals(fontInfo.FontFamilyName, item.Name, StringComparison.InvariantCultureIgnoreCase)); // If we cannot locate the font family by name, then fall back. if (fontFamily == null) { fontFamily = FontFamily.GenericSerif; } // Scale the font appropriately. if (fontInfo.FontHeightMode == FontHeightMode.Points) { // Convert the internal font to pixel size. result.Font = new Font(fontFamily, (fontInfo.Size * graphics.DpiY) / 72.0f, style, GraphicsUnit.Pixel); } else { result.Font = new Font(fontFamily, fontInfo.Size, style, GraphicsUnit.Pixel); } result.FontHeight = result.Font.GetHeight(graphics); result.StringFormat = new StringFormat(StringFormat.GenericTypographic) { FormatFlags = StringFormatFlags.NoFontFallback | StringFormatFlags.MeasureTrailingSpaces, Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Near }; result.StringFormat.SetMeasurableCharacterRanges(range); // Create a separate drawing format because some glyphs are being clipped when they have overhang // on the left boundary. result.DrawFormat = new StringFormat(StringFormat.GenericDefault) { FormatFlags = StringFormatFlags.NoFontFallback | StringFormatFlags.MeasureTrailingSpaces, Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Near }; result.DrawFormat.SetMeasurableCharacterRanges(range); return(result); }
/// <summary> /// Initializes a new instance of the <see cref="GlyphDraw"/> class. /// </summary> /// <param name="fontInfo">The font information used to create the font.</param> /// <param name="fontData">The font data for the glyphs.</param> public GlyphDraw(IGorgonFontInfo fontInfo, GdiFontData fontData) { _fontInfo = fontInfo; _fontData = fontData; }
/// <summary> /// Function to create or update the font. /// </summary> /// <param name="externalFontCollections">The external font collections from the application.</param> /// <remarks> /// <para> /// This is used to generate a new set of font textures, and essentially "create" the font object. /// </para> /// <para> /// This method will clear all the glyphs and textures in the font and rebuild the font with the specified parameters. This means that any custom glyphs, texture mapping, and/or kerning will be lost. /// Users must find a way to remember and restore any custom font info when updating. /// </para> /// <para> /// Internal textures used by the glyph will be destroyed. However, if there's a user defined texture or glyph using a user defined texture, then it will not be destroyed and clean up will be the /// responsibility of the user. /// </para> /// </remarks> /// <exception cref="GorgonException">Thrown when the texture size in the settings exceeds that of the capabilities of the feature level. /// <para>-or-</para> /// <para>Thrown when the font family name is <b>null</b> or Empty.</para> /// </exception> internal void GenerateFont(IEnumerable <System.Drawing.Text.PrivateFontCollection> externalFontCollections) { Bitmap setupBitmap = null; System.Drawing.Graphics graphics = null; GdiFontData fontData = default; Dictionary <Bitmap, IEnumerable <GlyphInfo> > groupedByBitmap = null; try { // Temporary bitmap used to gather a graphics context. setupBitmap = new Bitmap(2, 2, PixelFormat.Format32bppArgb); // Get a context for the rasterizing surface. graphics = System.Drawing.Graphics.FromImage(setupBitmap); graphics.PageUnit = GraphicsUnit.Pixel; // Build up the information using a GDI+ font. fontData = GdiFontData.GetFontData(graphics, Info, externalFontCollections); // Remove control characters and anything below a space. List <char> availableCharacters = GetAvailableCharacters(); // Set up the code to draw glyphs to bitmaps. var glyphDraw = new GlyphDraw(Info, fontData); // Gather the boundaries for each glyph character. Dictionary <char, GlyphRegions> glyphBounds = glyphDraw.GetGlyphRegions(availableCharacters, HasOutline); // Because the dictionary above remaps characters (if they don't have a glyph or their rects are empty), // we'll need to drop these from our main list of characters. availableCharacters.RemoveAll(item => !glyphBounds.ContainsKey(item)); // Get kerning and glyph advancement information. Dictionary <char, ABC> abcAdvances = GetKerningInformation(graphics, fontData.Font, availableCharacters); // Put the glyphs on packed bitmaps. Dictionary <char, GlyphInfo> glyphBitmaps = glyphDraw.DrawToPackedBitmaps(availableCharacters, glyphBounds, HasOutline); groupedByBitmap = (from glyphBitmap in glyphBitmaps where glyphBitmap.Value.GlyphBitmap != null group glyphBitmap.Value by glyphBitmap.Value.GlyphBitmap).ToDictionary(k => k.Key, v => v.Select(item => item)); // Generate textures from the bitmaps. // We will pack each bitmap into a single arrayed texture up to the maximum number of array indices allowed. // Once that limit is reached a new texture will be used. This should help performance a little, although it // is much better to resize the texture so that it has a single array index and single texture. GenerateTextures(groupedByBitmap); // Finally, generate our glyphs. GenerateGlyphs(glyphBitmaps, abcAdvances); FontHeight = fontData.FontHeight; Ascent = fontData.Ascent; Descent = fontData.Descent; LineHeight = fontData.LineHeight; } finally { graphics?.Dispose(); setupBitmap?.Dispose(); fontData?.Dispose(); if (groupedByBitmap != null) { foreach (Bitmap glyphBitmap in groupedByBitmap.Keys) { glyphBitmap.Dispose(); } } } }