using System; using System.Collections.Generic; using System.IO; using Quik.Typography; namespace Quik.FreeType { public class FreeTypeFont : QuikFont, IDisposable { private FreeTypeFontManager _owner; private FTLibrary _library; private FTFace _face; private List _textures = new List(); private Dictionary _entries = new Dictionary(); public override float Ascender => _face.ScaledSize.Ascender / 64f; public override float Descender => _face.ScaledSize.Descender / 64f; internal FreeTypeFont(FreeTypeFontManager owner, FileInfo file, QuikFontStyle style) { Style = style; _owner = owner; _library = owner._library; if (FT.NewFace(_library, file.FullName, 0, out _face) != FTError.None) { throw new Exception("Could not load font file."); } FT.SetCharSize(_face, 0, (long)(style.Size * 64), 96, 96); _textures.Add(new Atlas(owner.Context.TextureManager, _library)); } public override QuikFontStyle Style { get; } public override bool HasCharacter(int character) { return FT.GetCharIndex(_face, (ulong)character) != 0; } public override void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph) { GlyphEntry entry; if (_entries.TryGetValue(character, out entry)) { texture = entry.Atlas.Texture; glyph = entry.Metrics; return; } entry = new GlyphEntry(); entry.Character = character; uint index = FT.GetCharIndex(_face, (ulong) character); FT.LoadGlyph(_face, index, FTLoadFlags.Default); FT.RenderGlyph(_face.Glyph, FTRenderMode.Normal); entry.Atlas = _textures[_textures.Count - 1]; if (!entry.Atlas.CanFit(_face.Glyph)) { entry.Atlas = new Atlas(_owner.Context.TextureManager, _library); _textures.Add(entry.Atlas); } entry.Atlas.AttachGlyph(_face.Glyph, out QuikRectangle uvs); entry.Metrics = new QuikGlyph( character, uvs, new QuikVec2(_face.Glyph.Metrics.Width / 64f, _face.Glyph.Metrics.Height / 64f), new QuikVec2(_face.Glyph.Metrics.HorizontalBearingX / 64f, _face.Glyph.Metrics.HorizontalBearingY / 64f), new QuikVec2(_face.Glyph.Metrics.VerticalBearingX / 64f , _face.Glyph.Metrics.VerticalBearingY / 64f), new QuikVec2(_face.Glyph.Metrics.HorizontalAdvance / 64f, _face.Glyph.Metrics.VerticalAdvance / 64f)); _entries[character] = entry; texture = entry.Atlas.Texture; glyph = entry.Metrics; } public bool IsDisposed { get; private set; } = false; private void Dispose(bool disposing) { if (IsDisposed) return; if (disposing) { foreach (Atlas atlas in _textures) { atlas.Dispose(); } } FT.DoneFace(_face); IsDisposed = true; } public void Dispose() => Dispose(true); private class Atlas : IDisposable { public IQuikTexture Texture; private QuikVec2 _pointer = new QuikVec2(); private float _verticalAdvance = 0; private FTLibrary _ft; private FTBitmap _bitmap; public Atlas(IQuikTextureManager textureManager, FTLibrary ft) { Texture = textureManager.CreateTexture( new QuikVec2(4096, 4096), false, QuikImageFormat.RgbaU8); FT.BitmapInit(ref _bitmap); _ft = ft; } public bool CanFit(in FTGlyphSlot slot) { // FIXME: the atlas will overflow. return true; } public void AttachGlyph(in FTGlyphSlot slot, out QuikRectangle UVs) { FT.BitmapConvert(_ft, slot.Bitmap, ref _bitmap, 1); QuikRectangle position = new QuikRectangle( _pointer + new QuikVec2(_bitmap.Width + 1, _bitmap.Rows + 1), _pointer + new QuikVec2(1, 1)); Texture.SubImage( _bitmap.Buffer, QuikImageFormat.AlphaU8, position, 0, 1); _pointer.X += _bitmap.Width + 2; _verticalAdvance = Math.Max(_verticalAdvance, slot.Bitmap.Rows + 2); UVs = new QuikRectangle( position.Right / 4096, position.Bottom / 4096, position.Left / 4096, position.Top / 4096 ); } private bool _isDisposed = false; public void Dispose(bool disposing) { if (_isDisposed) return; if (disposing) { Texture.Dispose(); } FT.BitmapDone(_ft, ref _bitmap); _isDisposed = true; } public void Dispose() { } } private class GlyphEntry { public int Character; public Atlas Atlas; public QuikGlyph Metrics; } } }