Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 56 additions & 18 deletions Client/UI/CairoFont.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public class CairoFont : FontConfig, IDisposable

public double LineHeightMultiplier = 1f;

FontOptions CairoFontOptions;
FontOptions CairoFontOptions;
const string ArabicFallbackFontName = "Tahoma";

public FontSlant Slant = FontSlant.Normal;

Expand Down Expand Up @@ -213,11 +214,16 @@ public CairoFont WithFont(string fontname)
/// Sets up the context. Must be executed in the main thread, as it is not thread safe.
/// </summary>
/// <param name="ctx">The context to set up the CairoFont with.</param>
public void SetupContext(Context ctx)
{
ctx.SetFontSize(GuiElement.scaled(UnscaledFontsize));
ctx.SelectFontFace(Fontname, Slant, FontWeight);
CairoFontOptions = new FontOptions();
public void SetupContext(Context ctx)
{
SetupContext(ctx, null);
}

public void SetupContext(Context ctx, string text)
{
ctx.SetFontSize(GuiElement.scaled(UnscaledFontsize));
ctx.SelectFontFace(GetFontNameForText(text), Slant, GetFontWeightForText(text));
CairoFontOptions = new FontOptions();

// Antialias.Best does not work on Linux it completely borks the font
CairoFontOptions.Antialias = Antialias.Subpixel;
Expand All @@ -233,29 +239,61 @@ public void SetupContext(Context ctx)
{
ctx.SetSourceRGBA(Color[0], Color[1], Color[2], Color[3]);
}
}
}
}
}

string GetFontNameForText(string text)
{
if (!ComplexTextLayout.RequiresRightToLeftLayout(text))
{
return Fontname;
}

return ArabicFallbackFontName;
}

FontWeight GetFontWeightForText(string text)
{
if (!ComplexTextLayout.RequiresRightToLeftLayout(text))
{
return FontWeight;
}

return Cairo.FontWeight.Normal;
}

/// <summary>
/// Gets the font's extents.
/// </summary>
/// <returns>The FontExtents for this particular font.</returns>
public FontExtents GetFontExtents()
{
SetupContext(FontMeasuringContext);
return FontMeasuringContext.FontExtents;
}
public FontExtents GetFontExtents()
{
SetupContext(FontMeasuringContext);
return FontMeasuringContext.FontExtents;
}

public FontExtents GetFontExtents(string text)
{
SetupContext(FontMeasuringContext, text);
return FontMeasuringContext.FontExtents;
}

/// <summary>
/// Gets the extents of the text.
/// </summary>
/// <param name="text">The text to extend.</param>
/// <returns>The Text extends for this font with this text.</returns>
public TextExtents GetTextExtents(string text)
{
SetupContext(FontMeasuringContext);
return FontMeasuringContext.TextExtents(text);
}
public TextExtents GetTextExtents(string text)
{
SetupContext(FontMeasuringContext, text);

if (CairoGlyphLayout.TryCreateGlyphs(FontMeasuringContext, text, 0, 0, out Glyph[] glyphs))
{
return FontMeasuringContext.GlyphExtents(glyphs);
}

return FontMeasuringContext.TextExtents(ComplexTextLayout.PrepareForRendering(text));
}

/// <summary>
/// Clone function. Creates a duplicate of this Cairofont.
Expand Down
142 changes: 142 additions & 0 deletions Client/UI/CairoGlyphLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Runtime.InteropServices;
using Cairo;

#nullable disable

namespace Vintagestory.API.Client
{
internal static class CairoGlyphLayout
{
// The native cairo glyph conversion used by the shipped runtime resolves glyph indices,
// but it does not perform Arabic shaping. Enabling it leaves right-to-left text disconnected.
const bool NativeGlyphLayoutEnabled = false;

[StructLayout(LayoutKind.Sequential)]
struct NativeGlyph32
{
public uint Index;
public double X;
public double Y;
}

[StructLayout(LayoutKind.Sequential)]
struct NativeGlyph64
{
public ulong Index;
public double X;
public double Y;
}

[DllImport("libcairo-2.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int cairo_scaled_font_text_to_glyphs(
IntPtr scaledFont,
double x,
double y,
[MarshalAs(UnmanagedType.LPUTF8Str)] string utf8,
int utf8Len,
out IntPtr glyphs,
out int numGlyphs,
IntPtr clusters,
IntPtr numClusters,
IntPtr clusterFlags);

[DllImport("libcairo-2.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void cairo_glyph_free(IntPtr glyphs);

public static bool TryCreateGlyphs(Context ctx, string text, double originX, double originY, out Glyph[] glyphs)
{
glyphs = null;

if (!NativeGlyphLayoutEnabled)
{
return false;
}

if (ctx == null || string.IsNullOrEmpty(text) || !ComplexTextLayout.RequiresRightToLeftLayout(text))
{
return false;
}

string preparedText = ComplexTextLayout.PrepareForRendering(text);
if (string.IsNullOrEmpty(preparedText))
{
return false;
}

IntPtr nativeGlyphs = IntPtr.Zero;

try
{
int status = cairo_scaled_font_text_to_glyphs(
ctx.GetScaledFont().Handle,
originX,
originY,
preparedText,
-1,
out nativeGlyphs,
out int numGlyphs,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);

if (status != 0 || nativeGlyphs == IntPtr.Zero || numGlyphs <= 0)
{
return false;
}

glyphs = CopyGlyphs(nativeGlyphs, numGlyphs);
return glyphs.Length > 0;
}
catch
{
glyphs = null;
return false;
}
finally
{
if (nativeGlyphs != IntPtr.Zero)
{
cairo_glyph_free(nativeGlyphs);
}
}
}

static Glyph[] CopyGlyphs(IntPtr nativeGlyphs, int numGlyphs)
{
Glyph[] glyphs = new Glyph[numGlyphs];

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
int glyphSize = Marshal.SizeOf<NativeGlyph32>();
for (int i = 0; i < numGlyphs; i++)
{
NativeGlyph32 nativeGlyph = Marshal.PtrToStructure<NativeGlyph32>(nativeGlyphs + i * glyphSize);
glyphs[i] = new Glyph
{
Index = nativeGlyph.Index,
X = nativeGlyph.X,
Y = nativeGlyph.Y
};
}
}
else
{
int glyphSize = Marshal.SizeOf<NativeGlyph64>();
for (int i = 0; i < numGlyphs; i++)
{
NativeGlyph64 nativeGlyph = Marshal.PtrToStructure<NativeGlyph64>(nativeGlyphs + i * glyphSize);
glyphs[i] = new Glyph
{
Index = (long)nativeGlyph.Index,
X = nativeGlyph.X,
Y = nativeGlyph.Y
};
}
}

return glyphs;
}
}
}
Loading