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; }