Coder Social home page Coder Social logo

cyotek / cyotek.drawing.bitmapfont Goto Github PK

View Code? Open in Web Editor NEW
42.0 4.0 10.0 1.52 MB

Component for parsing bitmap font files generated by AngelCode's BMFont utility

License: MIT License

C# 98.09% Batchfile 1.91%
csharp csharp-library bmfont bitmap-font hacktoberfest

cyotek.drawing.bitmapfont's Introduction

C# AngelCode bitmap font parsing

Build status CodeQL NuGet Donate

The font parser library was used by this OpenGL application that renders text

While writing bitmap font processing code for an OpenGL project, I settled on using AngelCode's BMFont utility to generate both the textures and the font definition.

This library is a generic parser for the BMFont format - it doesn't include any rendering functionality or exotic references and should be usable in any version of .NET from 2.0 upwards. BMFont can generate fonts in three formats - binary, text and XML. This library supports all three formats, although it can only read version 3 binary fonts.

Note: This library only provides parsing functionality for loading font meta data. It is up to you to provide functionality used to load textures and render characters.

Getting the library

The easiest way of obtaining the library is via NuGet.

Install-Package Cyotek.Drawing.BitmapFont

If you don't use NuGet, pre-compiled binaries can be obtained from the GitHub Releases page.

Of course, you can always grab the source and build it yourself!

Overview of the library

There are four main classes used to describe a font:

  • BitmapFont - the main class representing the font and its attributes
  • Character - representing a single character
  • Kerning - represents the kerning between a pair of characters
  • Page - represents a texture page

There is also a support class, Padding, as I didn't want to reference System.Windows.Forms in order to use its own and using a Rectangle instead would be confusing.

What format should I use

All 3 formats contain the same information so ultimately there is no real different in the 3. However, the binary format is is far more efficient in both storage space and load speed than the other formats.

The following benchmarks were generated via BenchmarkDotNet, using a font with 424 characters. Loading binary fonts is the clear winner, both in terms of speed and allocations, followed by text and finally XML.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1082 (1909/November2018Update/19H2)
AMD FX(tm)-6300, 1 CPU, 6 logical and 3 physical cores
.NET Core SDK=3.1.402
  [Host]     : .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT
  DefaultJob : .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
LoadBinary 135.8 us 1.41 us 1.32 us 10.0098 - - 41.33 KB
LoadAutoBinary 227.1 us 3.60 us 4.29 us 10.9863 0.2441 - 45.55 KB
LoadText 2,734.2 us 6.06 us 5.37 us 164.0625 3.9063 - 674.75 KB
LoadAutoText 2,778.5 us 14.96 us 13.26 us 164.0625 - - 678.98 KB
LoadXml 3,216.2 us 18.86 us 14.73 us 144.5313 70.3125 - 780.34 KB
LoadAutoXml 3,298.5 us 26.18 us 24.49 us 152.3438 74.2188 - 784.53 KB

Loading a font

To load a font, call BitmapFontLoader.LoadFontFromFile. This will attempt to auto detect the file type and load a font. Alternatively, if you already know the file type in advance, then call the variations BitmapFontLoader.LoadFontFromBinaryFile, BitmapFontLoader.LoadFontFromTextFile or BitmapFontLoader.LoadFontFromXmlFile.

Each of these functions returns a new BitmapFont object on success.

Alternatively, you can create a BitmapFont instance yourself and call one one of the following methods:

  • Load(string) - attempts to auto-detect the format from the specified file
  • Load(Stream) - attempts to auto-detect the format from the specified Stream
  • LoadBinary(Stream) - loads a binary font from the given Stream
  • LoadText(string) - loads a text font from the given string
  • LoadText(Stream) - loads a text font from the given Stream
  • LoadText(TextReader) - loads a text font from the given TextReader
  • LoadXml(string) - loads a XML font from the given string
  • LoadXml(Stream) - loads a XML font from the given Stream
  • LoadXml(TextReader) - loads a XML font from the given TextReader

Note: Load methods on the BitmapFont and BitmapFontLoader classes that take a string filename will fully qualify the FileName property of loaded Page instances. Page instances for all other load methods will have a relative filename and it is up to the calling application to fully qualify the path as appropriate when loading textures.

Using a font

The BitmapFont class returns all the information specified in the font file, such as the attributes used to create the font. Most of these not directly used and are there only for if your application needs to know how the font was generated (for example if the textures are packed or not). The main things you would be interested in are:

  • Characters - this property contains all the characters defined in the font.
  • Kernings - this property contains all kerning definitions. However, mostly you should use the GetKerning method to get the kerning for a pair of characters.
  • Pages -this property contains the filenames of the textures used by the font. You'll need to manually load the relevant textures.
  • LineHeight - this property returns the line height. When rending text across multiple lines, use this property for incrementing the vertical coordinate - don't just use the largest character height or you'll end up with inconsistent line heights.

The Character class describes a single character. Your rendering functionality will probably need to use all of the properties it contains:

  • Bounds - the location and size of the character in the source texture.
  • TexturePage - the index of the page containing the source texture.
  • Offset - an offset to use when rendering the character so it lines up correctly with other characters.
  • XAdvance - the value to increment the horizontal coordinate by. Don't forgot to combine this value with the result of a call to GetKerning.

Example rendering using GDI

The sample project shows a very way of rending using GDI; however this is just for demonstration purposes and you should probably come up with something more efficient in a real application!

Example rendering using the Text Maker sample

    private void DrawCharacter(Graphics g, Character character, int x, int y)
    {
      g.DrawImage(_textures[character.TexturePage],
                  new RectangleF(x, y, character.Width, character.Height),
                  new Rectangle(character.X, character.Y, character.Width, character.Height),
                  GraphicsUnit.Pixel);
    }

    private void DrawPreview()
    {
      if (_font != null)
      {
        string text;
        Size size;

        text = previewTextBox.Text;
        size = _font.MeasureFont(text);

        if (size.Height != 0 && size.Width != 0)
        {
          Bitmap image;
          int x;
          int y;
          char previousCharacter;

          image = new Bitmap(size.Width, size.Height);
          x = 0;
          y = 0;
          previousCharacter = ' ';

          using (Graphics g = Graphics.FromImage(image))
          {
            foreach (char character in text)
            {
              switch (character)
              {
                case '\n':
                  x = 0;
                  y += _font.LineHeight;
                  break;

                case '\r':
                  break;

                default:
                  Character data;

                  data = _font[character];

                  if (!data.IsEmpty)
                  {
                    int kerning;
                    kerning = _font.GetKerning(previousCharacter, character);

                    this.DrawCharacter(g, data, x + data.XOffset + kerning, y + data.YOffset);

                    x += data.XAdvance + kerning;
                  }
                  break;
              }

              previousCharacter = character;
            }
          }

          previewImageBox.Image = image;
          previewImageBox.ActualSize();
        }
      }
    }

The Bitmap Font Viewer application

This sample application loads and previews bitmap fonts

Included in this repository is a sample WinForms application for viewing BMFont font definitions.

Note: All of the fonts I have created and tested were unpacked. The font viewer does not support packed textures, and while it will still load the font, it will not draw glyphs properly as it isn't able to do any of the magic with channels that the packed texture requires. In addition, as .NET doesn't support the TGA format by default, neither does this sample project.

Requirements

.NET Framework 2.0 or later.

Pre-built binaries are available via a signed NuGet package containing the following targets.

  • .NET 3.5
  • .NET 4.0
  • .NET 4.5.2
  • .NET 4.6.2
  • .NET 4.7.2
  • .NET 4.8
  • .NET Standard 1.3
  • .NET Standard 2.0
  • .NET Standard 2.1
  • .NET Core 2.1
  • .NET Core 2.2
  • .NET Core 3.1

Is there a target not on this list you'd like to see? Raise an issue, or even better, a pull request.

Acknowledgements

See CONTRIBUTORS.md for details of contributions to this library.

License

This source is licensed under the MIT license. See LICENSE.txt for the full text.

cyotek.drawing.bitmapfont's People

Contributors

abenedik avatar ashleighadams avatar cyotek avatar jtone123 avatar rds1983 avatar scottswu avatar tom94 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

cyotek.drawing.bitmapfont's Issues

xctk:ColorPicker localization

Is localization implemented for it?
I tried to use the following code during initialization to set it to a different language but there seem to be no effect

Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);

Add documentation

Add XML documentation comments, I think I saw only a bare handful. API is fairly simple but no excuse not to have them really.

Add concrete collections

The Characters, Kernings and Pages properties either expose a Dictionary<> or a []. Both of these are bad design decisions. Change to use concrete collections.

Change namespace to Cyotek.Drawing

The current namespace is Cyotek.Drawing.BitmapFont, but the core class is also BitmapFont. Change the namespace to be used Cyotek.Drawing to match normal conventions.

Make structs immutable

Given that the purpose of the library is to load bitmap fonts, but not to edit or save them, it doesn't make sense that the structures are mutable. It makes even less sense if the structs are updated with custom GetHashCode overrides.

Suggest making them fully immutable.

Remove .NET 3.5 and .NET Standard 1.3 targets

Building the .NET Standard 1.3 displays a build warning due to it relying on a beta Microsoft package. While this would be resolved by #22, it is an obsolete target.

Also, since installing the .NET 5.0 SDK (which takes precedence over other versions), it appears to be no longer possible to build the .NET 3.5 target. While forcing a specific SDK version works this done with a global file rather than a command line switch so isn't desirable. According to support docs .NET 3.5 is still supported until 2029 so it is a bit annoying that the tooling no longer appears to support it. Consider looking into doing a separate MSBuild based compile for 3.5 as long as it is still fully automatic for the NuGet package build. Otherwise, drop it as well.

`FormatException` from `BitmapFont.LoadXml` in some locales

Repro:
mkdir repro-25 && cd repro-25 && dotnet new console && dotnet add package Cyotek.Drawing.BitmapFont -v 2.0.1
Then edit Program.cs to:

using System;
using System.IO;

using Cyotek.Drawing.BitmapFont;

using var fs = File.OpenRead("./courier16px.fnt");
BitmapFont font = new();
font.LoadXml(fs);
Console.WriteLine("success");

Download this file (curl -O https://raw.githubusercontent.com/TASVideos/BizHawk/2.6.1/src/BizHawk.Client.EmuHawk/Resources/courier16px.fnt).
Finally, dotnet run. On Windows with the default en-AU locale (and presumably most others), you get:

success

But change this setting:
screenshot
...and run again, and you get:

Unhandled exception. System.FormatException: Input string was not in a correct format.
   at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)
   at Cyotek.Drawing.BitmapFont.BitmapFont.LoadXml(TextReader reader)
   at Cyotek.Drawing.BitmapFont.BitmapFont.LoadXml(Stream stream)
   at <Program>$.<Main>$(String[] args)

Probable cause: I think all these calls to Convert.ToInt32 need to have CultureInfo.InvariantCulture as the second argument.

_fontSize = Convert.ToInt32(properties.Attributes["size"].Value);
_bold = Convert.ToInt32(properties.Attributes["bold"].Value) != 0;
_italic = Convert.ToInt32(properties.Attributes["italic"].Value) != 0;
_unicode = Convert.ToInt32(properties.Attributes["unicode"].Value) != 0;
_stretchedHeight = Convert.ToInt32(properties.Attributes["stretchH"].Value);
_charset = properties.Attributes["charset"].Value;
_smoothed = Convert.ToInt32(properties.Attributes["smooth"].Value) != 0;
_superSampling = Convert.ToInt32(properties.Attributes["aa"].Value);
_padding = BitmapFontLoader.ParsePadding(properties.Attributes["padding"].Value);
_spacing = BitmapFontLoader.ParsePoint(properties.Attributes["spacing"].Value);
_outlineSize = Convert.ToInt32(properties.Attributes["outline"].Value);
// common attributes
properties = root.SelectSingleNode("common");
_baseHeight = Convert.ToInt32(properties.Attributes["base"].Value);
_lineHeight = Convert.ToInt32(properties.Attributes["lineHeight"].Value);
_textureSize = new Size(Convert.ToInt32(properties.Attributes["scaleW"].Value),
Convert.ToInt32(properties.Attributes["scaleH"].Value));
_packed = Convert.ToInt32(properties.Attributes["packed"].Value) != 0;
_alphaChannel = Convert.ToInt32(properties.Attributes["alphaChnl"].Value);
_redChannel = Convert.ToInt32(properties.Attributes["redChnl"].Value);
_greenChannel = Convert.ToInt32(properties.Attributes["greenChnl"].Value);
_blueChannel = Convert.ToInt32(properties.Attributes["blueChnl"].Value);

Add tests

It's a fairly simple library, but even so some tests would be helpful for sanity checking.

Merge BinaryRealms MeasureFont implementation

A quick compare of the original bitmap font code with the version embedded in the BinaryRealms library shows that I updated MeasureFont to handle tab characters, along with an extra overload that returned the source text data split up by lines (wrapped or forced). Might be worth merging in this change.

Improve allocations when loading text format fonts

Completely against my expectations, loading the Text format is slightly slower than XML, but also uses significantly more allocations. While the the performance is probably fine, need to cut down on the number of allocations.

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 1 (10.0.14393)
Processor=AMD FX(tm)-6300 Six-Core Processor, ProcessorCount=6
Frequency=3429483 Hz, Resolution=291.5891 ns, Timer=TSC
  [Host]     : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.7.2101.1
  DefaultJob : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.7.2101.1
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
LoadAuto 8.620 ms 0.1708 ms 0.3489 ms 187.5000 93.7500 15.6250 1.04 MB
LoadText 13.511 ms 0.2649 ms 0.3883 ms 2156.2500 15.6250 15.6250 3.7 MB
LoadXml 8.144 ms 0.1607 ms 0.2200 ms 203.1250 93.7500 15.6250 1.03 MB

Finish off removing dependency on System.Drawing

Some properties expose System.Drawing types (e.g. Rectangle, Size). In order to ensure the library can be used anywhere without dependencies these should be changed to native types.

v2 introduced native alternatives whilst flagging the System.Drawing versions as obsolete, but some were omitted (for example MeasureFont returns a Size)

Add support for binary file format

As reading the binary file is almost certainly going to be faster than parsing the text formats, especially the XML format, probably worth investigating adding a binary parser.

http://www.angelcode.com/products/bmfont/doc/file_format.html#bin

Format doesn't look too bad, although there's 3 different versions - either only support the third, or find older versions of the program to generate older formats.

Definitely needs tests adding if going to add binary support.

Nuget package update?

Is it possible to get the nuget package updated? We'd like to use the .NET Standard-compatible library in osu!.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.