Skip to content

Commit

Permalink
Corrected P/Invoke signatures for text handling and finalized demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Visual-Vincent committed Jan 13, 2022
1 parent 5cfe480 commit 26f9f8d
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 72 deletions.
37 changes: 30 additions & 7 deletions Generator/CodeTranslation/Generators/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,21 @@ public class PInvokeGenerator : ICodeGenerator
{ "nvgTransformMultiply", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
{ "nvgTransformPremultiply", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
{ "nvgTransformInverse", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
{ "nvgTransformPoint", new ArgumentMap() {{ "dstx", type => "float[]" }, { "dsty", type => "float[]" }, { "xform", type => "float[]" }} },
{ "nvgTransformPoint", new ArgumentMap() {{ "dstx", type => "float[]" }, { "dsty", type => "float[]" }, { "xform", type => "float[]" }} },
{ "nvgImageSize", new ArgumentMap() {{ "w", type => "out int" }, { "h", type => "out int" }} },
{ "nvgTextMetrics", new ArgumentMap() {{ "ascender", type => "out float" }, { "descender", type => "out float" }, { "lineh", type => "out float" }} },
{ "nvgTextBounds", new ArgumentMap() {{ "bounds", type => "float[]" }} },
{ "nvgTextBoxBounds", new ArgumentMap() {{ "bounds", type => "float[]" }} },
{ "nvgTextGlyphPositions", new ArgumentMap() {{ "positions", type => type.Pointer == PointerType.Standard ? $"{type.Name}[]" : null }} },
{ "nvgTextBreakLines", new ArgumentMap() {{ "rows", type => type.Pointer == PointerType.Standard ? $"{type.Name}[]" : null }} },
{ "nvgTextMetrics", new ArgumentMap() {{ "ascender", type => "out float" }, { "descender", type => "out float" }, { "lineh", type => "out float" }} },
{ "nvgText", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }} },
{ "nvgTextBounds", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "bounds", type => "float[]" }} },
{ "nvgTextBox", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }} },
{ "nvgTextBoxBounds", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "bounds", type => "float[]" }} },
{ "nvgTextGlyphPositions", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "positions", type => type.Pointer == PointerType.Standard ? $"IntPtr" : null }} },
{ "nvgTextBreakLines", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "rows", type => type.Pointer == PointerType.Standard ? $"IntPtr" : null }} },
};

private static readonly Dictionary<string, ArgumentMap> StructFieldOverrides = new Dictionary<string, ArgumentMap>() {
// { StructName, {{ FieldName, FieldType }} }
{ "NVGglyphPosition", new ArgumentMap() {{ "str", type => "IntPtr" }} },
{ "NVGtextRow", new ArgumentMap() {{ "start", type => "IntPtr" }, { "end", type => "IntPtr" }, { "next", type => "IntPtr" }} },
};

private string libraryName;
Expand Down Expand Up @@ -273,7 +281,7 @@ public string GenerateStruct(StructDefinition @struct, int index, int count)
bool isFixed = isArray && !string.IsNullOrWhiteSpace(field.Type.ArrayBounds);

string name = ConvertName(field.Name);
string type = ConvertType(field.Type, false);
string type = ConvertFieldType(@struct, field, false);
string bounds = isArray ? $"[{field.Type.ArrayBounds}]" : "";

builder.Append(indentation);
Expand Down Expand Up @@ -351,5 +359,20 @@ private static string ConvertArgumentType(FunctionDefinition function, ArgumentD

return ConvertType(type, arraySuffix);
}

// Handles special cases
private static string ConvertFieldType(StructDefinition @struct, FieldDefinition field, bool arraySuffix = true)
{
TypeDefinition type = field.Type;

if(StructFieldOverrides.TryGetValue(@struct.Name, out var map) && map.TryGetValue(field.Name, out var fieldOverride))
{
var newType = fieldOverride(type);
if(newType != null)
return newType;
}

return ConvertType(type, arraySuffix);
}
}
}
6 changes: 3 additions & 3 deletions NanoVG.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NanoVG.Native", "NanoVG.Nat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator\Generator.csproj", "{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{90C6EC8D-BF79-4D14-A069-6C198817D636}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{90C6EC8D-BF79-4D14-A069-6C198817D636}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -62,8 +62,8 @@ Global
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x64.Build.0 = Release|Any CPU
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x86.ActiveCfg = Release|Any CPU
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x86.Build.0 = Release|Any CPU
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.ActiveCfg = Debug|x86
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.Build.0 = Debug|x86
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x64.ActiveCfg = Debug|Any CPU
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x64.Build.0 = Debug|Any CPU
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x86.ActiveCfg = Debug|Any CPU
Expand Down
180 changes: 179 additions & 1 deletion NanoVG.NET/NVG.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace NanoVG
{
public static unsafe partial class NVG
public static partial class NVG
{
static NVG()
{
Expand Down Expand Up @@ -44,10 +46,186 @@ public struct NVGcolor
public float r,g,b,a;
}

public class TextRow
{
public string SourceText { get; }
public string Text { get; }
public int NextLinePosition { get; }
public float MaxX { get; }
public float MinX { get; }
public float Width { get; }

internal TextRow(string sourceText, string text, int nextLinePos, float minX, float maxX, float width)
{
SourceText = sourceText;
Text = text;
NextLinePosition = nextLinePos;
MinX = minX;
MaxX = maxX;
Width = width;
}
}

public enum NVGcreateFlags
{
NVG_ANTIALIAS = 1<<0,
NVG_STENCIL_STROKES = 1<<1,
NVG_DEBUG = 1<<2,
}

public static partial class NVG
{
private static T GetStringPointers<T>(string str, int length, Func<IntPtr, IntPtr, T> callback)
{
if(length > str.Length)
throw new ArgumentOutOfRangeException(nameof(length));

byte[] utf8 = Encoding.UTF8.GetBytes(str);
int byteLength = length > 0 ? Encoding.UTF8.GetByteCount(str, 0, length) : 0;

GCHandle handle = default;
try
{
handle = GCHandle.Alloc(utf8, GCHandleType.Pinned);

IntPtr strPtr = handle.AddrOfPinnedObject();
IntPtr endPtr = length > 0 ? endPtr = IntPtr.Add(strPtr, byteLength) : IntPtr.Zero;

return callback(strPtr, endPtr);
}
finally
{
if(handle.IsAllocated)
handle.Free();
}
}

/// <summary>
/// Draws text string at specified location.
/// </summary>
public static float Text(this NVGcontext ctx, float x, float y, string @string, int length = -1)
{
return GetStringPointers(@string, length, (strPtr, endPtr) => Text(ctx, x, y, strPtr, endPtr));
}

/// <summary>
/// Draws multi-line text string at specified location wrapped at the specified width. <br/>
/// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. <br/>
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
/// </summary>
public static void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, string @string, int length = -1)
{
GetStringPointers(@string, length, (strPtr, endPtr) => {
TextBox(ctx, x, y, breakRowWidth, strPtr, endPtr);
return (object)null;
});
}

/// <summary>
/// Measures the specified text string. Parameter bounds should be a pointer to float[4], <br/>
/// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] <br/>
/// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). <br/>
/// Measured values are returned in local coordinate space.
/// </summary>
public static float TextBounds(this NVGcontext ctx, float x, float y, string @string, out float[] bounds, int strLength = -1)
{
float[] retBounds = new float[4];
float retVal = GetStringPointers(@string, strLength, (strPtr, endPtr) => TextBounds(ctx, x, y, strPtr, endPtr, retBounds));

bounds = retBounds;
return retVal;
}

/// <summary>
/// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], <br/>
/// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] <br/>
/// Measured values are returned in local coordinate space.
/// </summary>
public static void TextBoxBounds(this NVGcontext ctx, float x, float y, float breakRowWidth, string @string, out float[] bounds, int strLength = -1)
{
float[] retBounds = new float[4];

GetStringPointers(@string, strLength, (strPtr, endPtr) => {
TextBoxBounds(ctx, x, y, breakRowWidth, strPtr, endPtr, retBounds);
return (object)null;
});

bounds = retBounds;
}

/// <summary>
/// Calculates the glyph x positions of the specified text. Measured values are returned in local coordinate space.
/// </summary>
public static NVGglyphPosition[] TextGlyphPositions(this NVGcontext ctx, float x, float y, string @string, int maxPositions, int strLength = -1)
{
if(maxPositions <= 0)
return Array.Empty<NVGglyphPosition>();

NVGglyphPosition[] positions = new NVGglyphPosition[maxPositions];
int posCount = GetStringPointers(@string, strLength, (strPtr, endPtr) => {
unsafe
{
fixed(NVGglyphPosition* ptr = &positions[0])
{
return TextGlyphPositions(ctx, x, y, strPtr, endPtr, new IntPtr(ptr), maxPositions);
}
}
});

if(posCount <= 0)
return Array.Empty<NVGglyphPosition>();

if(posCount != positions.Length)
Array.Resize(ref positions, posCount);

return positions;
}

/// <summary>
/// Breaks the specified text into lines. <br/>
/// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. <br/>
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
/// </summary>
public static TextRow[] TextBreakLines(this NVGcontext ctx, string @string, float breakRowWidth, int maxRows, int strLength = -1)
{
if(@string.Length <= 0)
return Array.Empty<TextRow>();

NVGtextRow[] _rows = new NVGtextRow[maxRows];
byte[] str = Encoding.UTF8.GetBytes(@string);
long startOffset = 0;

int rowCount = GetStringPointers(@string, strLength, (strPtr, endPtr) => {
unsafe
{
fixed(NVGtextRow* ptr = &_rows[0])
{
int rowCount = TextBreakLines(ctx, strPtr, endPtr, breakRowWidth, new IntPtr(ptr), maxRows);
startOffset = strPtr.ToInt64();
return rowCount;
}
}
});

if(rowCount <= 0)
return Array.Empty<TextRow>();

List<TextRow> rows = new List<TextRow>();

for(int i = 0; i < rowCount; i++)
{
NVGtextRow row = _rows[i];
int start = (int)(row.start.ToInt64() - startOffset);
int length = (int)(row.end.ToInt64() - row.start.ToInt64());
int nextLine = (int)(row.next.ToInt64() - startOffset);

// Convert from byte pos to char pos
nextLine = Encoding.UTF8.GetCharCount(str, 0, nextLine);

rows.Add(new TextRow(@string, Encoding.UTF8.GetString(str, start, length), nextLine, row.minx, row.maxx, row.width));
}

return rows.ToArray();
}
}
}
20 changes: 10 additions & 10 deletions NanoVG.NET/NVG.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ public struct NVGcompositeOperationState
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct NVGglyphPosition
{
[MarshalAs(UnmanagedType.LPUTF8Str)] public string str;
public IntPtr str;
public float x;
public float minx, maxx;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct NVGtextRow
{
[MarshalAs(UnmanagedType.LPUTF8Str)] public string start;
[MarshalAs(UnmanagedType.LPUTF8Str)] public string end;
[MarshalAs(UnmanagedType.LPUTF8Str)] public string next;
public IntPtr start;
public IntPtr end;
public IntPtr next;
public float width;
public float minx, maxx;
}
Expand Down Expand Up @@ -770,15 +770,15 @@ public static partial class NVG
/// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(Text))]
public static extern float Text(this NVGcontext ctx, float x, float y, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end);
public static extern float Text(this NVGcontext ctx, float x, float y, IntPtr @string, IntPtr end);

/// <summary>
/// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. <br/>
/// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. <br/>
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextBox))]
public static extern void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end);
public static extern void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, IntPtr @string, IntPtr end);

/// <summary>
/// Measures the specified text string. Parameter bounds should be a pointer to float[4], <br/>
Expand All @@ -787,22 +787,22 @@ public static partial class NVG
/// Measured values are returned in local coordinate space.
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextBounds))]
public static extern float TextBounds(this NVGcontext ctx, float x, float y, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end, float[] bounds);
public static extern float TextBounds(this NVGcontext ctx, float x, float y, IntPtr @string, IntPtr end, float[] bounds);

/// <summary>
/// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], <br/>
/// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] <br/>
/// Measured values are returned in local coordinate space.
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextBoxBounds))]
public static extern void TextBoxBounds(this NVGcontext ctx, float x, float y, float breakRowWidth, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end, float[] bounds);
public static extern void TextBoxBounds(this NVGcontext ctx, float x, float y, float breakRowWidth, IntPtr @string, IntPtr end, float[] bounds);

/// <summary>
/// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. <br/>
/// Measured values are returned in local coordinate space.
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextGlyphPositions))]
public static extern int TextGlyphPositions(this NVGcontext ctx, float x, float y, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end, NVGglyphPosition[] positions, int maxPositions);
public static extern int TextGlyphPositions(this NVGcontext ctx, float x, float y, IntPtr @string, IntPtr end, IntPtr positions, int maxPositions);

/// <summary>
/// Returns the vertical metrics based on the current text style. <br/>
Expand All @@ -817,7 +817,7 @@ public static partial class NVG
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
/// </summary>
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextBreakLines))]
public static extern int TextBreakLines(this NVGcontext ctx, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end, float breakRowWidth, NVGtextRow[] rows, int maxRows);
public static extern int TextBreakLines(this NVGcontext ctx, IntPtr @string, IntPtr end, float breakRowWidth, IntPtr rows, int maxRows);

/// <summary>
/// Debug function to dump cached path data.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

Inspired by [Saša Barišić's NanoVG port](https://github.com/sbarisic/nanovg_dotnet) for .NET Framework. Remade from scratch to build a more automated workflow, making it easier to stay up-to-date with NanoVG.

## Screenshot
[![screenshot](screenshot.png)](screenshot.png)

## System requirements
- .NET Core 3.1 or newer

Expand Down
Loading

0 comments on commit 26f9f8d

Please sign in to comment.