using Azimuth; using Blacklight.Core.Drawables; using Blacklight.Core; using System; using System.Reflection; using System.Drawing; using Annulus; namespace Glass.Blacklight { public class FontRenderer : SpriteFontDefinition { public FontAttribute Source { get; set; } public SpriteFontDefinition Definition { get; set; } public Texture FontTexture { get; set; } public virtual void UnsupportedCharacter(char c) { throw new InvalidOperationException($"Font {Definition.Name} does not have a glyph for '{c}'"); } public void Render(Cel context, TextMaterial material, Rect rect, DrawState drawState) { Rect? clipWindow = rect; if (drawState.ClipWindow != null) { Rect? intersection = rect.Intersection(drawState.ClipWindow.Value); if (intersection != null) { clipWindow = intersection; } else { return; } } // By convention, the margin for text at the sides is half of the line height. Scalar width = Math.Max(0, rect.Width - material.Font.LineHeight); Scalar horizontalMargin = material.Font.LineHeight * 0.5; Scalar verticalMargin = material.Font.LineHeight * 0.25; TextLayout.PlaceGlyphs(material.Font, new StringView(material.Text), width, (char c, Vector glyphOrigin) => { if (!material.Font.GlyphDefinitions.TryGetValue(c, out GlyphDefinition glyphDefinition)) { UnsupportedCharacter(c); return; } var glyphSource = FontTexture.Subregion( glyphDefinition.Origin, new Size(glyphDefinition.Size.X, glyphDefinition.Size.Y)); if (glyphDefinition.Channel != null) { glyphSource.RedChannel = ColorChannel.One; glyphSource.BlueChannel = ColorChannel.One; glyphSource.GreenChannel = ColorChannel.One; glyphSource.AlphaChannel = ColorChannels.FromChannelIndex(glyphDefinition.Channel.Value); } Vector offset = new Vector( horizontalMargin + glyphDefinition.Offset.X, verticalMargin + glyphDefinition.Offset.Y - material.Font.Baseline ); Vector drawOrigin = glyphOrigin + offset; Vector drawEnd = drawOrigin + new Vector(glyphSource.Size.Width, glyphSource.Size.Height); var region = new Sprite { Source = glyphSource, Destination = SpritePlacement.Corners(drawOrigin, drawEnd), ClipWindow = clipWindow, }; context.AddEntity(new Entity(region, drawState.ModelToWorld ?? AffineMatrix.Identity)); }); } public static FontRenderer FontBuilder(FieldInfo field, WindowHandle windowHandle, FontAttribute fontAttribute) { string source = fontAttribute.FontDefinitionSource; if (source == null) { throw new ArgumentException( $"Field {field} had a MaterialLibraryAttribute with a null source."); } FontRenderer fontRenderer = new FontRenderer(); fontRenderer.FontAttribute = fontAttribute; var assembly = Assembly.GetEntryAssembly(); using (var stream = assembly.GetManifestResourceStream(fontAttribute.FontDefinitionSource)) { if (stream == null) { throw new InvalidOperationException($"Could not load {fontAttribute.FontDefinitionSource}"); } fontRenderer.Definition = BMFontDefinition.FromBinaryFile(stream); fontRenderer.Definition.FontAttribute = fontAttribute; if (fontRenderer.Definition.Pages.Count != 1) { throw new NotImplementedException("Currently can only load fonts that have exactly 1 page, but " + $"${fontRenderer.Definition.Name} has {fontRenderer.Definition.Pages.Count}"); } int lastIndex = fontAttribute.FontDefinitionSource.LastIndexOf('.'); if (lastIndex < 0) { lastIndex = fontAttribute.FontDefinitionSource.Length; } else { lastIndex++; } string path = fontAttribute.FontDefinitionSource.Substring(0, lastIndex); using (var textureAtlasStream = assembly .GetManifestResourceStream("Glass.Testbed.SampleMaterials.Philosopher-Regular_0.png")) { if (stream == null) { throw new InvalidOperationException($"Could not load {fontRenderer.Definition.Name}"); } fontRenderer.FontTexture = windowHandle.CreateTexture(textureAtlasStream); } } return fontRenderer; } } }