Skip to content

Commit 8333e26

Browse files
support for basic non ascii characters
1 parent c03c9f7 commit 8333e26

File tree

7 files changed

+811
-3555
lines changed

7 files changed

+811
-3555
lines changed

Flamui.Tests/LRUCacheTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace Flamui.Test;
2+
3+
public class LRUCacheTests
4+
{
5+
[Fact]
6+
public void BasicTest()
7+
{
8+
var cache = new LRUCache<int, int>(2);
9+
10+
cache.Add(1, 1);
11+
cache.Add(2, 2);
12+
13+
Assert.True(cache.TryGet(1, out var value));
14+
Assert.Equal(1, value);
15+
16+
cache.Add(3, 3);
17+
18+
Assert.False(cache.TryGet(2, out _));
19+
20+
cache.Add(4, 4);
21+
22+
Assert.False(cache.TryGet(1, out _));
23+
24+
Assert.True(cache.TryGet(3, out value));
25+
Assert.Equal(3, value);
26+
27+
Assert.True(cache.TryGet(4, out value));
28+
Assert.Equal(4, value);
29+
30+
Assert.Equal(3, cache.GetLeastUsed());
31+
}
32+
33+
[Fact]
34+
public void AdvancedTest()
35+
{
36+
var cache = new LRUCache<int, int>(10);
37+
38+
for (int i = 0; i < 10; i++)
39+
{
40+
cache.Add(i, i);
41+
}
42+
43+
Assert.Equal(0, cache.GetLeastUsed());
44+
45+
for (int i = 0; i < 10; i++)
46+
{
47+
Assert.True(cache.TryGet(i, out var value));
48+
Assert.Equal(i, value);
49+
50+
var leastUsed = (i + 1) % 10;
51+
Assert.Equal(leastUsed, cache.GetLeastUsed());
52+
}
53+
}
54+
}

Flamui/Drawing/FontLoader.cs

Lines changed: 102 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Reflection;
22
using System.Runtime.InteropServices;
3+
using Silk.NET.OpenGL;
34
using StbTrueTypeSharp;
45

56
namespace Flamui.Drawing;
@@ -33,22 +34,12 @@ public ScaledFont(Font font, float pixelSize)
3334

3435
public float GetCharWidth(char c)
3536
{
36-
if (Font.FontGlyphInfos.TryGetValue(c, out var info))
37-
{
38-
return info.UnscaledAdvanceWidth * Scale;
39-
}
40-
41-
return 0;
37+
return Font.GetGlyphInfo(c).UnscaledAdvanceWidth * Scale;
4238
}
4339

4440
public int GetAdvanceWith(char c)
4541
{
46-
if (Font.FontGlyphInfos.TryGetValue(c, out var info))
47-
{
48-
return (int)(info.UnscaledAdvanceWidth * Scale);
49-
}
50-
51-
return 0;
42+
return (int)(Font.GetGlyphInfo(c).UnscaledAdvanceWidth * Scale);
5243
}
5344

5445
public float GetHeight() => Ascent - Descent;
@@ -68,7 +59,27 @@ public class Font
6859
public required float UnscaledLineGap;
6960
public required StbTrueType.stbtt_fontinfo FontInfo;
7061

71-
public required Dictionary<char, FontGlyphInfo> FontGlyphInfos;
62+
private Dictionary<char, FontGlyphInfo> fontGlyphInfos = [];
63+
64+
public unsafe FontGlyphInfo GetGlyphInfo(char c)
65+
{
66+
if (fontGlyphInfos.TryGetValue(c, out var info))
67+
return info;
68+
69+
int advanceWidth = 0;
70+
int leftSideBearing = 0;
71+
72+
StbTrueType.stbtt_GetCodepointHMetrics(FontInfo, c, &advanceWidth, &leftSideBearing);
73+
74+
info = new FontGlyphInfo
75+
{
76+
UnscaledAdvanceWidth = advanceWidth,
77+
UnscaledLeftSideBearing = leftSideBearing
78+
};
79+
80+
fontGlyphInfos[c] = info;
81+
return info;
82+
}
7283

7384
public float GetScale(float pixelSize)
7485
{
@@ -80,26 +91,79 @@ public float GetHeight(float scale)
8091
return UnscaledAscent * scale - UnscaledDescent * scale;
8192
}
8293

83-
public float GetCharWidth(char c, float scale)
84-
{
85-
if (FontGlyphInfos.TryGetValue(c, out var info))
86-
{
87-
return info.UnscaledAdvanceWidth * scale;
88-
}
89-
90-
return 0;
91-
}
94+
// public float GetCharWidth(char c, float scale)
95+
// {
96+
// if (FontGlyphInfos.TryGetValue(c, out var info))
97+
// {
98+
// return info.UnscaledAdvanceWidth * scale;
99+
// }
100+
//
101+
// return 0;
102+
// }
92103
}
93104

94105
public class FontAtlas
95106
{
96107
public required ScaledFont Font;
97-
public required byte[] AtlasBitmap;
98108
public required int AtlasWidth;
99109
public required int AtlasHeight;
100-
public required Dictionary<char, AtlasGlyphInfo> GlyphInfos;
101110

102111
public GpuTexture GpuTexture;
112+
113+
public required LRUCache<char, AtlasGlyphInfo> Table;
114+
115+
public unsafe AtlasGlyphInfo FindGlyphEntry(char c, float resolutionMultiplier)
116+
{
117+
if (Table.TryGet(c, out var entry))
118+
{
119+
return entry;
120+
}
121+
122+
entry = Table.GetLeastUsed();
123+
124+
int width = 0;
125+
int height = 0;
126+
int xOff = 0;
127+
int yOff = 0;
128+
129+
var bitmap = StbTrueType.stbtt_GetCodepointBitmap(Font.Font.FontInfo, 0, Font.Scale * resolutionMultiplier, c,
130+
&width, &height, &xOff, &yOff);
131+
var bitmapSpan = new Span<byte>(bitmap, width * height);
132+
133+
for (var i = 0; i < bitmapSpan.Length; i++) //correct upwards for clearer text, not sure why we need to do it...
134+
{
135+
ref var b = ref bitmapSpan[i];
136+
137+
var f = (double)b;
138+
f = 255 * Math.Pow(f / 255, 0.5f);
139+
b = (byte)f;
140+
}
141+
142+
GpuTexture.Gl.BindTexture(TextureTarget.Texture2D, GpuTexture.TextureId);
143+
GpuTexture.Gl.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
144+
GpuTexture.Gl.TexSubImage2D(TextureTarget.Texture2D, 0, entry.AtlasX, entry.AtlasY, (uint)width, (uint)height, PixelFormat.Red, PixelType.UnsignedByte, (void*)bitmap);
145+
146+
Console.WriteLine($"Uploading {c} at {entry.AtlasX},{entry.AtlasY}");
147+
148+
var info = new AtlasGlyphInfo
149+
{
150+
Height = height / resolutionMultiplier,
151+
Width = width / resolutionMultiplier,
152+
XOff = xOff / resolutionMultiplier,
153+
YOff = yOff / resolutionMultiplier,
154+
AtlasX = entry.AtlasX,
155+
AtlasY = entry.AtlasY,
156+
AtlasWidth = width,
157+
AtlasHeight = height,
158+
// GlyphBoundingBox = bb,
159+
FontGlyphInfo = Font.Font.GetGlyphInfo(c),
160+
Scale = Font.Scale
161+
};
162+
163+
Table.Add(c, info);
164+
165+
return info;
166+
}
103167
}
104168

105169
public struct FontGlyphInfo
@@ -128,22 +192,6 @@ public struct AtlasGlyphInfo
128192
// public required GlyphBoundingBox GlyphBoundingBox;
129193
}
130194

131-
public unsafe struct CharInfo
132-
{
133-
public required byte* Bitmap;
134-
public required int Width;
135-
public required int Height;
136-
public required int XOff;
137-
public required int YOff;
138-
public required char Char;
139-
140-
141-
public Span<byte> BitmapAsSpan()
142-
{
143-
return new Span<byte>(Bitmap, Width * Height);
144-
}
145-
}
146-
147195
public class FontLoader
148196
{
149197
public static unsafe Font LoadFont(string name)
@@ -169,22 +217,6 @@ public static unsafe Font LoadFont(string name)
169217
int lineGap = 0;
170218
StbTrueType.stbtt_GetFontVMetrics(info, &ascent, &descent, &lineGap);
171219

172-
Dictionary<char, FontGlyphInfo> glyphInfos = new('~' - ' ');
173-
174-
for (char i = ' '; i < '~'; i++)
175-
{
176-
int advanceWidth = 0;
177-
int leftSideBearing = 0;
178-
179-
StbTrueType.stbtt_GetCodepointHMetrics(info, i, &advanceWidth, &leftSideBearing);
180-
181-
glyphInfos[i] = new FontGlyphInfo
182-
{
183-
UnscaledAdvanceWidth = advanceWidth,
184-
UnscaledLeftSideBearing = leftSideBearing
185-
};
186-
}
187-
188220
return new Font
189221
{
190222
Name = name,
@@ -193,129 +225,33 @@ public static unsafe Font LoadFont(string name)
193225
UnscaledDescent = descent,
194226
UnscaledLineGap = lineGap,
195227
FontInfo = info,
196-
FontGlyphInfos = glyphInfos
197228
};
198229
}
199230

200-
public static unsafe FontAtlas CreateFontAtlas(ScaledFont scaledFont, float resolutionMultiplier)
231+
public static FontAtlas CreateFontAtlas(ScaledFont scaledFont)
201232
{
202-
// var scale = StbTrueType.stbtt_ScaleForPixelHeight(font.FontInfo, pixelSize);
203-
204-
int maxCharWidth = 0;
205-
int maxCharHeight = 0;
233+
var table = new LRUCache<char, AtlasGlyphInfo>(10*10);
206234

207-
CharInfo[] charInfos = new CharInfo['~' - ' '];
208-
209-
for (char i = ' '; i < '~'; i++)
235+
for (int i = 1; i < 11; i++)
210236
{
211-
int width = 0;
212-
int height = 0;
213-
int xOff = 0;
214-
int yOff = 0;
215-
216-
var bitmap = StbTrueType.stbtt_GetCodepointBitmap(scaledFont.Font.FontInfo, 0, scaledFont.Scale * resolutionMultiplier, i, &width, &height, &xOff, &yOff);
217-
218-
maxCharHeight = Math.Max(maxCharHeight, height);
219-
maxCharWidth = Math.Max(maxCharWidth, width);
220-
221-
charInfos[i - ' '] = new CharInfo
237+
for (int j = 1; j < 11; j++)
222238
{
223-
Bitmap = bitmap,
224-
Width = width,
225-
Height = height,
226-
XOff = xOff,
227-
YOff = yOff,
228-
Char = i,
229-
};
230-
}
231-
232-
int rowColCount = (int)Math.Ceiling(Math.Sqrt(charInfos.Length));
233-
int minAtlasWidth = rowColCount * maxCharHeight;
234-
int minAtlasHeight = rowColCount * maxCharWidth;
235-
int atlasSize = Math.Max(minAtlasHeight, minAtlasWidth);
236-
atlasSize = RoundUpToNextPowerOfTwo(atlasSize);
237-
//
238-
// if (atlasSize % 2 != 0)
239-
// {
240-
// atlasSize++;
241-
// }
242-
//todo make atalasSize even number
243-
244-
byte[] fontAtlasBitmapData = new byte[atlasSize * atlasSize];
245-
246-
BitRect fontAtlasBitmap = new BitRect
247-
{
248-
Data = fontAtlasBitmapData.AsSpan(),
249-
Height = atlasSize,
250-
Width = atlasSize
251-
};
252-
var glyphInfos = new Dictionary<char, AtlasGlyphInfo>(charInfos.Length);
253-
254-
int currentCol = 0;
255-
int currentRow = 0;
256-
257-
for (var i = 0; i < charInfos.Length; i++)
258-
{
259-
var c = charInfos[i];
260-
261-
// var bb = new GlyphBoundingBox();
262-
263-
// StbTrueType.stbtt_GetCodepointBitmapBox(font.FontInfo, i, 0, scale, &bb.x0, &bb.y0, &bb.x1, &bb.y1);
264-
265-
var bitmap = new BitRect
266-
{
267-
Data = c.BitmapAsSpan(),
268-
Height = c.Height,
269-
Width = c.Width
270-
};
271-
272-
int xOffset = currentCol * maxCharWidth;
273-
int yOffset = currentRow * maxCharHeight;
274-
275-
Copy(bitmap, fontAtlasBitmap, xOffset, yOffset);
276-
277-
glyphInfos[c.Char] = new AtlasGlyphInfo
278-
{
279-
Height = c.Height / resolutionMultiplier,
280-
Width = c.Width / resolutionMultiplier,
281-
XOff = c.XOff / resolutionMultiplier,
282-
YOff = c.YOff / resolutionMultiplier,
283-
AtlasX = xOffset,
284-
AtlasY = yOffset,
285-
AtlasWidth = c.Width,
286-
AtlasHeight = c.Height,
287-
// GlyphBoundingBox = bb,
288-
FontGlyphInfo = scaledFont.Font.FontGlyphInfos[c.Char],
289-
Scale = scaledFont.Scale
290-
};
291-
292-
if (currentCol == rowColCount - 1)
293-
{
294-
currentCol = 0;
295-
currentRow++;
296-
}
297-
else
298-
{
299-
currentCol++;
239+
table.Add((char)(int.MaxValue - i - 100 * j), default(AtlasGlyphInfo) with
240+
{
241+
AtlasX = i * 100,
242+
AtlasY = j * 100,
243+
});
300244
}
301245
}
302246

303-
for (var i = 0; i < fontAtlasBitmapData.Length; i++) //correct upwards for clearer text, not sure why we need to do it...
304-
{
305-
ref var b = ref fontAtlasBitmapData[i];
306-
307-
var f = (double)b;
308-
f = 255 * Math.Pow(f / 255, 0.5f);
309-
b = (byte)f;
310-
}
311-
312247
return new FontAtlas
313248
{
314249
Font = scaledFont,
315-
AtlasBitmap = fontAtlasBitmapData,
316-
AtlasWidth = atlasSize,
317-
AtlasHeight = atlasSize,
318-
GlyphInfos = glyphInfos,
250+
// AtlasBitmap = fontAtlasBitmapData,
251+
AtlasWidth = 1024,
252+
AtlasHeight = 1024,
253+
Table = table
254+
// GlyphInfos = glyphInfos,
319255
};
320256
}
321257

0 commit comments

Comments
 (0)