From 72ee0fd837761959f190ef29bb3adb23c0d0d505 Mon Sep 17 00:00:00 2001 From: "SND\\eagleeyestudios_cp" Date: Fri, 19 Oct 2012 21:59:35 +0000 Subject: [PATCH] Content Pipeline: - implemented FontDescriptionProcessor - began implementing FontDescriptionImporter --- .../ANX.Framework.Content.Pipeline.csproj | 1 + .../Graphics/FontDescriptionStyle.cs | 1 + .../Importer/FontDescriptionImporter.cs | 35 +++ .../Processors/FontDescriptionProcessor.cs | 284 +++++++++++++++++- .../Processors/SpriteFontContent.cs | 8 +- 5 files changed, 322 insertions(+), 7 deletions(-) create mode 100644 ANX.Framework.Content.Pipeline/Importer/FontDescriptionImporter.cs diff --git a/ANX.Framework.Content.Pipeline/ANX.Framework.Content.Pipeline.csproj b/ANX.Framework.Content.Pipeline/ANX.Framework.Content.Pipeline.csproj index ba698dd6..92ad209e 100644 --- a/ANX.Framework.Content.Pipeline/ANX.Framework.Content.Pipeline.csproj +++ b/ANX.Framework.Content.Pipeline/ANX.Framework.Content.Pipeline.csproj @@ -106,6 +106,7 @@ + diff --git a/ANX.Framework.Content.Pipeline/Graphics/FontDescriptionStyle.cs b/ANX.Framework.Content.Pipeline/Graphics/FontDescriptionStyle.cs index f6428c43..e344ed4a 100644 --- a/ANX.Framework.Content.Pipeline/Graphics/FontDescriptionStyle.cs +++ b/ANX.Framework.Content.Pipeline/Graphics/FontDescriptionStyle.cs @@ -12,6 +12,7 @@ using System.Text; namespace ANX.Framework.Content.Pipeline.Graphics { + [Flags] public enum FontDescriptionStyle { Bold, diff --git a/ANX.Framework.Content.Pipeline/Importer/FontDescriptionImporter.cs b/ANX.Framework.Content.Pipeline/Importer/FontDescriptionImporter.cs new file mode 100644 index 00000000..8c1c99cb --- /dev/null +++ b/ANX.Framework.Content.Pipeline/Importer/FontDescriptionImporter.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Xml; +using ANX.Framework.Content.Pipeline.Graphics; +using ANX.Framework.NonXNA.Development; + +namespace ANX.Framework.Content.Pipeline.Importer +{ + /// Provides methods for reading .spritefont files for use in the Content Pipeline. + [PercentageComplete(20)] + [Developer("SilentWarrior/Eagle Eye Studios")] + [TestState(TestStateAttribute.TestState.Untested)] + [ContentImporter("Spritefont", ".spritefont", DefaultProcessor = "FontDescriptionProcessor")] + public class FontDescriptionImporter : ContentImporter + { + /// Called by the Framework when importing a .spritefont file to be used as a game asset. This is the method called by the Framework when an asset is to be imported into an object that can be recognized by the Content Pipeline. + /// Name of a game asset file. + /// Contains information for importing a game asset, such as a logger interface. + public override FontDescription Import(string filename, ContentImporterContext context) + { + FontDescription fontDescription = null; + using (XmlReader xmlReader = XmlReader.Create(filename)) + { + fontDescription = Deserialize(xmlReader, filename); + } + fontDescription.Identity = new ContentIdentity(new FileInfo(filename).FullName, "FontDescriptionImporter"); + return fontDescription; + } + + private FontDescription Deserialize(XmlReader reader, string filename) + { + throw new NotImplementedException(); + } + } +} diff --git a/ANX.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs b/ANX.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs index ffb77765..009ac19d 100644 --- a/ANX.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs +++ b/ANX.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs @@ -1,9 +1,15 @@ #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 System.Text; using ANX.Framework.Content.Pipeline.Graphics; +using ANX.Framework.NonXNA.Development; #endregion @@ -13,13 +19,285 @@ using ANX.Framework.Content.Pipeline.Graphics; namespace ANX.Framework.Content.Pipeline.Processors { + [PercentageComplete(90)] + [Developer("SilentWarrior/Eagle Eye Studios")] + [TestState(TestStateAttribute.TestState.Untested)] //due to missing importer [ContentProcessor] public class FontDescriptionProcessor : ContentProcessor { - public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context) + { + 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 = 8; + + 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.Format32bppArgb)) + { + // Arrage 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++) + { + spriteFontContent.Cropping.Add(new Rectangle(xPositions[i], yPositions[i], bitmaps[i].Width, + bitmaps[i].Height)); + graphics.DrawImage(bitmaps[i], xPositions[i], + yPositions[i]); + } + + graphics.Flush(); + } + spriteFontContent.Texture = ConvertBitmap(bitmap); + spriteFontContent.CharacterMap = input.Characters.ToList(); + spriteFontContent.DefaultCharacter = input.DefaultCharacter; + spriteFontContent.Glyphs = CreateOutputGlyphs(); + for (int i = 0; i < input.Characters.Count; i++) + { + Vector3 value = spriteFontContent.Kerning[i]; + if (!input.UseKerning) + { + value.Y = spriteFontContent.Cropping[i].Width; + } + if (!input.UseKerning) + { + value.X = 0f; + value.Z = 0f; + } + spriteFontContent.Kerning[i] = value; + } + spriteFontContent.LineSpacing = (int) Math.Ceiling(font.GetHeight()); + spriteFontContent.Spacing = input.Spacing; + } + } + finally + { + // Clean up temporary objects. + foreach (Bitmap bitmap in bitmaps) + bitmap.Dispose(); + } + + 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(System.Drawing.Color.Transparent); + + 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.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); + var destColor = new Color(); + + for (int x = bitmap.Width; x > 0; x--) + { + for (int y = bitmap.Height; y > 0; y--) + { + System.Drawing.Color sourceColor = bitmap.GetPixel(x, y); + + destColor.R = sourceColor.R; + destColor.G = sourceColor.G; + destColor.B = sourceColor.B; + destColor.A = sourceColor.A; + + bitmapContent.SetPixel(x, y, destColor); + } + } + + 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 } -} +} \ No newline at end of file diff --git a/ANX.Framework.Content.Pipeline/Processors/SpriteFontContent.cs b/ANX.Framework.Content.Pipeline/Processors/SpriteFontContent.cs index 5717c871..aad30d09 100644 --- a/ANX.Framework.Content.Pipeline/Processors/SpriteFontContent.cs +++ b/ANX.Framework.Content.Pipeline/Processors/SpriteFontContent.cs @@ -11,16 +11,16 @@ namespace ANX.Framework.Content.Pipeline.Processors public sealed class SpriteFontContent { [ContentSerializer(ElementName = "Texture", AllowNull = false)] - internal Texture2DContent Texture { get; private set; } + internal Texture2DContent Texture { get; set; } [ContentSerializer(ElementName = "Glyphs", AllowNull = false)] - internal List Glyphs { get; private set; } + internal List Glyphs { get; set; } [ContentSerializer(ElementName = "Cropping", AllowNull = false)] - internal List Cropping { get; private set; } + internal List Cropping { get; set; } [ContentSerializer(ElementName = "CharacterMap", AllowNull = false)] - internal List CharacterMap { get; private set; } + internal List CharacterMap { get; set; } [ContentSerializer(ElementName = "LineSpacing", AllowNull = false)] internal int LineSpacing { get; set; }