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 Entire, Segment; private bool Final; public ReadOnlySpan Current => Segment; public LineEnumerator(ReadOnlySpan value) { Entire = value; Segment = ReadOnlySpan.Empty; Final = false; } public void Reset() { Segment = ReadOnlySpan.Empty; Final = false; } public bool MoveNext() { if (Final) { return false; } else if (Segment == ReadOnlySpan.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 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 str, float size, QFont font) { var enumerator = new LineEnumerator(str); float width = 0.0f; float height = 0.0f; while (enumerator.MoveNext()) { ReadOnlySpan 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 str, QVec2 origin, float size, QFont font) { Dictionary drawInfo = new Dictionary(); var enumerator = new LineEnumerator(str); QVec2 pen = origin; while (enumerator.MoveNext()) { ReadOnlySpan 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(); 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 rectangles; } } }