Browse Source

Allow Negative Spacing In Bitmap Fonts (#1079)

* Read spacing values as `sbyte` to allow for negative spacing

While the angle code bitmap font generator tool does not allow negative spacing values, its possible to set negative values when using tools like Hiero, or if the user manually edits the .fnt file.

Resolves #1074

* Use LineSpacing and LetterSpacing when rendering bitmap fonts

Resolves #1078
Christopher Whitley 2 months ago
parent
commit
395d3bf8a2

+ 2 - 0
source/MonoGame.Extended.Content.Pipeline/BitmapFonts/BitmapFontWriter.cs

@@ -19,6 +19,8 @@ namespace MonoGame.Extended.Content.Pipeline.BitmapFonts
             writer.Write(fontFile.FontName);
             writer.Write(fontFile.Info.FontSize);
             writer.Write(fontFile.Common.LineHeight);
+            writer.Write(fontFile.Info.SpacingHoriz);
+            writer.Write(fontFile.Info.SpacingVert);
             writer.Write(fontFile.Characters.Count);
 
             foreach (var c in fontFile.Characters)

+ 18 - 5
source/MonoGame.Extended/BitmapFonts/BitmapFont.cs

@@ -22,17 +22,23 @@ public sealed class BitmapFont
     public int LineHeight { get; }
 
     public int LetterSpacing { get; set; }
+    public int LineSpacing {get; set;}
 
     public bool UseKernings { get; set; } = true;
 
     public BitmapFont(string face, int size, int lineHeight, IEnumerable<BitmapFontCharacter> characters)
+        :this(face, size, lineHeight, 0, 0, characters) { }
+
+    public BitmapFont(string face, int size, int lineHeight, int letterSpacing, int lineSpacing, IEnumerable<BitmapFontCharacter> characters)
     {
         Face = face;
         Size = size;
         LineHeight = lineHeight;
+        LetterSpacing = letterSpacing;
+        LineSpacing = lineSpacing;
         _characters = new Dictionary<int, BitmapFontCharacter>();
 
-        foreach (BitmapFontCharacter character in characters)
+        foreach(BitmapFontCharacter character in characters)
         {
             _characters.Add(character.Character, character);
         }
@@ -103,7 +109,7 @@ public sealed class BitmapFont
             }
 
             if (glyph.CharacterID == '\n')
-                rectangle.Height += LineHeight;
+                rectangle.Height += LineHeight + LineSpacing;
         }
 
         return rectangle;
@@ -213,7 +219,7 @@ public sealed class BitmapFont
             if (character != '\n')
                 return true;
 
-            _positionDelta.Y += _font.LineHeight;
+            _positionDelta.Y += _font.LineHeight + _font.LineSpacing;
             _positionDelta.X = 0;
             _previousGlyph = null;
 
@@ -331,7 +337,7 @@ public sealed class BitmapFont
             if (character != '\n')
                 return true;
 
-            _positionDelta.Y += _font.LineHeight;
+            _positionDelta.Y += _font.LineHeight + _font.LineSpacing;
             _positionDelta.X = _position.X;
             _previousGlyph = null;
 
@@ -414,6 +420,13 @@ public sealed class BitmapFont
             }
         }
 
-        return new BitmapFont(bmfFile.FontName, bmfFile.Info.FontSize, bmfFile.Common.LineHeight, characters.Values);
+        return new BitmapFont(
+            bmfFile.FontName,
+            bmfFile.Info.FontSize,
+            bmfFile.Common.LineHeight,
+            bmfFile.Info.SpacingHoriz,
+            bmfFile.Info.SpacingVert,
+            characters.Values
+        );
     }
 }

+ 2 - 2
source/MonoGame.Extended/Content/BitmapFonts/BitmapFontFileContent.cs

@@ -47,8 +47,8 @@ public sealed class BitmapFontFileContent
         [FieldOffset(8)] public byte PaddingRight;
         [FieldOffset(9)] public byte PaddingDown;
         [FieldOffset(10)] public byte PaddingLeft;
-        [FieldOffset(11)] public byte SpacingHoriz;
-        [FieldOffset(12)] public byte SpacingVert;
+        [FieldOffset(11)] public sbyte SpacingHoriz;
+        [FieldOffset(12)] public sbyte SpacingVert;
         [FieldOffset(13)] public byte Outline;
     }
 

+ 3 - 3
source/MonoGame.Extended/Content/BitmapFonts/BitmapFontFileReader.cs

@@ -237,7 +237,7 @@ public static class BitmapFontFileReader
         bmfFile.Info.PaddingDown = paddingValues[2];
         bmfFile.Info.PaddingLeft = paddingValues[3];
 
-        var spacingValues = node.GetByteDelimitedAttribute("spacing", 2);
+        var spacingValues = node.GetSignedByteDelimitedAttribute("spacing", 2);
         bmfFile.Info.SpacingHoriz = spacingValues[0];
         bmfFile.Info.SpacingVert = spacingValues[1];
 
@@ -415,8 +415,8 @@ public static class BitmapFontFileReader
                     var spacingValues = split[1].Split(',');
                     if (spacingValues.Length == 2)
                     {
-                        bmfFile.Info.SpacingHoriz = Convert.ToByte(spacingValues[0], CultureInfo.InvariantCulture);
-                        bmfFile.Info.SpacingVert = Convert.ToByte(spacingValues[1], CultureInfo.InvariantCulture);
+                        bmfFile.Info.SpacingHoriz = Convert.ToSByte(spacingValues[0], CultureInfo.InvariantCulture);
+                        bmfFile.Info.SpacingVert = Convert.ToSByte(spacingValues[1], CultureInfo.InvariantCulture);
                     }
                     break;
                 case "outline":

+ 10 - 1
source/MonoGame.Extended/Content/ContentReaders/BitmapFontContentReader.cs

@@ -26,6 +26,8 @@ public class BitmapFontContentReader : ContentTypeReader<BitmapFont>
         var fontName = reader.ReadString();
         var fontSize = reader.ReadInt16();
         var lineHeight = reader.ReadUInt16();
+        var spacingHoriz = reader.ReadSByte();
+        var spacingVert = reader.ReadSByte();
 
         var characterCount = reader.ReadInt32();
         var characters = new Dictionary<int, BitmapFontCharacter>();
@@ -61,6 +63,13 @@ public class BitmapFontContentReader : ContentTypeReader<BitmapFont>
             }
         }
 
-        return new BitmapFont(fontName, fontSize, lineHeight, characters.Values);
+        return new BitmapFont(
+            fontName,
+            fontSize,
+            lineHeight,
+            spacingHoriz,
+            spacingVert,
+            characters.Values
+        );
     }
 }

+ 26 - 0
source/MonoGame.Extended/Serialization/Xml/XmlNode.Extensions.cs

@@ -205,4 +205,30 @@ public static class XmlNodeExtensions
 
         return result;
     }
+
+    /// <summary>
+    /// Retrieves the signed byte values from the specified delimited attribute and returns them in an array.
+    /// </summary>
+    /// <param name="node">The XML node.</param>
+    /// <param name="attribute">The name of the attribute.</param>
+    /// <param name="expectedCount">The expected number of signed byte values.</param>
+    /// <returns>
+    /// An array of signed byte values parsed from the attribute, or an array of default values if the attribute is not found
+    /// or is empty.
+    /// </returns>
+    public static sbyte[] GetSignedByteDelimitedAttribute(this XmlNode node, string attribute, int expectedCount)
+    {
+        sbyte[] result = new sbyte[expectedCount];
+
+        if (node.GetAttributeValue(attribute, out string value))
+        {
+            string[] split = value.Split(',');
+            for (int i = 0; i < expectedCount; ++i)
+            {
+                result[i] = Convert.ToSByte(split[i], CultureInfo.InvariantCulture);
+            }
+        }
+
+        return result;
+    }
 }