using System.Runtime.InteropServices; using WellspringCS; namespace Nerfed.Runtime.Graphics; public unsafe class Font : GraphicsResource { public Texture Texture { get; } public float PixelsPerEm { get; } public float DistanceRange { get; } internal IntPtr Handle { get; } private byte* StringBytes; private int StringBytesLength; /// /// Loads a TTF or OTF font from a path for use in MSDF rendering. /// Note that there must be an msdf-atlas-gen JSON and image file alongside. /// /// public static Font Load(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, string fontPath) { FileStream fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read); void* fontFileByteBuffer = NativeMemory.Alloc((nuint)fontFileStream.Length); Span fontFileByteSpan = new Span(fontFileByteBuffer, (int)fontFileStream.Length); fontFileStream.ReadExactly(fontFileByteSpan); fontFileStream.Close(); FileStream atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read); void* atlasFileByteBuffer = NativeMemory.Alloc((nuint)atlasFileStream.Length); Span atlasFileByteSpan = new Span(atlasFileByteBuffer, (int)atlasFileStream.Length); atlasFileStream.ReadExactly(atlasFileByteSpan); atlasFileStream.Close(); IntPtr handle = Wellspring.Wellspring_CreateFont( (IntPtr)fontFileByteBuffer, (uint)fontFileByteSpan.Length, (IntPtr)atlasFileByteBuffer, (uint)atlasFileByteSpan.Length, out float pixelsPerEm, out float distanceRange ); string imagePath = Path.ChangeExtension(fontPath, ".png"); ImageUtils.ImageInfoFromFile(imagePath, out uint width, out uint height, out uint sizeInBytes); ResourceUploader uploader = new ResourceUploader(graphicsDevice); Texture texture = uploader.CreateTexture2DFromCompressed(imagePath); uploader.Upload(); uploader.Dispose(); NativeMemory.Free(fontFileByteBuffer); NativeMemory.Free(atlasFileByteBuffer); return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange); } private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device) { Handle = handle; Texture = texture; PixelsPerEm = pixelsPerEm; DistanceRange = distanceRange; StringBytesLength = 32; StringBytes = (byte*)NativeMemory.Alloc((nuint)StringBytesLength); } public unsafe bool TextBounds( string text, int pixelSize, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, out Wellspring.Rectangle rectangle ) { int byteCount = System.Text.Encoding.UTF8.GetByteCount(text); if (StringBytesLength < byteCount) { StringBytes = (byte*)NativeMemory.Realloc(StringBytes, (nuint)byteCount); } fixed (char* chars = text) { System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount); byte result = Wellspring.Wellspring_TextBounds( Handle, pixelSize, (Wellspring.HorizontalAlignment)horizontalAlignment, (Wellspring.VerticalAlignment)verticalAlignment, (IntPtr)StringBytes, (uint)byteCount, out rectangle ); if (result == 0) { Log.Warning("Could not decode string: " + text); return false; } } return true; } protected override void Dispose(bool disposing) { if (!IsDisposed) { if (disposing) { Texture.Dispose(); } Wellspring.Wellspring_DestroyFont(Handle); } base.Dispose(disposing); } }