#region Using Statements using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.Globalization; using System.Linq; using ANX.Framework.Content.Pipeline.Graphics; using ANX.Framework.NonXNA.Development; #endregion // This file is part of the ANX.Framework created by the // "ANX.Framework developer group" and released under the Ms-PL license. // For details see: http://anxframework.codeplex.com/license namespace ANX.Framework.Content.Pipeline.Processors { [PercentageComplete(95)] [Developer("SilentWarrior/Eagle Eye Studios")] [TestState(TestStateAttribute.TestState.InProgress)] [ContentProcessor(DisplayName = "FontDescription Processor - ANX Framework")] public class FontDescriptionProcessor : ContentProcessor { //Default GDI transparent is a white transparent, but in ANX we use a black transparent. private static readonly System.Drawing.Color TransparentBlack = System.Drawing.Color.FromArgb(0); public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context) { context.Logger.LogMessage("Processing of FontDescription started."); if (input == null) { throw new ArgumentNullException("input"); } var list = new List(input.Characters); if (input.DefaultCharacter.HasValue && !input.Characters.Contains(input.DefaultCharacter.Value)) { list.Add(input.DefaultCharacter.Value); } list.Sort(); var spriteFontContent = new SpriteFontContent(); // Build up a list of all the glyphs to be output. var bitmaps = new List(); var xPositions = new List(); var yPositions = new List(); var globalBitmap = new Bitmap(1, 1, PixelFormat.Format32bppArgb); System.Drawing.Graphics globalGraphics = System.Drawing.Graphics.FromImage(globalBitmap); var font = new Font(input.FontName, input.Size, XnaFontStyleToGdiFontStyle(input.Style)); try { const int padding = 4; int width = padding; int height = padding; int lineWidth = padding; int lineHeight = padding; int count = 0; // Rasterize each character in turn, // and add it to the output list. foreach (char ch in input.Characters) { Bitmap bitmap = RasterizeCharacter(globalGraphics, ch, true, font); bitmaps.Add(bitmap); xPositions.Add(lineWidth); yPositions.Add(height); lineWidth += bitmap.Width + padding; lineHeight = Math.Max(lineHeight, bitmap.Height + padding); // Output 16 glyphs per line, then wrap to the next line. if ((++count == 16) || (ch == input.Characters.Count - 1)) { width = Math.Max(width, lineWidth); height += lineHeight; lineWidth = padding; lineHeight = padding; count = 0; } } using (var bitmap = new Bitmap(width, height, PixelFormat.Format32bppPArgb)) { // Arrange all the glyphs onto a single larger bitmap. using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap)) { graphics.Clear(System.Drawing.Color.Magenta); graphics.CompositingMode = CompositingMode.SourceCopy; for (int i = 0; i < bitmaps.Count; i++) { //cropping defines the dimensions of a single character spriteFontContent.Cropping.Add(new Rectangle(0, 0, bitmaps[i].Width - 1, //we need to subtract one unit of height and width to suppress nasty rendering artifacts. bitmaps[i].Height - 1)); //Glyphs defines the section and dimensions where the character is located on the texture spriteFontContent.Glyphs.Add(new Rectangle(xPositions[i] + 1, yPositions[i] + 1, bitmaps[i].Width -1, bitmaps[i].Height-1)); graphics.DrawImage(bitmaps[i], xPositions[i], yPositions[i]); } graphics.Flush(); } spriteFontContent.Texture = ConvertBitmap(bitmap); spriteFontContent.CharacterMap = input.Characters.ToList(); spriteFontContent.DefaultCharacter = input.DefaultCharacter; spriteFontContent.Kerning = new List(); for (var i = 0; i < input.Characters.Count; i++) { var value = Vector3.Zero; if (input.UseKerning) { throw new NotImplementedException("Kerning is not implemented!"); //TODO: Implement! } else { value.Y = spriteFontContent.Cropping[i].Width; value.X = 0f; value.Z = 0f; } spriteFontContent.Kerning.Add(value); } spriteFontContent.LineSpacing = (int) Math.Ceiling(font.GetHeight()); spriteFontContent.Spacing = input.Spacing; } } finally { // Clean up temporary objects. foreach (Bitmap bitmap in bitmaps) bitmap.Dispose(); } context.Logger.LogMessage("Processing of FontDescription finished."); return spriteFontContent; } #region RasterizeCharacter /// /// Helper for rendering out a single font character /// into a System.Drawing bitmap. /// private static Bitmap RasterizeCharacter(System.Drawing.Graphics globalGraphics, char ch, bool antiAliasing, Font font) { string text = ch.ToString(CultureInfo.InvariantCulture); SizeF size = globalGraphics.MeasureString(text, font); var width = (int) Math.Ceiling(size.Width); var height = (int) Math.Ceiling(size.Height); var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap)) { graphics.TextRenderingHint = antiAliasing ? TextRenderingHint.AntiAliasGridFit : TextRenderingHint.SingleBitPerPixelGridFit; graphics.Clear(TransparentBlack); using (Brush brush = new SolidBrush(System.Drawing.Color.White)) using (var format = new StringFormat()) { format.Alignment = StringAlignment.Near; format.LineAlignment = StringAlignment.Near; graphics.DrawString(text, font, brush, 0, 0, format); } graphics.Flush(); } return CropCharacter(bitmap); } #endregion #region CropCharacter /// /// Helper for cropping ununsed space from the sides of a bitmap. /// private static Bitmap CropCharacter(Bitmap bitmap) { int cropLeft = 0; int cropRight = bitmap.Width - 1; // Remove unused space from the left. while ((cropLeft < cropRight) && (BitmapEmpty(bitmap, cropLeft))) cropLeft++; // Remove unused space from the right. while ((cropRight > cropLeft) && (BitmapEmpty(bitmap, cropRight))) cropRight--; // Don't crop if that would reduce the glyph down to nothing at all! if (cropLeft == cropRight) return bitmap; // Add some padding back in. cropLeft = Math.Max(cropLeft - 1, 0); cropRight = Math.Min(cropRight + 1, bitmap.Width - 1); int width = cropRight - cropLeft + 1; // Crop the glyph. var croppedBitmap = new Bitmap(width, bitmap.Height, bitmap.PixelFormat); using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(croppedBitmap)) { graphics.Clear(TransparentBlack); graphics.CompositingMode = CompositingMode.SourceCopy; graphics.DrawImage(bitmap, 0, 0, new System.Drawing.Rectangle(cropLeft, 0, width, bitmap.Height), GraphicsUnit.Pixel); graphics.Flush(); } bitmap.Dispose(); return croppedBitmap; } #endregion #region CreateOutputGlyphs private static List CreateOutputGlyphs() { throw new NotImplementedException(); } #endregion #region ConvertBitmap private Texture2DContent ConvertBitmap(Bitmap bitmap) { var bitmapContent = new PixelBitmapContent(bitmap.Width, bitmap.Height); for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { System.Drawing.Color sourceColor = bitmap.GetPixel(x, y); bitmapContent.SetPixel(x, y, Color.FromNonPremultiplied(sourceColor.R, sourceColor.G, sourceColor.B, sourceColor.A)); } } var textureContent = new Texture2DContent(); textureContent.Faces[0] = new MipmapChain(bitmapContent); return textureContent; } #endregion #region FontDescriptionStyleConversion private static FontStyle XnaFontStyleToGdiFontStyle(FontDescriptionStyle fontStyle) { var fontStyle2 = FontStyle.Regular; if ((fontStyle & FontDescriptionStyle.Bold) == FontDescriptionStyle.Bold) { fontStyle2 |= FontStyle.Bold; } if ((fontStyle & FontDescriptionStyle.Italic) == FontDescriptionStyle.Italic) { fontStyle2 |= FontStyle.Italic; } return fontStyle2; } #endregion #region BitmapEmpty /// /// Helper for testing whether a column of a bitmap is entirely empty. /// private static bool BitmapEmpty(Bitmap bitmap, int x) { for (int y = 0; y < bitmap.Height; y++) { if (bitmap.GetPixel(x, y).A != 0) return false; } return true; } #endregion } }