181 lines
5.6 KiB
C#
181 lines
5.6 KiB
C#
|
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<Atlas> _textures = new List<Atlas>();
|
||
|
private Dictionary<int, GlyphEntry> _entries = new Dictionary<int, GlyphEntry>();
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|