Skip to content

Commit 26f9f8d

Browse files
Corrected P/Invoke signatures for text handling and finalized demo
1 parent 5cfe480 commit 26f9f8d

File tree

9 files changed

+281
-72
lines changed

9 files changed

+281
-72
lines changed

Generator/CodeTranslation/Generators/PInvokeGenerator.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,21 @@ public class PInvokeGenerator : ICodeGenerator
5353
{ "nvgTransformMultiply", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
5454
{ "nvgTransformPremultiply", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
5555
{ "nvgTransformInverse", new ArgumentMap() {{ "dst", type => "float[]" }, { "src", type => "float[]" }} },
56-
{ "nvgTransformPoint", new ArgumentMap() {{ "dstx", type => "float[]" }, { "dsty", type => "float[]" }, { "xform", type => "float[]" }} },
56+
{ "nvgTransformPoint", new ArgumentMap() {{ "dstx", type => "float[]" }, { "dsty", type => "float[]" }, { "xform", type => "float[]" }} },
5757
{ "nvgImageSize", new ArgumentMap() {{ "w", type => "out int" }, { "h", type => "out int" }} },
58-
{ "nvgTextMetrics", new ArgumentMap() {{ "ascender", type => "out float" }, { "descender", type => "out float" }, { "lineh", type => "out float" }} },
59-
{ "nvgTextBounds", new ArgumentMap() {{ "bounds", type => "float[]" }} },
60-
{ "nvgTextBoxBounds", new ArgumentMap() {{ "bounds", type => "float[]" }} },
61-
{ "nvgTextGlyphPositions", new ArgumentMap() {{ "positions", type => type.Pointer == PointerType.Standard ? $"{type.Name}[]" : null }} },
62-
{ "nvgTextBreakLines", new ArgumentMap() {{ "rows", type => type.Pointer == PointerType.Standard ? $"{type.Name}[]" : null }} },
58+
{ "nvgTextMetrics", new ArgumentMap() {{ "ascender", type => "out float" }, { "descender", type => "out float" }, { "lineh", type => "out float" }} },
59+
{ "nvgText", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }} },
60+
{ "nvgTextBounds", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "bounds", type => "float[]" }} },
61+
{ "nvgTextBox", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }} },
62+
{ "nvgTextBoxBounds", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "bounds", type => "float[]" }} },
63+
{ "nvgTextGlyphPositions", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "positions", type => type.Pointer == PointerType.Standard ? $"IntPtr" : null }} },
64+
{ "nvgTextBreakLines", new ArgumentMap() {{ "string", type => "IntPtr" }, { "end", type => "IntPtr" }, { "rows", type => type.Pointer == PointerType.Standard ? $"IntPtr" : null }} },
65+
};
66+
67+
private static readonly Dictionary<string, ArgumentMap> StructFieldOverrides = new Dictionary<string, ArgumentMap>() {
68+
// { StructName, {{ FieldName, FieldType }} }
69+
{ "NVGglyphPosition", new ArgumentMap() {{ "str", type => "IntPtr" }} },
70+
{ "NVGtextRow", new ArgumentMap() {{ "start", type => "IntPtr" }, { "end", type => "IntPtr" }, { "next", type => "IntPtr" }} },
6371
};
6472

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

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

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

352360
return ConvertType(type, arraySuffix);
353361
}
362+
363+
// Handles special cases
364+
private static string ConvertFieldType(StructDefinition @struct, FieldDefinition field, bool arraySuffix = true)
365+
{
366+
TypeDefinition type = field.Type;
367+
368+
if(StructFieldOverrides.TryGetValue(@struct.Name, out var map) && map.TryGetValue(field.Name, out var fieldOverride))
369+
{
370+
var newType = fieldOverride(type);
371+
if(newType != null)
372+
return newType;
373+
}
374+
375+
return ConvertType(type, arraySuffix);
376+
}
354377
}
355378
}

NanoVG.NET.sln

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NanoVG.Native", "NanoVG.Nat
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator\Generator.csproj", "{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}"
1717
EndProject
18-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{90C6EC8D-BF79-4D14-A069-6C198817D636}"
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{90C6EC8D-BF79-4D14-A069-6C198817D636}"
1919
EndProject
2020
Global
2121
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -62,8 +62,8 @@ Global
6262
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x64.Build.0 = Release|Any CPU
6363
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x86.ActiveCfg = Release|Any CPU
6464
{363756D7-CA1B-4870-AD45-A2AAD3A6A9FB}.Release|x86.Build.0 = Release|Any CPU
65-
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66-
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.Build.0 = Debug|Any CPU
65+
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.ActiveCfg = Debug|x86
66+
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|Any CPU.Build.0 = Debug|x86
6767
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x64.ActiveCfg = Debug|Any CPU
6868
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x64.Build.0 = Debug|Any CPU
6969
{90C6EC8D-BF79-4D14-A069-6C198817D636}.Debug|x86.ActiveCfg = Debug|Any CPU

NanoVG.NET/NVG.cs

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.InteropServices;
4+
using System.Text;
35

46
namespace NanoVG
57
{
6-
public static unsafe partial class NVG
8+
public static partial class NVG
79
{
810
static NVG()
911
{
@@ -44,10 +46,186 @@ public struct NVGcolor
4446
public float r,g,b,a;
4547
}
4648

49+
public class TextRow
50+
{
51+
public string SourceText { get; }
52+
public string Text { get; }
53+
public int NextLinePosition { get; }
54+
public float MaxX { get; }
55+
public float MinX { get; }
56+
public float Width { get; }
57+
58+
internal TextRow(string sourceText, string text, int nextLinePos, float minX, float maxX, float width)
59+
{
60+
SourceText = sourceText;
61+
Text = text;
62+
NextLinePosition = nextLinePos;
63+
MinX = minX;
64+
MaxX = maxX;
65+
Width = width;
66+
}
67+
}
68+
4769
public enum NVGcreateFlags
4870
{
4971
NVG_ANTIALIAS = 1<<0,
5072
NVG_STENCIL_STROKES = 1<<1,
5173
NVG_DEBUG = 1<<2,
5274
}
75+
76+
public static partial class NVG
77+
{
78+
private static T GetStringPointers<T>(string str, int length, Func<IntPtr, IntPtr, T> callback)
79+
{
80+
if(length > str.Length)
81+
throw new ArgumentOutOfRangeException(nameof(length));
82+
83+
byte[] utf8 = Encoding.UTF8.GetBytes(str);
84+
int byteLength = length > 0 ? Encoding.UTF8.GetByteCount(str, 0, length) : 0;
85+
86+
GCHandle handle = default;
87+
try
88+
{
89+
handle = GCHandle.Alloc(utf8, GCHandleType.Pinned);
90+
91+
IntPtr strPtr = handle.AddrOfPinnedObject();
92+
IntPtr endPtr = length > 0 ? endPtr = IntPtr.Add(strPtr, byteLength) : IntPtr.Zero;
93+
94+
return callback(strPtr, endPtr);
95+
}
96+
finally
97+
{
98+
if(handle.IsAllocated)
99+
handle.Free();
100+
}
101+
}
102+
103+
/// <summary>
104+
/// Draws text string at specified location.
105+
/// </summary>
106+
public static float Text(this NVGcontext ctx, float x, float y, string @string, int length = -1)
107+
{
108+
return GetStringPointers(@string, length, (strPtr, endPtr) => Text(ctx, x, y, strPtr, endPtr));
109+
}
110+
111+
/// <summary>
112+
/// Draws multi-line text string at specified location wrapped at the specified width. <br/>
113+
/// 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/>
114+
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
115+
/// </summary>
116+
public static void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, string @string, int length = -1)
117+
{
118+
GetStringPointers(@string, length, (strPtr, endPtr) => {
119+
TextBox(ctx, x, y, breakRowWidth, strPtr, endPtr);
120+
return (object)null;
121+
});
122+
}
123+
124+
/// <summary>
125+
/// Measures the specified text string. Parameter bounds should be a pointer to float[4], <br/>
126+
/// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] <br/>
127+
/// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). <br/>
128+
/// Measured values are returned in local coordinate space.
129+
/// </summary>
130+
public static float TextBounds(this NVGcontext ctx, float x, float y, string @string, out float[] bounds, int strLength = -1)
131+
{
132+
float[] retBounds = new float[4];
133+
float retVal = GetStringPointers(@string, strLength, (strPtr, endPtr) => TextBounds(ctx, x, y, strPtr, endPtr, retBounds));
134+
135+
bounds = retBounds;
136+
return retVal;
137+
}
138+
139+
/// <summary>
140+
/// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], <br/>
141+
/// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] <br/>
142+
/// Measured values are returned in local coordinate space.
143+
/// </summary>
144+
public static void TextBoxBounds(this NVGcontext ctx, float x, float y, float breakRowWidth, string @string, out float[] bounds, int strLength = -1)
145+
{
146+
float[] retBounds = new float[4];
147+
148+
GetStringPointers(@string, strLength, (strPtr, endPtr) => {
149+
TextBoxBounds(ctx, x, y, breakRowWidth, strPtr, endPtr, retBounds);
150+
return (object)null;
151+
});
152+
153+
bounds = retBounds;
154+
}
155+
156+
/// <summary>
157+
/// Calculates the glyph x positions of the specified text. Measured values are returned in local coordinate space.
158+
/// </summary>
159+
public static NVGglyphPosition[] TextGlyphPositions(this NVGcontext ctx, float x, float y, string @string, int maxPositions, int strLength = -1)
160+
{
161+
if(maxPositions <= 0)
162+
return Array.Empty<NVGglyphPosition>();
163+
164+
NVGglyphPosition[] positions = new NVGglyphPosition[maxPositions];
165+
int posCount = GetStringPointers(@string, strLength, (strPtr, endPtr) => {
166+
unsafe
167+
{
168+
fixed(NVGglyphPosition* ptr = &positions[0])
169+
{
170+
return TextGlyphPositions(ctx, x, y, strPtr, endPtr, new IntPtr(ptr), maxPositions);
171+
}
172+
}
173+
});
174+
175+
if(posCount <= 0)
176+
return Array.Empty<NVGglyphPosition>();
177+
178+
if(posCount != positions.Length)
179+
Array.Resize(ref positions, posCount);
180+
181+
return positions;
182+
}
183+
184+
/// <summary>
185+
/// Breaks the specified text into lines. <br/>
186+
/// 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/>
187+
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
188+
/// </summary>
189+
public static TextRow[] TextBreakLines(this NVGcontext ctx, string @string, float breakRowWidth, int maxRows, int strLength = -1)
190+
{
191+
if(@string.Length <= 0)
192+
return Array.Empty<TextRow>();
193+
194+
NVGtextRow[] _rows = new NVGtextRow[maxRows];
195+
byte[] str = Encoding.UTF8.GetBytes(@string);
196+
long startOffset = 0;
197+
198+
int rowCount = GetStringPointers(@string, strLength, (strPtr, endPtr) => {
199+
unsafe
200+
{
201+
fixed(NVGtextRow* ptr = &_rows[0])
202+
{
203+
int rowCount = TextBreakLines(ctx, strPtr, endPtr, breakRowWidth, new IntPtr(ptr), maxRows);
204+
startOffset = strPtr.ToInt64();
205+
return rowCount;
206+
}
207+
}
208+
});
209+
210+
if(rowCount <= 0)
211+
return Array.Empty<TextRow>();
212+
213+
List<TextRow> rows = new List<TextRow>();
214+
215+
for(int i = 0; i < rowCount; i++)
216+
{
217+
NVGtextRow row = _rows[i];
218+
int start = (int)(row.start.ToInt64() - startOffset);
219+
int length = (int)(row.end.ToInt64() - row.start.ToInt64());
220+
int nextLine = (int)(row.next.ToInt64() - startOffset);
221+
222+
// Convert from byte pos to char pos
223+
nextLine = Encoding.UTF8.GetCharCount(str, 0, nextLine);
224+
225+
rows.Add(new TextRow(@string, Encoding.UTF8.GetString(str, start, length), nextLine, row.minx, row.maxx, row.width));
226+
}
227+
228+
return rows.ToArray();
229+
}
230+
}
53231
}

NanoVG.NET/NVG.g.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ public struct NVGcompositeOperationState
3232
[StructLayout(LayoutKind.Sequential, Pack = 1)]
3333
public unsafe struct NVGglyphPosition
3434
{
35-
[MarshalAs(UnmanagedType.LPUTF8Str)] public string str;
35+
public IntPtr str;
3636
public float x;
3737
public float minx, maxx;
3838
}
3939

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

775775
/// <summary>
776776
/// 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/>
777777
/// 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/>
778778
/// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
779779
/// </summary>
780780
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextBox))]
781-
public static extern void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, [MarshalAs(UnmanagedType.LPUTF8Str)] string @string, [MarshalAs(UnmanagedType.LPUTF8Str)] string end);
781+
public static extern void TextBox(this NVGcontext ctx, float x, float y, float breakRowWidth, IntPtr @string, IntPtr end);
782782

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

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

800800
/// <summary>
801801
/// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. <br/>
802802
/// Measured values are returned in local coordinate space.
803803
/// </summary>
804804
[DllImport(LibraryName, EntryPoint = FunctionPrefix + nameof(TextGlyphPositions))]
805-
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);
805+
public static extern int TextGlyphPositions(this NVGcontext ctx, float x, float y, IntPtr @string, IntPtr end, IntPtr positions, int maxPositions);
806806

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

822822
/// <summary>
823823
/// Debug function to dump cached path data.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
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.
55

6+
## Screenshot
7+
[![screenshot](screenshot.png)](screenshot.png)
8+
69
## System requirements
710
- .NET Core 3.1 or newer
811

0 commit comments

Comments
 (0)