Basic Harfbuzz support and use MSDGen with Skia

This commit is contained in:
Simon Lübeß
2022-01-05 15:31:09 +01:00
parent f4d64b15fa
commit 826ac68873
6 changed files with 316 additions and 12 deletions
+189 -6
View File
@@ -4,6 +4,7 @@ using GlitchyEngine.Math;
using GlitchyEngine.Core;
using System.Collections;
using msdfgen;
using static FreeType.HarfBuzz;
using internal GlitchyEngine.Renderer.Text;
@@ -22,6 +23,7 @@ namespace GlitchyEngine.Renderer.Text
public int32 Width, Height;
public double TranslationX, TranslationY;
//public double Scale;
/// How many pixels we have to move the pen after drawing this glyph.
public float Advance;
@@ -50,7 +52,8 @@ namespace GlitchyEngine.Renderer.Text
internal Texture2D _atlas ~ _?.ReleaseRef();
private Int3 _atlasSize;
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ DeleteDictionaryAndValues!(_);
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ delete _;//DeleteDictionaryAndValues!(_);
private Dictionary<uint32, GlyphDescriptor> _glyphsById = new .() ~ DeleteDictionaryAndValues!(_);
GlyphDescriptor _missingGlyph;
private SamplerState _sampler ~ _.ReleaseRef();
@@ -60,6 +63,10 @@ namespace GlitchyEngine.Renderer.Text
internal double _range = 4.0;
private hb_blob_t* blob ~ hb_blob_destroy(_);
private hb_face_t* face ~ hb_face_destroy(_);
private hb_font_t* font ~ hb_font_destroy(_);
/**
* Gets or sets the fallback Font for this Font.
* The Fallback font will be used to render Glyphs that are not defined in the current font.
@@ -113,8 +120,6 @@ namespace GlitchyEngine.Renderer.Text
// Set default sampler
Sampler = null;
_glyphs.Add('\0', new GlyphDescriptor(){Font = this});
_fontSize = fontSize;
_faceIndex = faceIndex;
_hasColor = hasColor;
@@ -130,7 +135,23 @@ namespace GlitchyEngine.Renderer.Text
_range = 4.0 / _geometryScaler;
GlyphDescriptor nullDesc = new GlyphDescriptor(){Font = this};
nullDesc.GlyphIndex = FreeType.Get_Char_Index(_face, '\0');
_glyphs.Add('\0', nullDesc);
_glyphsById.Add(nullDesc.GlyphIndex, nullDesc);
LoadGlyphs(firstChar, charCount);
// HarfBuzz
{
blob = hb_blob_create_from_file(fontPath.CStr()); /* or hb_blob_create_from_file_or_fail() */
face = hb_face_create(blob, 0);
font = hb_font_create(face);
hb_font_set_scale(font, (.)fontSize * 64, (.)fontSize * 64);
}
//TestMSDF();
}
public void LoadGlyphs(char32 firstChar, uint32 charCount)
@@ -140,6 +161,37 @@ namespace GlitchyEngine.Renderer.Text
UpdateAtlas();
}
public void LoadGlyphs(uint32 firstGlyph, uint32 charCount)
{
ExtendRange(firstGlyph, firstGlyph + charCount);
UpdateAtlas();
}
internal GlyphDescriptor GetGlyph(uint32 glyphId, bool allowDynamicLoading = true)
{
GlyphDescriptor desc;
if(_glyphsById.TryGetValue(glyphId, out desc))
{
// if the char is not defined in the current font try to load it from the fallback
if(desc == null && _fallback != null)
{
desc = _fallback.GetGlyph(glyphId, allowDynamicLoading);
}
}
else if(allowDynamicLoading)
{
// TODO: make reloading optional
uint32 start = (glyphId >> 4) << 4;
LoadGlyphs(start, 64);
desc = GetGlyph(glyphId, false);
}
return desc ?? _missingGlyph;
}
internal GlyphDescriptor GetGlyph(char32 char, bool allowDynamicLoading = true)
{
GlyphDescriptor desc;
@@ -177,6 +229,8 @@ namespace GlitchyEngine.Renderer.Text
desc.Font = this;
desc.GlyphIndex = FreeType.Get_Char_Index(_face, char);
_glyphsById.TryAdd(desc.GlyphIndex, desc);
// if the char is not contained by the font we set the desc to null
if(desc.GlyphIndex == 0)
{
@@ -191,6 +245,33 @@ namespace GlitchyEngine.Renderer.Text
}
}
void ExtendRange(uint32 firstGlyph, uint32 rangeEnd)
{
// TODO: refactor
for(uint32 glyphId = firstGlyph; glyphId < rangeEnd; glyphId++)
{
GlyphDescriptor desc = new GlyphDescriptor();
if(_glyphsById.TryAdd(glyphId, desc))
{
//desc.Face = _face;
desc.Font = this;
desc.GlyphIndex = glyphId;//FreeType.Get_Char_Index(_face, char);
// if the char is not contained by the font we set the desc to null
if(desc.GlyphIndex == 0)
{
delete desc;
_glyphsById[glyphId] = null;
}
}
else
{
delete desc;
}
}
}
void UpdateAtlas()
{
DrawAtlas();
@@ -227,7 +308,7 @@ namespace GlitchyEngine.Renderer.Text
int32 atlasHeight = _atlasSize.Y;
int32 atlasArray = _atlasSize.Z;
for(var (char, desc) in _glyphs)
for(var (char, desc) in _glyphsById)
{
if(desc == null || desc.IsCalculated)
{
@@ -324,7 +405,7 @@ namespace GlitchyEngine.Renderer.Text
}
}
for(let (char, desc) in _glyphs)
for(let (char, desc) in _glyphsById)
{
if(desc == null)
continue;
@@ -356,6 +437,9 @@ namespace GlitchyEngine.Renderer.Text
desc.Advance = (float)(advance * _geometryScaler);
//shape.OrientContours();
msdfgen.ResolveShapeGeometry(shape);
shape.Normalize();
var bounds = shape.GetBounds();
@@ -407,6 +491,8 @@ namespace GlitchyEngine.Renderer.Text
desc.TranslationX = translationX;
desc.TranslationY = translationY;
//desc.Scale = scale;
desc.AdjustToBaseLine = (float)(-translationY * _geometryScaler);
desc.AdjustToPen = (float)(-translationX);
@@ -414,6 +500,101 @@ namespace GlitchyEngine.Renderer.Text
return true;
}
/*
void TestMSDF()
{
GlyphDescriptor desc = scope .();
desc.GlyphIndex = FreeType.Get_Char_Index(_face, 'A');
var v = _range;
_range = 4.0f;
_geometryScaler = 1.0f;
Calculate(ref desc);
// prepare shape
double advance = 0;
Shape shape;
msdfgen.LoadGlyph(out shape, ref _face, 36, out advance);
bool b = msdfgen.ResolveShapeGeometry(shape);
if(!b)
{
}
shape.Normalize();
var bounds = shape.GetBounds();
//shape.ReverseIfNeeded(bounds);
msdfgen.EdgeColoringSimple(shape, 3.0);
// prepare projection
msdfgen.Projection projection = .();
projection.ScaleX = _geometryScaler;
projection.ScaleY = _geometryScaler;
projection.TranslationX = desc.TranslationX;
projection.TranslationY = desc.TranslationY;
int bufferX = desc.Width;
int bufferY = desc.Height;
using(Bitmap<ColorRGB, const 1> bitmap = .((.)bufferX, (.)bufferY))
{
MSDFGeneratorConfig config = .();
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, projection, _range, config);
// Check if we have to invert
/*
Line 1039
// Get sign of signed distance outside bounds
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
orientation = distance <= 0 ? KEEP : REVERSE;
if (invert)
{
invertColor<3>(bitmap);
}
*/
uint8[] pixels = new:ScopedAlloc! uint8[desc.Width * desc.Height * 4];
//int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue);
uint8 ToInt8(float f) => (.)Math.Clamp(255 * f, uint8.MinValue, uint8.MaxValue);
for(int y = 0; y < desc.Height; y++)
for(int x = 0; x < desc.Width; x++)
{
ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x];
int index = ((desc.Height - y - 1) * desc.Width + x) * 4;
pixels[index + 0] = ToInt8(pixel.Red);
pixels[index + 1] = ToInt8(pixel.Green);
pixels[index + 2] = ToInt8(pixel.Blue);
pixels[index + 3] = uint8.MaxValue;
}
LodePng.LodePng.Encode32File("testA.png", pixels.CArray(), (.)desc.Width, (.)desc.Height);
//_atlas.SetData<Color>((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
// (.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z);
}
_range = v;
}
*/
void GenerateMSDF(GlyphDescriptor desc)
{
// prepare shape
@@ -423,11 +604,13 @@ namespace GlitchyEngine.Renderer.Text
Shape shape;
msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance);
msdfgen.ResolveShapeGeometry(shape);
shape.Normalize();
var bounds = shape.GetBounds();
shape.ReverseIfNeeded(bounds);
//shape.ReverseIfNeeded(bounds);
msdfgen.EdgeColoringSimple(shape, 3.0);
@@ -3,6 +3,7 @@ using FreeType;
using System.Diagnostics;
using GlitchyEngine.Math;
using System.Collections;
using static FreeType.HarfBuzz;
using internal GlitchyEngine.Renderer.Text;
using internal GlitchyEngine.Renderer;
@@ -109,6 +110,87 @@ namespace GlitchyEngine.Renderer.Text
//float f = 1.0f;
//int32 depthInt = *(int32*)&f;
// Shape!!!!!!!!!!!!!!!!!!!
// 1. Create a buffer and put your text in it:
hb_buffer_t *buf;
buf = hb_buffer_create();
hb_buffer_add_utf8(buf, text.CStr(), -1, 0, -1);
// 2. Set the script, language and direction of the buffer.
hb_buffer_set_direction(buf, .HB_DIRECTION_LTR);
hb_buffer_set_script(buf, .HB_SCRIPT_LATIN);
hb_buffer_set_language(buf, hb_language_from_string("en".CStr(), -1));
// 3. Get the face
hb_font_t* hb_font = font.[Friend]font;
// 4. Shape:
hb_shape(hb_font, buf, null, 0);
// 5. Get the glyph and position information.
uint32 glyph_count = ?;
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
for (uint32 i < glyph_count)
{
hb_codepoint_t glyphid = glyph_info[i].codepoint;
hb_position_t x_offset = glyph_pos[i].x_offset;
hb_position_t y_offset = glyph_pos[i].y_offset;
hb_position_t x_advance = glyph_pos[i].x_advance;
hb_position_t y_advance = glyph_pos[i].y_advance;
// Get glyph from Font
var glyphDesc = font.GetGlyph(glyphid);
// This never happens
Debug.Assert(glyphDesc != null);
// The glyph might belong to a fallback font
var glyphFont = glyphDesc.Font;
float glyphFontScale = (float)fontSize / glyphFont._fontSize;
Texture2D atlas = glyphFont._atlas;
// Add reference to atlas in case it is recreated during rendering
if(!atlasses.Contains(atlas))
{
atlasses.Add(atlas..AddRef());
}
Vector2 atlasSize = .(atlas.Width, atlas.Height);
float adjustToPenX = glyphDesc.AdjustToPen;
adjustToPenX *= glyphFontScale;
float adjustToBaseline = glyphDesc.AdjustToBaseLine;
adjustToBaseline *= glyphFontScale;
// Rectangle on the screen
Vector4 viewportRect = .(
penPosition + adjustToPenX,
baseline + adjustToBaseline,
glyphDesc.Width * glyphFontScale,
glyphDesc.Height * glyphFontScale);
// Rectangle on the font atlas
Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.Width, glyphDesc.Height);
Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor;
texRect /= Vector4(atlasSize, atlasSize);
Renderer2D.DrawQuad(Vector3(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2, 1), .(viewportRect.Z, viewportRect.W), 0, .Red);
Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, glyphColor, texRect);
Renderer2D.DrawQuad(Vector3(penPosition, baseline, -1), .(1), 0, .Green);
penPosition += (x_advance / 64) * glyphFontScale;
baseline += (y_advance / 64) * glyphFontScale;
}
/*
// enumerate through the unicode codepoints
for(char32 char in text.DecodedChars)
{
@@ -182,7 +264,7 @@ namespace GlitchyEngine.Renderer.Text
// Increase depth for the next glyph
//depthInt--;
}
*/
Renderer2D.Flush();
// TODO: not good!
+37 -1
View File
@@ -35,11 +35,34 @@ PS_Input VS(VS_Input input)
return output;
}
/*
in vec2 texCoord;
out vec4 color;
uniform sampler2D msdf;
uniform vec4 bgColor;
uniform vec4 fgColor;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
float ScreenPxRange(float2 texcoord) {
void main() {
vec3 msd = texture(msdf, texCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange()*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
color = mix(bgColor, fgColor, opacity);
}
*/
float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}
float ScreenPxRange(float2 texcoord)
{
float2 screenTexSize = 1.0f / fwidth(texcoord);
return max(0.5f * dot(UnitRange, screenTexSize), 1.0f);
}
@@ -54,4 +77,17 @@ float4 PS(PS_Input input) : SV_Target0
return float4(input.Color.rgb, opacity * input.Color.a);
}
/*
// 2D
float4 PS(PS_Input input) : SV_Target0
{
float3 msd = Texture.Sample(Sampler, input.TexCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPixelRange*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return float4(input.Color.rgb, opacity * input.Color.a);
}
*/
#effect[VS=VS, PS=PS]
+4 -1
View File
@@ -69,9 +69,11 @@ namespace Sandbox
var japanese = new Font("C:\\Windows\\Fonts\\YuGothM.ttc", 64, true, '\0', 1);
var emojis = new Font("C:\\Windows\\Fonts\\seguiemj.ttf", 64, true, '😂' - 10, 1);
var mathstuff = new Font("C:\\Windows\\Fonts\\cambria.ttc", 64, true, 'α', 1);
var cascadiaCode = new Font("C:\\Windows\\Fonts\\CascadiaCode.ttf", 32, true, 'A', 1);
fonty.Fallback = japanese..ReleaseRefNoDelete();
japanese.Fallback = emojis..ReleaseRefNoDelete();
emojis.Fallback = mathstuff..ReleaseRefNoDelete();
mathstuff.Fallback = cascadiaCode..ReleaseRefNoDelete();
_textureViewer = new TextureViewer();
@@ -112,7 +114,7 @@ namespace Sandbox
Renderer2D.DrawCircle(Vector3(-2, -2, -2f), Vector2(1), .GreenYellow);
FontRenderer.DrawText(fonty, "Hallo! gjy", 0, 0, 64, .White, .White);
FontRenderer.DrawText(fonty.Fallback.Fallback.Fallback.Fallback, "Hallö! gjy ÄAwww www", 0, 0, 64, .White, .White); // Hallo! gjy
Renderer2D.EndScene();
}
@@ -143,6 +145,7 @@ namespace Sandbox
ImGui.End();
//_textureViewer.ViewTexture(fonty.Fallback.Fallback.Fallback.Fallback.[Friend]_atlas);
_textureViewer.ViewTexture(fonty.[Friend]_atlas);
_context.SetRenderTarget(null);