Preliminary typesetting work.
This commit is contained in:
parent
279e619c3b
commit
bd69c0d93f
@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Quik.FreeType;
|
using Quik.FreeType;
|
||||||
using Quik.Media;
|
|
||||||
using Quik.Media.Color;
|
using Quik.Media.Color;
|
||||||
|
using Quik.Media.Font;
|
||||||
|
|
||||||
namespace Quik.Media.Defaults
|
namespace Quik.Media.Defaults
|
||||||
{
|
{
|
||||||
@ -11,7 +12,7 @@ namespace Quik.Media.Defaults
|
|||||||
private MemoryStream ms;
|
private MemoryStream ms;
|
||||||
private FTFace face;
|
private FTFace face;
|
||||||
|
|
||||||
public override FontInfo Info => throw new NotImplementedException();
|
public override FontFace Face => throw new NotImplementedException();
|
||||||
|
|
||||||
public QFontFreeType(Stream stream)
|
public QFontFreeType(Stream stream)
|
||||||
{
|
{
|
||||||
@ -30,65 +31,39 @@ namespace Quik.Media.Defaults
|
|||||||
return FT.GetCharIndex(face, (ulong)rune) != 0;
|
return FT.GetCharIndex(face, (ulong)rune) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override QFontPage RasterizePage(int codepage, float size, in FontRasterizerOptions options)
|
protected override QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options)
|
||||||
{
|
{
|
||||||
FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
|
FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
|
||||||
QGlyphMetrics[] allMetrics = new QGlyphMetrics[256];
|
|
||||||
|
|
||||||
// Figure out the map size needed.
|
uint index = FT.GetCharIndex(face, (ulong)codepoint);
|
||||||
int pixels = 0;
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
|
|
||||||
FT.LoadGlyph(face, index, FTLoadFlags.Default);
|
FT.LoadGlyph(face, index, FTLoadFlags.Default);
|
||||||
|
|
||||||
ref readonly FTGlyphMetrics metrics = ref face.Glyph.Metrics;
|
ref readonly FTGlyphMetrics ftmetrics = ref face.Glyph.Metrics;
|
||||||
allMetrics[i] = new QGlyphMetrics(
|
metrics = new QGlyphMetrics(codepoint,
|
||||||
codepage + i,
|
new QVec2(ftmetrics.Width/64f, ftmetrics.Height/64f),
|
||||||
new QVec2(metrics.Width, metrics.Height),
|
new QVec2(ftmetrics.HorizontalBearingX/64f, ftmetrics.HorizontalBearingY/64f),
|
||||||
new QVec2(metrics.HorizontalBearingX/64f, metrics.HorizontalBearingY/64f),
|
new QVec2(ftmetrics.VerticalBearingX/64f, ftmetrics.VerticalBearingY/64f),
|
||||||
new QVec2(metrics.VerticalBearingX/64f, metrics.VerticalBearingY/64f),
|
new QVec2(ftmetrics.HorizontalAdvance/64f, ftmetrics.VerticalAdvance/64f)
|
||||||
new QVec2(metrics.HorizontalAdvance/64f, metrics.VerticalAdvance/64f)
|
|
||||||
);
|
);
|
||||||
pixels = (int)Math.Max(pixels, Math.Max(face.Glyph.Metrics.Width/64f, face.Glyph.Metrics.Height/64f));
|
|
||||||
}
|
|
||||||
|
|
||||||
int bits = Math.ILogB(pixels);
|
|
||||||
if (1 << bits != pixels)
|
|
||||||
{
|
|
||||||
pixels = 1 << bits + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can create a bitmap and render our glyphs.
|
|
||||||
|
|
||||||
QImageBuffer buffer = new QImageBuffer(QImageFormat.RedU8, (int)pixels, (int)pixels, 256);
|
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
|
|
||||||
FT.LoadGlyph(face, index, FTLoadFlags.Default);
|
|
||||||
FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Mono);
|
|
||||||
|
|
||||||
|
FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Normal);
|
||||||
ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
|
ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
|
||||||
|
|
||||||
buffer.LockBits3d(out QImageLock dst, QImageLockOptions.Default, i);
|
if (bitmap.Width == 0 || bitmap.Pitch == 0 || bitmap.Buffer == IntPtr.Zero)
|
||||||
|
|
||||||
if (bitmap.Buffer != IntPtr.Zero) unsafe
|
|
||||||
{
|
{
|
||||||
for (int j = 0; j < bitmap.Rows; j++)
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageBuffer image = new QImageBuffer(QImageFormat.RedU8, (int)bitmap.Width, (int)bitmap.Rows);
|
||||||
|
image.LockBits2d(out QImageLock lk, QImageLockOptions.Default);
|
||||||
|
|
||||||
|
unsafe
|
||||||
{
|
{
|
||||||
Buffer.MemoryCopy(
|
Buffer.MemoryCopy((void*)bitmap.Buffer, (void*)lk.ImagePtr, lk.Width * lk.Height, bitmap.Width * bitmap.Rows);
|
||||||
(byte*)bitmap.Buffer + (j * bitmap.Pitch),
|
|
||||||
(byte*)dst.ImagePtr + (j * dst.Width),
|
|
||||||
dst.Width,
|
|
||||||
bitmap.Width);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.UnlockBits();
|
image.UnlockBits();
|
||||||
}
|
return image;
|
||||||
|
|
||||||
return new QFontPage(this, codepage, size, options, buffer, allMetrics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
@ -4,14 +4,14 @@ using System.Collections;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Quik.Media;
|
using Quik.Media.Font;
|
||||||
|
|
||||||
// WebRequest is obsolete but runs on .NET framework.
|
// WebRequest is obsolete but runs on .NET framework.
|
||||||
#pragma warning disable SYSLIB0014
|
#pragma warning disable SYSLIB0014
|
||||||
|
|
||||||
namespace Quik.Media.Defaults
|
namespace Quik.Media.Defaults
|
||||||
{
|
{
|
||||||
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontInfo>
|
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontFace>
|
||||||
{
|
{
|
||||||
public bool AllowRemoteTransfers { get; set; } = false;
|
public bool AllowRemoteTransfers { get; set; } = false;
|
||||||
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
|
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
|
||||||
@ -31,9 +31,9 @@ namespace Quik.Media.Defaults
|
|||||||
{
|
{
|
||||||
return GetMedia((FileInfo)key, hint);
|
return GetMedia((FileInfo)key, hint);
|
||||||
}
|
}
|
||||||
else if (t == typeof(FontInfo))
|
else if (t == typeof(FontFace))
|
||||||
{
|
{
|
||||||
return GetMedia((FontInfo)key, hint);
|
return GetMedia((FontFace)key, hint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ namespace Quik.Media.Defaults
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDisposable GetMedia(FontInfo key, MediaHint hint)
|
public IDisposable GetMedia(FontFace key, MediaHint hint)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
151
Quik.Media.Defaults/Win32/EnumerateFonts.cs
Normal file
151
Quik.Media.Defaults/Win32/EnumerateFonts.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#if false
|
||||||
|
namespace Quik.Media.Defaults.Win32
|
||||||
|
{
|
||||||
|
|
||||||
|
public class EnumerateFonts
|
||||||
|
{
|
||||||
|
private const byte DEFAULT_CHARSET = 1;
|
||||||
|
|
||||||
|
public static void Enumerate(FontFace font)
|
||||||
|
{
|
||||||
|
/* It's windows, just borrow the desktop window. */
|
||||||
|
IntPtr hdc = GetDC(GetDesktopWindow());
|
||||||
|
|
||||||
|
List<(LogFontA, TextMetricA)> list = new List<(LogFontA, TextMetricA)>();
|
||||||
|
|
||||||
|
LogFontA font2 = new LogFontA()
|
||||||
|
{
|
||||||
|
//FaceName = font.Family,
|
||||||
|
Weight = ((font.Style & FontStyle.Bold) != 0) ? FontWeight.Bold : FontWeight.Regular,
|
||||||
|
Italic = (font.Style & FontStyle.Italic) != 0,
|
||||||
|
CharSet = DEFAULT_CHARSET
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(font2.FaceName);
|
||||||
|
|
||||||
|
EnumFontFamiliesExProc proc = (in LogFontA font, in TextMetricA metric, int type, IntPtr lparam) =>
|
||||||
|
{
|
||||||
|
list.Add((font, metric));
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
EnumFontFamiliesExA(hdc, font2, proc, IntPtr.Zero, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string gdi32 = "Gdi32.dll";
|
||||||
|
private const string user32 = "User32.dll";
|
||||||
|
|
||||||
|
[DllImport(gdi32)]
|
||||||
|
private static extern int EnumFontFamiliesExA(
|
||||||
|
IntPtr hdc,
|
||||||
|
in LogFontA font,
|
||||||
|
[MarshalAs(UnmanagedType.FunctionPtr)] EnumFontFamiliesExProc proc,
|
||||||
|
IntPtr lparam,
|
||||||
|
int flags /* Should be zero. */);
|
||||||
|
|
||||||
|
[DllImport(user32)]
|
||||||
|
private static extern IntPtr /* HWND */ GetDesktopWindow();
|
||||||
|
|
||||||
|
[DllImport(user32)]
|
||||||
|
private static extern IntPtr /* HDC */ GetDC(IntPtr hwnd);
|
||||||
|
|
||||||
|
private delegate int EnumFontFamiliesExProc(in LogFontA font, in TextMetricA metric, int fontType, IntPtr lParam);
|
||||||
|
|
||||||
|
private struct LogFontA
|
||||||
|
{
|
||||||
|
public long Height;
|
||||||
|
public long Width;
|
||||||
|
public long Escapement;
|
||||||
|
public long Orientation;
|
||||||
|
public FontWeight Weight;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool Italic;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool Underline;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public bool StrikeOut;
|
||||||
|
public byte CharSet;
|
||||||
|
public byte OutPrecision;
|
||||||
|
public byte ClipPrecision;
|
||||||
|
public byte PitchAndFamily;
|
||||||
|
private unsafe fixed byte aFaceName[32];
|
||||||
|
public unsafe string FaceName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
fixed (byte* str = aFaceName)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
for (; str[len] != 0 && len < 32; len++) ;
|
||||||
|
return Encoding.UTF8.GetString(str, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
fixed (byte *str = aFaceName)
|
||||||
|
{
|
||||||
|
Span<byte> span = new Span<byte>(str, 32);
|
||||||
|
Encoding.UTF8.GetBytes(value, span);
|
||||||
|
span[31] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TextMetricA
|
||||||
|
{
|
||||||
|
public long Height;
|
||||||
|
public long Ascent;
|
||||||
|
public long Descent;
|
||||||
|
public long InternalLeading;
|
||||||
|
public long ExternalLeading;
|
||||||
|
public long AveCharWidth;
|
||||||
|
public long MaxCharWidth;
|
||||||
|
public long Weight;
|
||||||
|
public long Overhang;
|
||||||
|
public long DigitizedAspectX;
|
||||||
|
public long DigitizedAspectY;
|
||||||
|
public byte FirstChar;
|
||||||
|
public byte LastChar;
|
||||||
|
public byte DefaultChar;
|
||||||
|
public byte BreakChar;
|
||||||
|
public byte Italic;
|
||||||
|
public byte Underlined;
|
||||||
|
public byte StruckOut;
|
||||||
|
public byte PitchAndFamily;
|
||||||
|
public byte CharSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FontWeight : long
|
||||||
|
{
|
||||||
|
DontCare = 0,
|
||||||
|
Thin = 100,
|
||||||
|
ExtraLight = 200,
|
||||||
|
UltraLight = 200,
|
||||||
|
Light = 300,
|
||||||
|
Normal = 400,
|
||||||
|
Regular = 400,
|
||||||
|
Medium = 500,
|
||||||
|
Semibold = 600,
|
||||||
|
Demibold = 600,
|
||||||
|
Bold = 700,
|
||||||
|
Extrabold = 800,
|
||||||
|
Ultrabold = 800,
|
||||||
|
Heavy = 900,
|
||||||
|
Black = 900
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
@ -87,5 +87,18 @@ namespace Quik.OpenTK
|
|||||||
public void PortShow(IQuikPortHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
|
public void PortShow(IQuikPortHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
|
||||||
|
|
||||||
public void PortPaint(IQuikPortHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
|
public void PortPaint(IQuikPortHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
|
||||||
|
|
||||||
|
public void GetMaximumImage(out int width, out int height)
|
||||||
|
{
|
||||||
|
GL.Get(GLEnum.GL_MAX_TEXTURE_SIZE, out int value);
|
||||||
|
width = height = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetMaximumImage(out int width, out int height, out int depth)
|
||||||
|
{
|
||||||
|
GetMaximumImage(out width, out height);
|
||||||
|
GL.Get(GLEnum.GL_MAX_ARRAY_TEXTURE_LAYERS, out int value);
|
||||||
|
depth = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,6 +78,7 @@ namespace Quik.OpenTK
|
|||||||
if (!_glDriver.IsInit)
|
if (!_glDriver.IsInit)
|
||||||
_glDriver.Init();
|
_glDriver.Init();
|
||||||
|
|
||||||
|
GL.Clear(GLEnum.GL_COLOR_BUFFER_BIT);
|
||||||
_glDriver.Draw(_vertexEngine.DrawQueue, view);
|
_glDriver.Draw(_vertexEngine.DrawQueue, view);
|
||||||
|
|
||||||
_window.Context.SwapBuffers();
|
_window.Context.SwapBuffers();
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Quik.Media
|
namespace Quik.Media.Color
|
||||||
{
|
{
|
||||||
public class QImageBuffer : QImage
|
public class QImageBuffer : QImage
|
||||||
{
|
{
|
||||||
private byte[] buffer;
|
private byte[] buffer;
|
||||||
GCHandle handle;
|
GCHandle handle;
|
||||||
|
private bool isSdf = false;
|
||||||
|
|
||||||
public override QImageFormat InternalFormat { get; }
|
public override QImageFormat InternalFormat { get; }
|
||||||
public override int Width { get; }
|
public override int Width { get; }
|
||||||
public override int Height { get; }
|
public override int Height { get; }
|
||||||
public override int Depth { get; }
|
public override int Depth { get; }
|
||||||
|
public override bool IsSdf => isSdf;
|
||||||
|
|
||||||
public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
|
public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace Quik.Media
|
|||||||
Height = height;
|
Height = height;
|
||||||
Depth = depth;
|
Depth = depth;
|
||||||
|
|
||||||
buffer = new byte[width * height * depth * format.BytesPerPixel()];
|
buffer = new byte[width * height * depth];
|
||||||
}
|
}
|
||||||
~QImageBuffer()
|
~QImageBuffer()
|
||||||
{
|
{
|
||||||
@ -29,7 +31,7 @@ namespace Quik.Media
|
|||||||
|
|
||||||
private QImageLock Lock()
|
private QImageLock Lock()
|
||||||
{
|
{
|
||||||
handle.Free();
|
if (handle.IsAllocated) handle.Free();
|
||||||
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||||
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
||||||
return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
|
return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
|
||||||
@ -37,56 +39,32 @@ namespace Quik.Media
|
|||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (handle.IsAllocated) handle.Free();
|
|
||||||
buffer = null;
|
buffer = null;
|
||||||
|
if (handle.IsAllocated) handle.Free();
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
|
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
|
||||||
{
|
{
|
||||||
if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
|
imageLock = Lock();
|
||||||
if (Depth > 1) throw new InvalidOperationException("This texture has a depth component.");
|
|
||||||
|
|
||||||
UnlockBits();
|
|
||||||
|
|
||||||
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
||||||
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
|
||||||
imageLock = new QImageLock(InternalFormat, Width, Height, Depth, ptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
|
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
|
||||||
{
|
{
|
||||||
if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
|
imageLock = Lock();
|
||||||
UnlockBits();
|
|
||||||
|
|
||||||
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
||||||
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
|
||||||
imageLock = new QImageLock(InternalFormat, Width, Height, Depth, ptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
|
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
|
||||||
{
|
{
|
||||||
if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
|
imageLock = Lock();
|
||||||
if (depth < 0 || depth > Depth)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(depth), "Depth must be in range.");
|
|
||||||
|
|
||||||
UnlockBits();
|
|
||||||
|
|
||||||
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
||||||
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
|
||||||
imageLock = new QImageLock(
|
|
||||||
InternalFormat,
|
|
||||||
Width,
|
|
||||||
Height,
|
|
||||||
1,
|
|
||||||
ptr + (depth * Width * Height * InternalFormat.BytesPerPixel()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UnlockBits()
|
public override void UnlockBits()
|
||||||
{
|
{
|
||||||
if (handle.IsAllocated)
|
|
||||||
handle.Free();
|
handle.Free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetSdf(bool value = true) => isSdf = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Quik.Media.Color;
|
||||||
|
|
||||||
namespace Quik.Media.Font
|
namespace Quik.Media.Font
|
||||||
{
|
{
|
||||||
@ -17,11 +21,28 @@ namespace Quik.Media.Font
|
|||||||
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
|
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
|
||||||
private int index = 0;
|
private int index = 0;
|
||||||
private AtlasPage last = null;
|
private AtlasPage last = null;
|
||||||
|
private bool isSdf = false;
|
||||||
|
private int expansion;
|
||||||
|
|
||||||
public FontAtlas(int width, int height)
|
public bool IsSdf
|
||||||
|
{
|
||||||
|
get => isSdf;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (AtlasPage page in atlases)
|
||||||
|
{
|
||||||
|
(page.Image as QImageBuffer).SetSdf(value);
|
||||||
|
}
|
||||||
|
isSdf = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontAtlas(int width, int height, bool isSdf, int expansion = 4)
|
||||||
{
|
{
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
IsSdf = isSdf;
|
||||||
|
this.expansion = expansion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info)
|
public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info)
|
||||||
@ -33,7 +54,7 @@ namespace Quik.Media.Font
|
|||||||
{
|
{
|
||||||
info = new FontAtlasGlyphInfo() { Codepoint = codepoint };
|
info = new FontAtlasGlyphInfo() { Codepoint = codepoint };
|
||||||
|
|
||||||
if (last == null || last.IsFull)
|
if (last == null || !last.WouldFit(source))
|
||||||
{
|
{
|
||||||
AddPage();
|
AddPage();
|
||||||
}
|
}
|
||||||
@ -51,7 +72,8 @@ namespace Quik.Media.Font
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
last = new AtlasPage(width, height);
|
last = new AtlasPage(width, height, expansion);
|
||||||
|
(last.Image as QImageBuffer).SetSdf(IsSdf);
|
||||||
atlases.Add(last);
|
atlases.Add(last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,12 +114,14 @@ namespace Quik.Media.Font
|
|||||||
public QImage Image;
|
public QImage Image;
|
||||||
public int PointerX, PointerY;
|
public int PointerX, PointerY;
|
||||||
public int RowHeight;
|
public int RowHeight;
|
||||||
|
public int Expansion;
|
||||||
|
|
||||||
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
|
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
|
||||||
|
|
||||||
public AtlasPage(int width, int height)
|
public AtlasPage(int width, int height, int expansion)
|
||||||
{
|
{
|
||||||
Image = new QImageBuffer(QImageFormat.RedU8, width, height);
|
Image = new QImageBuffer(QImageFormat.RedU8, width, height);
|
||||||
|
Expansion = expansion;
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +134,8 @@ namespace Quik.Media.Font
|
|||||||
src.CopyTo(dst, PointerX, PointerY);
|
src.CopyTo(dst, PointerX, PointerY);
|
||||||
Image.UnlockBits();
|
Image.UnlockBits();
|
||||||
|
|
||||||
QVec2 min = new QVec2(PointerX/Image.Width, PointerY/Image.Width);
|
QVec2 min = new QVec2((float)PointerX/Image.Width, (float)PointerY/Image.Height);
|
||||||
QVec2 size = new QVec2(src.Width/Image.Width, src.Height/Image.Height);
|
QVec2 size = new QVec2((float)src.Width/Image.Width, (float)src.Height/Image.Height);
|
||||||
|
|
||||||
prototype.Image = Image;
|
prototype.Image = Image;
|
||||||
prototype.UVs = new QRectangle(min + size, min);
|
prototype.UVs = new QRectangle(min + size, min);
|
||||||
@ -127,7 +151,7 @@ namespace Quik.Media.Font
|
|||||||
public void AdvanceRow()
|
public void AdvanceRow()
|
||||||
{
|
{
|
||||||
PointerX = 0;
|
PointerX = 0;
|
||||||
PointerY += RowHeight;
|
PointerY += RowHeight + Expansion;
|
||||||
|
|
||||||
RowHeight = 0;
|
RowHeight = 0;
|
||||||
}
|
}
|
||||||
@ -135,7 +159,7 @@ namespace Quik.Media.Font
|
|||||||
public void AdvanceColumn(int width, int height)
|
public void AdvanceColumn(int width, int height)
|
||||||
{
|
{
|
||||||
RowHeight = Math.Max(RowHeight, height);
|
RowHeight = Math.Max(RowHeight, height);
|
||||||
PointerX += width;
|
PointerX += width + Expansion;
|
||||||
|
|
||||||
if (PointerX > Image.Width)
|
if (PointerX > Image.Width)
|
||||||
{
|
{
|
||||||
@ -153,6 +177,11 @@ namespace Quik.Media.Font
|
|||||||
|
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool WouldFit(QImageLock source)
|
||||||
|
{
|
||||||
|
return !IsFull || PointerX + source.Width > Image.Width || PointerY + source.Height > Image.Height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Quik.Media;
|
using Quik.Media;
|
||||||
using Quik.Media.Font;
|
using Quik.Media.Font;
|
||||||
|
|
||||||
@ -17,8 +18,23 @@ namespace Quik.Media
|
|||||||
public FontStretch Stretch => Face.Stretch;
|
public FontStretch Stretch => Face.Stretch;
|
||||||
|
|
||||||
public abstract bool HasRune(int rune);
|
public abstract bool HasRune(int rune);
|
||||||
public abstract QFontPage RasterizePage(int codepage, float size, in FontRasterizerOptions options);
|
protected abstract QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options);
|
||||||
public QFontPage RasterizePage(int codepage, float size) => RasterizePage(codepage, size, FontRasterizerOptions.Default);
|
|
||||||
|
|
||||||
|
private readonly Dictionary<float, SizedFontCollection> _atlasses = new Dictionary<float, SizedFontCollection>();
|
||||||
|
|
||||||
|
public void Get(int codepoint, float size, out FontGlyph glyph)
|
||||||
|
{
|
||||||
|
SizedFontCollection collection;
|
||||||
|
|
||||||
|
if (!_atlasses.TryGetValue(size, out collection))
|
||||||
|
{
|
||||||
|
collection = new SizedFontCollection(size);
|
||||||
|
_atlasses.Add(size, collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.Get(codepoint, out glyph, this);
|
||||||
|
}
|
||||||
|
|
||||||
// IDisposable
|
// IDisposable
|
||||||
private bool isDisposed = false;
|
private bool isDisposed = false;
|
||||||
@ -32,6 +48,71 @@ namespace Quik.Media
|
|||||||
}
|
}
|
||||||
protected virtual void Dispose(bool disposing) { }
|
protected virtual void Dispose(bool disposing) { }
|
||||||
public void Dispose() => DisposePrivate(true);
|
public void Dispose() => DisposePrivate(true);
|
||||||
|
|
||||||
|
private class SizedFontCollection
|
||||||
|
{
|
||||||
|
public float Size { get; }
|
||||||
|
private readonly Dictionary<int, FontGlyph> glyphs = new Dictionary<int, FontGlyph>();
|
||||||
|
private readonly FontAtlas atlas;
|
||||||
|
|
||||||
|
public SizedFontCollection(float size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
|
||||||
|
QuikApplication.Current.Platform.GetMaximumImage(out int height, out int width);
|
||||||
|
|
||||||
|
// Do no allow to create a texture that is greater than 16 square characters at 200 DPI.
|
||||||
|
width = Math.Min(width, (int)(size * 200 * 16));
|
||||||
|
height = Math.Min(height, (int)(size * 200 * 16));
|
||||||
|
// width = height = 256;
|
||||||
|
|
||||||
|
atlas = new FontAtlas(width, height, QuikApplication.Current.FontProvider.RasterizerOptions.Sdf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Get(int codepoint, out FontGlyph glyph, QFont font)
|
||||||
|
{
|
||||||
|
if (glyphs.TryGetValue(codepoint, out glyph))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QImage image = font.Render(
|
||||||
|
out QGlyphMetrics metrics,
|
||||||
|
codepoint,
|
||||||
|
Size,
|
||||||
|
QuikApplication.Current.FontProvider.RasterizerOptions);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
image.LockBits2d(out QImageLock l, QImageLockOptions.Default);
|
||||||
|
atlas.PutGlyph(codepoint, l, out FontAtlasGlyphInfo glyphInfo);
|
||||||
|
image.UnlockBits();
|
||||||
|
image.Dispose();
|
||||||
|
|
||||||
|
glyph = new FontGlyph(codepoint, glyphInfo.Image, metrics, glyphInfo.UVs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glyph = new FontGlyph(codepoint, null, metrics, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
glyphs[codepoint] = glyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct FontGlyph
|
||||||
|
{
|
||||||
|
public readonly int CodePoint;
|
||||||
|
public readonly QImage Image;
|
||||||
|
public readonly QGlyphMetrics Metrics;
|
||||||
|
public readonly QRectangle UVs;
|
||||||
|
|
||||||
|
public FontGlyph(int codepoint, QImage image, in QGlyphMetrics metrics, in QRectangle uvs)
|
||||||
|
{
|
||||||
|
CodePoint = codepoint;
|
||||||
|
Image = image;
|
||||||
|
Metrics = metrics;
|
||||||
|
UVs = uvs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct FontRasterizerOptions
|
public struct FontRasterizerOptions
|
||||||
@ -42,53 +123,7 @@ namespace Quik.Media
|
|||||||
public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
|
public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
|
||||||
{
|
{
|
||||||
Resolution = 96.0f,
|
Resolution = 96.0f,
|
||||||
Sdf = true
|
Sdf = false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QFontPage : IDisposable
|
|
||||||
{
|
|
||||||
public QFont Font { get; }
|
|
||||||
public int CodePage { get; }
|
|
||||||
public float Size { get; }
|
|
||||||
public virtual QImage Image { get; } = null;
|
|
||||||
public virtual QGlyphMetrics[] Metrics { get; } = Array.Empty<QGlyphMetrics>();
|
|
||||||
|
|
||||||
public FontRasterizerOptions Options { get; }
|
|
||||||
public float Resolution => Options.Resolution;
|
|
||||||
public bool Sdf => Options.Sdf;
|
|
||||||
|
|
||||||
public void Dispose() => DisposeInternal(false);
|
|
||||||
|
|
||||||
protected QFontPage(QFont font, int codepage, float size, in FontRasterizerOptions options)
|
|
||||||
{
|
|
||||||
Font = font;
|
|
||||||
CodePage = codepage;
|
|
||||||
Size = size;
|
|
||||||
Options = options;
|
|
||||||
}
|
|
||||||
public QFontPage(QFont font, int codepage, float size, in FontRasterizerOptions options, QImage image, QGlyphMetrics[] metrics)
|
|
||||||
: this(font, codepage, size, options)
|
|
||||||
{
|
|
||||||
Image = image;
|
|
||||||
Metrics = metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool isDisposed = false;
|
|
||||||
private void DisposeInternal(bool disposing)
|
|
||||||
{
|
|
||||||
if (isDisposed) return;
|
|
||||||
|
|
||||||
Dispose(disposing);
|
|
||||||
isDisposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Image?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -78,7 +78,7 @@ namespace Quik.Media
|
|||||||
int bpp = Format.BytesPerPixel();
|
int bpp = Format.BytesPerPixel();
|
||||||
for (int i = 0; i < Height; i++)
|
for (int i = 0; i < Height; i++)
|
||||||
{
|
{
|
||||||
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * bpp);
|
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * Width * bpp);
|
||||||
|
|
||||||
long dstPos = x + i * destination.Width;
|
long dstPos = x + i * destination.Width;
|
||||||
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
|
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
|
||||||
|
@ -5,6 +5,7 @@ using Quik.VertexGenerator;
|
|||||||
using static Quik.OpenGL.GLEnum;
|
using static Quik.OpenGL.GLEnum;
|
||||||
using Quik.Media;
|
using Quik.Media;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Quik.OpenGL
|
namespace Quik.OpenGL
|
||||||
{
|
{
|
||||||
@ -105,7 +106,7 @@ namespace Quik.OpenGL
|
|||||||
GL.UseProgram(program);
|
GL.UseProgram(program);
|
||||||
GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1));
|
GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1));
|
||||||
GL.UniformMatrix4(m4Transforms, false, in matrix);
|
GL.UniformMatrix4(m4Transforms, false, in matrix);
|
||||||
GL.Uniform1(fSdfThreshold, 0.0f);
|
GL.Uniform1(fSdfThreshold, 0.5f);
|
||||||
GL.Uniform1(tx2d, 0);
|
GL.Uniform1(tx2d, 0);
|
||||||
GL.Enable(GL_BLEND);
|
GL.Enable(GL_BLEND);
|
||||||
GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
@ -11,6 +11,9 @@ namespace Quik.OpenGL
|
|||||||
GL_RENDERER = 0x1F01,
|
GL_RENDERER = 0x1F01,
|
||||||
GL_VERSION = 0x1F02,
|
GL_VERSION = 0x1F02,
|
||||||
GL_EXTENSIONS = 0x1F03,
|
GL_EXTENSIONS = 0x1F03,
|
||||||
|
GL_MAX_TEXTURE_SIZE = 0x0D33,
|
||||||
|
GL_MAX_3D_TEXTURE_SIZE = 0x8073,
|
||||||
|
GL_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF,
|
||||||
|
|
||||||
GL_MULTISAMPLE = 0x809D,
|
GL_MULTISAMPLE = 0x809D,
|
||||||
GL_BLEND = 0x0BE2,
|
GL_BLEND = 0x0BE2,
|
||||||
|
@ -55,5 +55,7 @@ namespace Quik.PAL
|
|||||||
void PortFocus(IQuikPortHandle port);
|
void PortFocus(IQuikPortHandle port);
|
||||||
void PortShow(IQuikPortHandle port, bool shown = true);
|
void PortShow(IQuikPortHandle port, bool shown = true);
|
||||||
void PortPaint(IQuikPortHandle port, CommandList commands);
|
void PortPaint(IQuikPortHandle port, CommandList commands);
|
||||||
|
void GetMaximumImage(out int width, out int height);
|
||||||
|
void GetMaximumImage(out int width, out int height, out int depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
71
Quik/Typography/FontProvider.cs
Normal file
71
Quik/Typography/FontProvider.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using Quik.Media;
|
||||||
|
using Quik.Media.Font;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Quik.Typography
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The font provider is a caching object that provides fonts for typesetting classes.
|
||||||
|
/// </summary>
|
||||||
|
public class FontProvider : IDisposable
|
||||||
|
{
|
||||||
|
private Dictionary<FontFace, QFont> Fonts { get; } = new Dictionary<FontFace, QFont>();
|
||||||
|
private HashSet<QFont> UsedFonts { get; } = new HashSet<QFont>();
|
||||||
|
public readonly FontRasterizerOptions RasterizerOptions;
|
||||||
|
private readonly QuikApplication App;
|
||||||
|
|
||||||
|
public QFont this[FontFace info]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Fonts.TryGetValue(info, out QFont font))
|
||||||
|
{
|
||||||
|
font = (QFont)App.GetMedia(info, MediaHint.Font);
|
||||||
|
Fonts[info] = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
UsedFonts.Add(font);
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontProvider(QuikApplication app, in FontRasterizerOptions options)
|
||||||
|
{
|
||||||
|
RasterizerOptions = options;
|
||||||
|
App = app;
|
||||||
|
}
|
||||||
|
public FontProvider(QuikApplication app)
|
||||||
|
: this(app, FontRasterizerOptions.Default)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the use of fonts used by this typesetter and removes any that haven't been referenced since the last cycle.
|
||||||
|
/// </summary>
|
||||||
|
public void Collect()
|
||||||
|
{
|
||||||
|
// foreach (FontJar jar in Fonts.Values.ToArray())
|
||||||
|
// {
|
||||||
|
// if (!UsedFonts.Contains(jar))
|
||||||
|
// {
|
||||||
|
// Fonts.Remove(jar.Info);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// UsedFonts.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isDisposed = false;
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (isDisposed) return;
|
||||||
|
|
||||||
|
isDisposed = true;
|
||||||
|
foreach (QFont font in Fonts.Values)
|
||||||
|
{
|
||||||
|
font.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -226,7 +226,7 @@ namespace Quik.Typography
|
|||||||
|
|
||||||
pen.Y -= PostSpace;
|
pen.Y -= PostSpace;
|
||||||
|
|
||||||
group.BoundingBox = new QRectangle(width, 0, 0, pen.Y);
|
group.BoundingBox = new QRectangle(width, pen.Y, 0, 0);
|
||||||
group.Translate(-pen);
|
group.Translate(-pen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,13 +353,13 @@ namespace Quik.Typography
|
|||||||
public struct TypesetCharacter
|
public struct TypesetCharacter
|
||||||
{
|
{
|
||||||
public int Character;
|
public int Character;
|
||||||
public QuikTexture Texture;
|
public QImage Texture;
|
||||||
public QRectangle Position;
|
public QRectangle Position;
|
||||||
public QRectangle UV;
|
public QRectangle UV;
|
||||||
|
|
||||||
public TypesetCharacter(
|
public TypesetCharacter(
|
||||||
int chr,
|
int chr,
|
||||||
QuikTexture texture,
|
QImage texture,
|
||||||
in QRectangle position,
|
in QRectangle position,
|
||||||
in QRectangle uv)
|
in QRectangle uv)
|
||||||
{
|
{
|
||||||
|
155
Quik/Typography/Typesetter.cs
Normal file
155
Quik/Typography/Typesetter.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using Quik.CommandMachine;
|
||||||
|
using Quik.Media;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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 void TypesetHorizontalDirect(this CommandList list, string str, QVec2 origin, float size, QFont font)
|
||||||
|
{
|
||||||
|
Dictionary<QImage, FontDrawInfo> drawInfo = new Dictionary<QImage, FontDrawInfo>();
|
||||||
|
var enumerator = new LineEnumerator(str.AsSpan());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1063,7 +1063,7 @@ namespace Quik.VertexGenerator
|
|||||||
DrawQueue.AddVertex(vertex);
|
DrawQueue.AddVertex(vertex);
|
||||||
|
|
||||||
vertex.Position = new QVec2(rect.Right, rect.Top);
|
vertex.Position = new QVec2(rect.Right, rect.Top);
|
||||||
vertex.TextureCoordinates = new QVec2(uvs.Right, uvs.Right);
|
vertex.TextureCoordinates = new QVec2(uvs.Right, uvs.Top);
|
||||||
DrawQueue.AddVertex(vertex);
|
DrawQueue.AddVertex(vertex);
|
||||||
|
|
||||||
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
|
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
|
||||||
|
@ -13,9 +13,8 @@ out vec4 fragColor;
|
|||||||
uniform int iEnableSdf;
|
uniform int iEnableSdf;
|
||||||
uniform int iEnableTexture;
|
uniform int iEnableTexture;
|
||||||
uniform int iAlphaDiscard;
|
uniform int iAlphaDiscard;
|
||||||
uniform float fSdfThreshold;
|
uniform float fSdfThreshold = 0.5;
|
||||||
uniform sampler2D tx2d;
|
uniform sampler2D tx2d;
|
||||||
uniform sampler2DArray tx2dArray;
|
|
||||||
|
|
||||||
const float fAlphaThreshold = 0.01;
|
const float fAlphaThreshold = 0.01;
|
||||||
|
|
||||||
@ -23,7 +22,13 @@ vec4 getTexture()
|
|||||||
{
|
{
|
||||||
if (iEnableTexture == 3)
|
if (iEnableTexture == 3)
|
||||||
{
|
{
|
||||||
return texture(tx2dArray, vec3(fv2TexPos, ffTexLayer));
|
// return texture(tx2dArray, vec3(fv2TexPos, ffTexLayer));
|
||||||
|
}
|
||||||
|
else if (iEnableSdf == 1)
|
||||||
|
{
|
||||||
|
vec2 texelSz = 1.0/vec2(textureSize(tx2d, 0));
|
||||||
|
vec2 txCoord2 = fv2TexPos + texelSz * (1 - mod(fv2TexPos, texelSz));
|
||||||
|
return texture(tx2d, txCoord2);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -41,12 +46,7 @@ void main(void)
|
|||||||
|
|
||||||
if (iEnableSdf != 0)
|
if (iEnableSdf != 0)
|
||||||
{
|
{
|
||||||
float a = max(value.r, value.a);
|
value = vec4(vec3(1.0), smoothstep(fSdfThreshold-0.1, fSdfThreshold+0.1, value.r));
|
||||||
|
|
||||||
value =
|
|
||||||
(a >= fSdfThreshold) ?
|
|
||||||
vec4(1.0,1.0,1.0,1.0) :
|
|
||||||
vec4(0.0,0.0,0.0,0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iAlphaDiscard != 0 && value.a <= fAlphaThreshold)
|
if (iAlphaDiscard != 0 && value.a <= fAlphaThreshold)
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
using Quik;
|
using System;
|
||||||
|
using Quik;
|
||||||
using Quik.CommandMachine;
|
using Quik.CommandMachine;
|
||||||
using Quik.Controls;
|
using Quik.Controls;
|
||||||
using Quik.OpenTK;
|
using Quik.OpenTK;
|
||||||
|
using Quik.Media.Defaults;
|
||||||
|
using Quik.Media;
|
||||||
|
using Quik.Typography;
|
||||||
|
using Quik.PAL;
|
||||||
|
|
||||||
namespace QuikDemo
|
namespace QuikDemo
|
||||||
{
|
{
|
||||||
@ -18,11 +22,23 @@ namespace QuikDemo
|
|||||||
|
|
||||||
public class EmptyView : View
|
public class EmptyView : View
|
||||||
{
|
{
|
||||||
protected override void PaintBegin(CommandQueue cmd)
|
private QFont font;
|
||||||
|
|
||||||
|
protected override void PaintBegin(CommandList cmd)
|
||||||
{
|
{
|
||||||
base.PaintBegin(cmd);
|
base.PaintBegin(cmd);
|
||||||
|
|
||||||
cmd.Rectangle(new QRectangle(0, 0, 16, 16));
|
if (font == null)
|
||||||
|
{
|
||||||
|
IFontDataBase db = FontDataBaseProvider.Instance;
|
||||||
|
font = new QFontFreeType(db.FontFileInfo(db.Sans).OpenRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Rectangle(new QRectangle(16, 16, 0, 0));
|
||||||
|
cmd.TypesetHorizontalDirect(
|
||||||
|
"The quick brown fox jumps over the lazy dog.\n" +
|
||||||
|
"hi?",
|
||||||
|
new QVec2(64.33f, 0.77f), 9, font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user