diff --git a/Quik/QuikContext.cs b/Quik/QuikContext.cs
index 5fb4117..6687880 100644
--- a/Quik/QuikContext.cs
+++ b/Quik/QuikContext.cs
@@ -1,4 +1,6 @@
-namespace Quik
+using Quik.Typography;
+
+namespace Quik
{
///
/// An object which QUIK commands may be issued to.
@@ -10,11 +12,13 @@
///
public QuikDraw Draw { get; } = new QuikDraw();
- public QuikStrokeStyle DefaultStroke { get; set; } = new QuikStrokeStyle(new QuikColor(0x000000FF), 4);
+ public QuikStrokeStyle DefaultStroke { get; set; } = new QuikStrokeStyle(new QuikColor(0xaaaaaaff), 4);
public QuikFillStyle DefaultFill { get; set; } = new QuikFillStyle()
{
- Color = new QuikColor(0xA0A0A0FF)
+ Color = new QuikColor(0xeeeeeeff)
};
+
+ public QuikFont DefaultFont { get; set; }
}
}
\ No newline at end of file
diff --git a/Quik/QuikDraw.cs b/Quik/QuikDraw.cs
index eafb467..7d978af 100644
--- a/Quik/QuikDraw.cs
+++ b/Quik/QuikDraw.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.IO.Compression;
using System.Linq;
namespace Quik
@@ -33,5 +34,8 @@ namespace Quik
public void StencilClear() => Commands.Enqueue(new QuikCommandStencilClear());
public void StencilBegin() => Commands.Enqueue(new QuikCommandStencilBegin());
public void StencilEnd() => Commands.Enqueue(new QuikCommandStencilEnd());
+ public void PutChar(char chr, QuikVec2 position) => Commands.Enqueue(new QuikCommandPutChar(chr, position));
+ public void PutText(string text, QuikVec2 position) => Commands.Enqueue(new QuikCommandPutText(text, position));
+ public void FlowText(string text, QuikRectangle bounds) => Commands.Enqueue(new QuikCommandFlowText(text, bounds));
}
}
\ No newline at end of file
diff --git a/Quik/Typography/QuikFont.cs b/Quik/Typography/QuikFont.cs
new file mode 100644
index 0000000..e13b751
--- /dev/null
+++ b/Quik/Typography/QuikFont.cs
@@ -0,0 +1,14 @@
+namespace Quik.Typography
+{
+ public abstract class QuikFont
+ {
+ public abstract QuikFontStyle Style { get; }
+
+ public string FontFamily => Style.Family;
+ public float FontSize => Style.Size;
+ public QuikFontStyle FontStyle => Style;
+
+ public abstract bool HasCharacter(int character);
+ public abstract void GetCharacter(int character, out int texture, out QuikGlyph glyph);
+ }
+}
\ No newline at end of file
diff --git a/Quik/Typography/QuikFontStyle.cs b/Quik/Typography/QuikFontStyle.cs
new file mode 100644
index 0000000..78c4468
--- /dev/null
+++ b/Quik/Typography/QuikFontStyle.cs
@@ -0,0 +1,23 @@
+namespace Quik.Typography
+{
+ public class QuikFontStyle
+ {
+ public string Family { get; }
+ public QuikFontType Type { get; }
+ public float Size { get; }
+
+ public QuikFontStyle(string family, float size, QuikFontType type = QuikFontType.Normal)
+ {
+ Family = family;
+ Size = size;
+ Type = type;
+ }
+ }
+
+ public enum QuikFontType
+ {
+ Normal,
+ Italic,
+ Bold
+ }
+}
\ No newline at end of file
diff --git a/Quik/Typography/QuikGlyph.cs b/Quik/Typography/QuikGlyph.cs
new file mode 100644
index 0000000..437d22c
--- /dev/null
+++ b/Quik/Typography/QuikGlyph.cs
@@ -0,0 +1,54 @@
+namespace Quik.Typography
+{
+ ///
+ /// Glyph properties with metrics based on FreeType glyph metrics.
+ ///
+ public struct QuikGlyph
+ {
+ ///
+ /// The code point for the character.
+ ///
+ public int Character { get; }
+
+ ///
+ /// Location of the glyph on the atlas.
+ ///
+ public QuikRectangle Location { get; }
+
+ ///
+ /// Size of the glyph in units.
+ ///
+ public QuikVec2 Size { get; }
+
+ ///
+ /// Bearing vector for horizontal layout.
+ ///
+ public QuikVec2 HorizontalBearing { get; }
+
+ ///
+ /// Bearing vector for vertical layout.
+ ///
+ public QuikVec2 VerticalBearing { get; }
+
+ ///
+ /// Advance vector for vertical and horizontal layouts.
+ ///
+ public QuikVec2 Advance { get; }
+
+ public QuikGlyph(
+ int character,
+ QuikRectangle location,
+ QuikVec2 size,
+ QuikVec2 horizontalBearing,
+ QuikVec2 verticalBearing,
+ QuikVec2 advance)
+ {
+ Character = character;
+ Location = location;
+ Size = size;
+ HorizontalBearing = horizontalBearing;
+ VerticalBearing = verticalBearing;
+ Advance = advance;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Quik/VertexGenerator/QuikVertexGenerator.cs b/Quik/VertexGenerator/QuikVertexGenerator.cs
index 8e054c9..8cd88a9 100644
--- a/Quik/VertexGenerator/QuikVertexGenerator.cs
+++ b/Quik/VertexGenerator/QuikVertexGenerator.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Quik.Typography;
namespace Quik.VertexGenerator
{
@@ -223,9 +224,17 @@ namespace Quik.VertexGenerator
case QuikCommandType.Rectangles:
RenderRectangles(command as QuikCommandRectangles);
goto exit_with_call;
+
+ case QuikCommandType.PutChar:
+ RenderCharacter(command as QuikCommandPutChar);
+ goto exit_with_call;
+
+ case QuikCommandType.PutText:
+ RenderTextPut(command as QuikCommandPutText);
+ goto exit_with_call;
+
default:
{
- QuikDrawCall? subcall = null;
if (OnHandleCommand(this, command))
goto exit_with_call;
break;
@@ -1393,6 +1402,97 @@ namespace Quik.VertexGenerator
}
#endregion
+
+ #region Text
+
+ private void RenderCharacter(QuikCommandPutChar chr)
+ {
+ Context.DefaultFont.GetCharacter(chr.Character, out int texture, out QuikGlyph metrics);
+
+ QuikVertex a, b, c, d;
+ a = b = c = d = new QuikVertex() {Color = new QuikColor(0xffffffff)};
+
+ a.Position = chr.Position + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
+ a.TextureCoordinates = metrics.Location.Min;
+
+ b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
+ c.Position = a.Position + metrics.Size;
+ d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
+
+ b.TextureCoordinates = new QuikVec2(metrics.Location.Right, metrics.Location.Bottom);
+ c.TextureCoordinates = metrics.Location.Max;
+ d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
+
+ short startVertex = (short)_vertexBufferPointer;
+ short startElement = (short) _elementBufferPointer;
+ AddVertex(a, b, c, d);
+ AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
+
+ QuikDrawCall call = CallTemplate;
+ call.Texture = texture;
+ call.Offset = (short) (startElement * 2);
+ call.Count = 6;
+ DrawCalls.Add(call);
+ }
+
+ private void RenderTextPut(QuikCommandPutText text)
+ {
+ short startElement = (short)_elementBufferPointer;
+ QuikFont font = Context.DefaultFont;
+ QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0xff7777ff)};
+ QuikVec2 pointer = text.Position;
+ int texture = -1;
+
+ for (int i = 0; i < text.Text.Length; i++)
+ {
+ int chr = text.Text[i];
+ QuikGlyph metrics;
+
+ int ntex;
+ font.GetCharacter(chr, out ntex, out metrics);
+
+ if (ntex != texture && texture != -1)
+ {
+ QuikDrawCall call = CallTemplate;
+ call.Texture = texture;
+ call.Offset = (short) (startElement * 2);
+ call.Count = (short)(_elementBufferPointer - startElement);
+ DrawCalls.Add(call);
+
+ startElement = (short) _elementBufferPointer;
+ }
+
+ texture = ntex;
+ QuikVertex a, b, c, d;
+ a = b = c = d = vertex;
+
+ a.Position = pointer + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
+ a.TextureCoordinates = metrics.Location.Min;
+
+ b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
+ c.Position = a.Position + metrics.Size;
+ d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
+
+ b.TextureCoordinates = new QuikVec2(metrics.Location.Right, metrics.Location.Bottom);
+ c.TextureCoordinates = metrics.Location.Max;
+ d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
+
+ pointer.X += metrics.Advance.X;
+
+ short startVertex = (short)_vertexBufferPointer;
+ AddVertex(a, b, c, d);
+ AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
+ }
+
+ {
+ QuikDrawCall call = CallTemplate;
+ call.Texture = texture;
+ call.Offset = (short) (startElement * 2);
+ call.Count = (short)(_elementBufferPointer - startElement);
+ DrawCalls.Add(call);
+ }
+ }
+ #endregion
}
public delegate void VertexGeneratorCommandHandler(QuikVertexGenerator generator, QuikCommand command, ref bool anyCalls);
@@ -1410,5 +1510,6 @@ namespace Quik.VertexGenerator
public short Count;
public QuikRectangle Bounds;
public bool ClearStencil;
+ public int Texture;
}
}
\ No newline at end of file
diff --git a/QuikTestApplication/Program.cs b/QuikTestApplication/Program.cs
index de276ca..44f1148 100644
--- a/QuikTestApplication/Program.cs
+++ b/QuikTestApplication/Program.cs
@@ -35,9 +35,11 @@ in vec2 ftexcoord;
in vec4 fcolor;
out vec4 outcolor;
+uniform sampler2D texture0;
+
void main()
{
- outcolor = fcolor;
+ outcolor = fcolor * texture(texture0, ftexcoord);
}
";
@@ -96,7 +98,7 @@ void main()
QuikVertex.PositionOffset);
GL.EnableVertexAttribArray(loc);
GL.VertexAttribPointer(
- loc = GL.GetAttribLocation(sp, "texcoords"),
+ loc = GL.GetAttribLocation(sp, "texcoord"),
2,
VertexAttribPointerType.Float,
false,
@@ -133,6 +135,31 @@ void main()
Color = new QuikColor(0xeeeeeeff)
};
+
+ int whiteTexture = GL.GenTexture();
+ uint[] whitePixel = {0xFFFFFFFF};
+ GL.BindTexture(TextureTarget.Texture2D, whiteTexture);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 1, 1, 0, PixelFormat.Rgba, PixelType.UnsignedByte, whitePixel);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
+
+ TestFont font = new TestFont();
+ font.TextureBase = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, font.TextureBase);
+ GL.TexImage2D(
+ TextureTarget.Texture2D,
+ 0,
+ PixelInternalFormat.Rgb,
+ (int)TestFont.TextureSize.X, (int)TestFont.TextureSize.Y, 0,
+ PixelFormat.Rgba, PixelType.UnsignedByte,
+ TestFont.Texture);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
+
+ context.DefaultFont = font;
+
+ window.Context.SwapInterval = 0;
+
for (;!window.IsExiting;)
{
NativeWindow.ProcessWindowEvents(false);
@@ -144,9 +171,9 @@ void main()
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
0,
- (float)window.Size.X / 2,
+ (float)window.Size.X,
0,
- (float)window.Size.Y / 2,
+ (float)window.Size.Y,
1,
-1);
GL.UniformMatrix4(loc, false, ref matrix);
@@ -198,6 +225,8 @@ void main()
FillStyle = fill,
StrokeStyle = strokeBorder
});
+
+ context.Draw.PutText("Aaah! the cat turned into a cat!", new QuikVec2(25,30));
QuikCommand command;
while (context.Draw.Commands.TryDequeue(out command))
@@ -210,6 +239,7 @@ void main()
foreach (QuikDrawCall call in gen.DrawCalls)
{
+ GL.BindTexture(TextureTarget.Texture2D, call.Texture == 0 ? whiteTexture : call.Texture);
GL.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset);
}
diff --git a/QuikTestApplication/QuikTestApplication.csproj b/QuikTestApplication/QuikTestApplication.csproj
index 786da43..b4f36c4 100644
--- a/QuikTestApplication/QuikTestApplication.csproj
+++ b/QuikTestApplication/QuikTestApplication.csproj
@@ -9,4 +9,11 @@
+
+
+
+
+
+
+
diff --git a/QuikTestApplication/TestFont.cs b/QuikTestApplication/TestFont.cs
new file mode 100644
index 0000000..66977ee
--- /dev/null
+++ b/QuikTestApplication/TestFont.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using Quik;
+using Quik.Typography;
+
+namespace QuikTestApplication
+{
+ public class TestFont : QuikFont
+ {
+ public override QuikFontStyle Style => _style;
+
+ private static QuikFontStyle _style;
+ private static HashSet _characters = new HashSet();
+ private static Dictionary _glyphs = new Dictionary();
+
+ public static byte[] Texture { get; private set; } = Array.Empty();
+ public static QuikVec2 TextureSize { get; private set; }
+
+ public int TextureBase { get; set; } = 1;
+
+ static TestFont()
+ {
+ using (Stream str = typeof(TestFont).Assembly.GetManifestResourceStream("QuikTestApplication.font.xml"))
+ {
+ XmlDocument document = new XmlDocument();
+ document.Load(str);
+
+ string family = document.DocumentElement.Attributes["name"].Value;
+ float fontSize = float.Parse(document.DocumentElement.Attributes["size"].Value);
+ QuikFontType style = QuikFontType.Normal;
+ if (document.DocumentElement.Attributes["bold"].Value == "true")
+ style = QuikFontType.Bold;
+ if (document.DocumentElement.Attributes["italic"].Value == "true")
+ style = QuikFontType.Italic;
+
+ _style = new QuikFontStyle(family, fontSize, style);
+
+ QuikVec2 atlasSize = default;
+ atlasSize.X = float.Parse(document.DocumentElement.Attributes["width"].Value);
+ atlasSize.Y = float.Parse(document.DocumentElement.Attributes["height"].Value);
+
+ TextureSize = atlasSize;
+
+ foreach (XmlElement element in document.SelectNodes("/font/character"))
+ {
+ QuikRectangle UVs;
+ QuikVec2 origin;
+ float advance;
+ int chr = element.Attributes["text"].Value[0];
+
+ QuikVec2 pos;
+ QuikVec2 size;
+ pos.X = float.Parse(element.Attributes["x"].Value);
+ pos.Y = float.Parse(element.Attributes["y"].Value);
+ size.X = float.Parse(element.Attributes["width"].Value);
+ size.Y = float.Parse(element.Attributes["height"].Value);
+ UVs = new QuikRectangle(
+ (pos.X + size.X)/atlasSize.X,
+ pos.Y/atlasSize.Y,
+ pos.X/atlasSize.X,
+ (size.Y + pos.Y)/atlasSize.Y);
+
+ origin.X = float.Parse(element.Attributes["origin-x"].Value);
+ origin.Y = float.Parse(element.Attributes["origin-y"].Value);
+
+ advance = float.Parse(element.Attributes["advance"].Value);
+
+ QuikGlyph glyph = new QuikGlyph(chr, UVs, size, origin, default, new QuikVec2(advance, 0));
+ _glyphs.Add(chr, glyph);
+ _characters.Add(chr);
+ }
+ }
+
+ using (Stream str = typeof(TestFont).Assembly.GetManifestResourceStream("QuikTestApplication.font.dat"))
+ {
+ Texture = new byte[(int)str.Length];
+ str.Read(Texture, 0, (int)str.Length);
+ }
+ }
+
+ public override bool HasCharacter(int character)
+ {
+ return _characters.Contains((char) character);
+ }
+
+ public override void GetCharacter(int character, out int texture, out QuikGlyph glyph)
+ {
+ if (!_glyphs.TryGetValue(character, out glyph))
+ {
+ glyph = _glyphs['?'];
+ }
+
+ texture = TextureBase;
+ }
+ }
+}
\ No newline at end of file
diff --git a/QuikTestApplication/font.dat b/QuikTestApplication/font.dat
new file mode 100644
index 0000000..1c997c7
Binary files /dev/null and b/QuikTestApplication/font.dat differ
diff --git a/QuikTestApplication/font.png b/QuikTestApplication/font.png
new file mode 100644
index 0000000..eaa57da
Binary files /dev/null and b/QuikTestApplication/font.png differ
diff --git a/QuikTestApplication/font.xml b/QuikTestApplication/font.xml
new file mode 100644
index 0000000..7ace8aa
--- /dev/null
+++ b/QuikTestApplication/font.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file