Dashboard/Quik/Typography/Typesetter.cs

182 lines
5.7 KiB
C#

using Quik.CommandMachine;
using Quik.Media;
using System;
using System.Collections.Generic;
using System.Text;
namespace Quik.Typography
{
public static class Typesetter
{
private ref struct LineEnumerator
{
private ReadOnlySpan<char> Entire, Segment;
private bool Final;
public ReadOnlySpan<char> Current => Segment;
public LineEnumerator(ReadOnlySpan<char> value)
{
Entire = value;
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public void Reset()
{
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public bool MoveNext()
{
if (Final)
{
return false;
}
else if (Segment == ReadOnlySpan<char>.Empty)
{
int index = Entire.IndexOf('\n');
if (index == -1)
{
Segment = Entire;
}
else
{
Segment = Entire.Slice(0, index);
}
return true;
}
else
{
Entire.Overlaps(Segment, out int offset);
if (offset + Segment.Length >= Entire.Length)
{
return false;
}
ReadOnlySpan<char> rest = Entire.Slice(offset + Segment.Length + 1);
int index = rest.IndexOf('\n');
if (index == -1)
{
Segment = rest;
Final = true;
}
else
{
Segment = rest.Slice(0, index);
}
return true;
}
}
}
public static QVec2 MeasureHorizontal(ReadOnlySpan<char> str, float size, QFont font)
{
var enumerator = new LineEnumerator(str);
float width = 0.0f;
float height = 0.0f;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float lineHeight = 0.0f;
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
width += glyph.Metrics.Advance.X;
lineHeight = Math.Max(lineHeight, glyph.Metrics.Size.Y);
}
height += lineHeight;
}
return new QVec2(width, height);
}
public static void TypesetHorizontalDirect(this CommandList list, ReadOnlySpan<char> str, QVec2 origin, float size, QFont font)
{
Dictionary<QImage, FontDrawInfo> drawInfo = new Dictionary<QImage, FontDrawInfo>();
var enumerator = new LineEnumerator(str);
QVec2 pen = origin;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float rise = 0.0f;
float fall = 0.0f;
// Find out all the code pages required, and the line height.
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
float crise = glyph.Metrics.HorizontalBearing.Y;
float cfall = glyph.Metrics.Size.Y - crise;
rise = Math.Max(crise, rise);
fall = Math.Max(cfall, fall);
}
pen += new QVec2(0, rise);
foreach (Rune r in line.EnumerateRunes())
{
FontDrawInfo info;
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
ref readonly QGlyphMetrics metrics = ref glyph.Metrics;
QImage image = glyph.Image;
if (image == null)
{
pen += new QVec2(metrics.Advance.X, 0);
continue;
}
if (!drawInfo.TryGetValue(image, out info))
{
info = new FontDrawInfo();
info.Image = image;
info.rectangles = new List<QRectangle>();
drawInfo[image] = info;
}
QRectangle dest = new QRectangle(
pen + new QVec2(metrics.HorizontalBearing.X + metrics.Size.X, metrics.Size.Y - metrics.HorizontalBearing.Y),
pen + new QVec2(metrics.HorizontalBearing.X, -metrics.HorizontalBearing.Y));
info.rectangles.Add(dest);
info.rectangles.Add(glyph.UVs);
pen.X += metrics.Advance.X;
}
pen.X = origin.X;
pen.Y += fall;
}
// Now for each rectangle we can dispatch draw calls.
foreach (FontDrawInfo info in drawInfo.Values)
{
list.Image(info.Image, info.rectangles.ToArray(), true);
}
}
private struct FontDrawInfo
{
public QImage Image;
public List<QRectangle> rectangles;
}
}
}