/// <summary> /// Measures the specified text string. Parameter bounds contains the bounds of the text.<br/> /// Measured values are returned in local coordinate space. /// </summary> /// <param name="bounds">Contains the bounds of the text when returned.</param> /// <returns>The horizontal advance of the measured text (i.e. where the next character should be drawn).</returns> public static float TextBounds(this Nvg nvg, Vector2D <float> pos, string @string, string end, out Rectangle <float> bounds) { bounds = default; Fontstash fons = nvg.fontManager.Fontstash; State state = nvg.stateStack.CurrentState; float scale = nvg.fontManager.GetFontScale() * nvg.pixelRatio.DevicePxRatio; float invscale = 1.0f / scale; if (state.FontId == Fontstash.INVALID) { return(0); } fons.SetSize(state.FontSize * scale); fons.SetSpacing(state.LetterSpacing * scale); fons.SetBlur(state.FontBlur * scale); fons.SetAlign((int)state.TextAlign); fons.SetFont(state.FontId); float width = fons.TextBounds(pos.X * scale, pos.Y * scale, @string, end, out float[] bs); if (bs != null) { fons.LineBounds(pos.Y * scale, out bs[1], out bs[3]); bounds = new Rectangle <float>() { Origin = new Vector2D <float>(bs[0] * invscale, bs[1] * invscale), Size = new Vector2D <float>((bs[2] - bs[0]) * invscale, (bs[3] - bs[1]) * invscale) }; } return(width * invscale); }
public FontManager(Nvg nvg) { _nvg = nvg; for (uint i = 0; i < MAX_FONTIMAGES; i++) { _fontImages[i] = 0; } FonsParams fontParams = new() { width = (int)INIT_FONTIMAGE_SIZE, height = (int)INIT_FONTIMAGE_SIZE, flags = (byte)FonsFlags.ZeroTopleft, renderCreate = null, renderUpdate = null, renderDraw = null, renderDelete = null }; Fontstash = new Fontstash(fontParams); _fontImages[0] = _nvg.renderer.CreateTexture(Texture.Alpha, new Vector2D <uint>((uint)fontParams.width, (uint)fontParams.height), 0, null); if (_fontImages[0] == 0) { _nvg.Dispose(); throw new Exception("Failed to create dummy font atlas!"); } _fontImageIdx = 0; }
/// <summary> /// Fills the current path with current fill style. /// </summary> public static void Fill(this Nvg nvg) { State state = nvg.stateStack.CurrentState; Paint fillPaint = state.Fill; nvg.instructionQueue.FlattenPaths(); if (nvg.renderer.EdgeAntiAlias && nvg.stateStack.CurrentState.ShapeAntiAlias) { nvg.pathCache.ExpandFill(nvg.pixelRatio.FringeWidth, Graphics.LineCap.Miter, 2.4f, nvg.pixelRatio); } else { nvg.pathCache.ExpandFill(0.0f, Graphics.LineCap.Miter, 2.4f, nvg.pixelRatio); } fillPaint.PremultiplyAlpha(nvg.stateStack.CurrentState.Alpha); nvg.renderer.Fill(fillPaint, state.CompositeOperation, state.Scissor, nvg.pixelRatio.FringeWidth, nvg.pathCache.Bounds, nvg.pathCache.Paths); foreach (Path path in nvg.pathCache.Paths) { nvg.FrameMeta.Update(0, 0, (uint)path.Fill.Count - 2 + (uint)path.Stroke.Count - 2, 2); } }
/// <summary> /// Fills the current path with current stroke style. /// </summary> public static void Stroke(this Nvg nvg) { State state = nvg.stateStack.CurrentState; float scale = Maths.GetAverageScale(state.Transform); float strokeWidth = Maths.Clamp(state.StrokeWidth * scale, 0.0f, 200.0f); Paint strokePaint = state.Stroke; if (strokeWidth < nvg.pixelRatio.FringeWidth) { float alpha = Maths.Clamp(strokeWidth / nvg.pixelRatio.FringeWidth, 0.0f, 1.0f); strokePaint.PremultiplyAlpha(alpha * alpha); strokeWidth = nvg.pixelRatio.FringeWidth; } strokePaint.PremultiplyAlpha(state.Alpha); nvg.instructionQueue.FlattenPaths(); if (nvg.renderer.EdgeAntiAlias && state.ShapeAntiAlias) { nvg.pathCache.ExpandStroke(strokeWidth * 0.5f, nvg.pixelRatio.FringeWidth, state.LineCap, state.LineJoin, state.MiterLimit, nvg.pixelRatio); } else { nvg.pathCache.ExpandStroke(strokeWidth * 0.5f, 0.0f, state.LineCap, state.LineJoin, state.MiterLimit, nvg.pixelRatio); } nvg.renderer.Stroke(strokePaint, state.CompositeOperation, state.Scissor, nvg.pixelRatio.FringeWidth, strokeWidth, nvg.pathCache.Paths); foreach (Path path in nvg.pathCache.Paths) { nvg.FrameMeta.Update(0, (uint)path.Stroke.Count - 2, 0, 1); } }
private static void Load() { IInputContext input = window.CreateInput(); foreach (IKeyboard keyboard in input.Keyboards) { keyboard.KeyDown += KeyDown; } foreach (IMouse mouse in input.Mice) { mouse.MouseMove += MouseMove; } gl = window.CreateOpenGL(); OpenGLRenderer nvgRenderer = new(CreateFlags.Antialias | CreateFlags.StencilStrokes | CreateFlags.Debug, gl); nvg = Nvg.Create(nvgRenderer); demo = new Demo(nvg); timer = Stopwatch.StartNew(); timer.Restart(); prevTime = timer.Elapsed.TotalMilliseconds; }
/// <summary> /// Adds an arc segment at the corner defined by the last path point and two specified points. /// </summary> public static void ArcTo(this Nvg nvg, Vector2D <float> p1, Vector2D <float> p2, float radius) { Vector2D <float> p0 = nvg.instructionQueue.EndPosition; if (nvg.instructionQueue.Count == 0) { return; } float distToll = nvg.pixelRatio.DistTol; if (Maths.PtEquals(p0, p1, distToll) || Maths.PtEquals(p1, p2, distToll) || Maths.DistPtSeg(p1, p0, p2) < distToll || radius < distToll) { LineTo(nvg, p1); return; } Vector2D <float> d0 = p0 - p1; Vector2D <float> d1 = p2 - p1; d0 = Vector2D.Normalize(d0); d1 = Vector2D.Normalize(d1); float a = MathF.Acos(d0.X * d1.X + d0.Y * d1.Y); float d = radius / MathF.Tan(a / 2.0f); if (d > 10000.0f) { LineTo(nvg, p1); return; } Winding dir; Vector2D <float> valuesC; float a0, a1; if (Maths.Cross(d0, d1) > 0.0f) { float cx = p1.X + d0.X * d + d0.Y * radius; float cy = p1.Y + d0.Y * d + -d0.X * radius; a0 = MathF.Atan2(d0.X, -d0.Y); a1 = MathF.Atan2(-d1.X, d1.Y); dir = Winding.Cw; valuesC = new Vector2D <float>(cx, cy); } else { float cx = p1.X + d0.X * d + -d0.Y * radius; float cy = p1.Y + d0.Y * d + d0.X * radius; a0 = MathF.Atan2(-d0.X, d0.Y); a1 = MathF.Atan2(d1.X, -d1.Y); dir = Winding.Ccw; valuesC = new Vector2D <float>(cx, cy); } Arc(nvg, valuesC, radius, a0, a1, dir); }
/// <summary> /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. /// </summary> public static void QuadTo(this Nvg nvg, Vector2D <float> cp, Vector2D <float> p) { Vector2D <float> lastPos = nvg.instructionQueue.EndPosition; nvg.instructionQueue.AddBezierTo( lastPos + 2.0f / 3.0f * (cp - lastPos), p + 2.0f / 3.0f * (cp - p), p ); }
/// <summary> /// Creates a new rectangle shaped sub-path. /// </summary> public static void Rect(this Nvg nvg, Rectangle <float> rect) { InstructionQueue queue = nvg.instructionQueue; queue.AddMoveTo(rect.Origin); queue.AddLineTo(new(rect.Origin.X, rect.Max.Y)); queue.AddLineTo(rect.Max); queue.AddLineTo(new(rect.Max.X, rect.Origin.Y)); queue.AddClose(); }
/// <summary> /// Adds a fallback font by handle. /// </summary> public static bool AddFallbackFontId(this Nvg nvg, int baseFont, int fallbackFont) { if (baseFont == -1 || fallbackFont == -1) { return(false); } Fontstash fons = nvg.fontManager.Fontstash; return(fons.AddFallbackFont(baseFont, fallbackFont)); }
/// <summary> /// Finds a loaded font from specified name. /// </summary> /// <returns>Handle to it, or -1 if the font is not found.</returns> public static int FindFont(this Nvg nvg, string name) { if (string.IsNullOrEmpty(name)) { return(-1); } Fontstash fons = nvg.fontManager.Fontstash; return(fons.GetFontByName(name)); }
/// <summary> /// Creates image by loading it from the specified chunk of memory. /// </summary> /// <returns>Handle to the image.</returns> public static int CreateImageMem(this Nvg nvg, ImageFlags imageFlags, byte[] data) { ImageResult result = ImageResult.FromMemory(data, ColorComponents.RedGreenBlueAlpha); if (result == null) { return(0); } int image = CreateImageRgba(nvg, (uint)result.Width, (uint)result.Height, imageFlags, data); return(image); }
/// <summary> /// Calculates the glyph x positions of the specified text. Only the sub-string will be used.<br/> /// Measures values are returned in local coordinate space. /// </summary> public static int TextGlyphPositions(this Nvg nvg, Vector2D <float> pos, string @string, string end, out GlyphPosition[] positions, int maxRows) { positions = new GlyphPosition[maxRows]; Fontstash fons = nvg.fontManager.Fontstash; State state = nvg.stateStack.CurrentState; float scale = nvg.fontManager.GetFontScale() * nvg.pixelRatio.DevicePxRatio; float invscale = 1.0f / scale; FonsQuad q = new(); int npos = 0; if (state.FontId == Fontstash.INVALID) { return(0); } if (@string == end) { return(0); } fons.SetSize(state.FontSize * scale); fons.SetSpacing(state.LetterSpacing * scale); fons.SetBlur(state.FontBlur * scale); fons.SetAlign((int)state.TextAlign); fons.SetFont(state.FontId); fons.TextIterInit(out FonsTextIter iter, pos.X * scale, pos.Y * scale, @string, end, FonsGlyphBitmap.Optional); FonsTextIter prevIter = iter; while (fons.TextIterNext(ref iter, ref q)) { if (iter.prevGlyphIndex < 0 && nvg.fontManager.AllocTextAtlas()) { iter = prevIter; fons.TextIterNext(ref iter, ref q); } prevIter = iter; positions[npos++] = new GlyphPosition(iter.str, iter.x * invscale, MathF.Min(iter.x, q.x0) * invscale, MathF.Max(iter.nextx, q.x1) * invscale); if (npos >= maxRows) { return(npos); } } return(npos); }
private static void Load() { IInputContext input = window.CreateInput(); foreach (IKeyboard keyboard in input.Keyboards) { keyboard.KeyDown += KeyDown; } gl = window.CreateOpenGL(); OpenGLRenderer nvgRenderer = new(CreateFlags.Antialias | CreateFlags.StencilStrokes | CreateFlags.Debug, gl); nvg = Nvg.Create(nvgRenderer); fontSize = 20.0f; _ = nvg.CreateFont("Roboto", "Roboto-Regular.ttf"); }
/// <summary> /// Creates a new rounded rectangle shaped sub-path with varying radii for each corner. /// </summary> public static void RoundedRectVarying(this Nvg nvg, Rectangle <float> rect, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft) { if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { Rect(nvg, rect); } else { InstructionQueue queue = nvg.instructionQueue; float factor = 1 - KAPPA90; Vector2D <float> half = Vector2D.Abs(rect.Size) * 0.5f; Vector2D <float> rBL = new(MathF.Min(radBottomLeft, half.X) * Maths.Sign(rect.Size.X), MathF.Min(radBottomLeft, half.Y) * Maths.Sign(rect.Size.Y)); Vector2D <float> rBR = new(MathF.Min(radBottomRight, half.X) * Maths.Sign(rect.Size.X), MathF.Min(radBottomRight, half.Y) * Maths.Sign(rect.Size.Y)); Vector2D <float> rTR = new(MathF.Min(radTopRight, half.X) * Maths.Sign(rect.Size.X), MathF.Min(radTopRight, half.Y) * Maths.Sign(rect.Size.Y)); Vector2D <float> rTL = new(MathF.Min(radTopLeft, half.X) * Maths.Sign(rect.Size.X), MathF.Min(radTopLeft, half.Y) * Maths.Sign(rect.Size.Y)); queue.AddMoveTo(new(rect.Origin.X, rect.Origin.Y + rTL.Y)); queue.AddLineTo(new(rect.Origin.X, rect.Origin.Y + rect.Size.Y - rBL.Y)); queue.AddBezierTo( new(rect.Origin.X, rect.Origin.Y + rect.Size.Y - rBL.Y * factor), new(rect.Origin.X + rBL.X * factor, rect.Origin.Y + rect.Size.Y), new(rect.Origin.X + rBL.X, rect.Origin.Y + rect.Size.Y) ); queue.AddLineTo(new(rect.Origin.X + rect.Size.X - rBR.X, rect.Origin.Y + rect.Size.Y)); queue.AddBezierTo( new(rect.Origin.X + rect.Size.X - rBR.X * factor, rect.Origin.Y + rect.Size.Y), new(rect.Origin.X + rect.Size.X, rect.Origin.Y + rect.Size.Y - rBR.Y * factor), new(rect.Origin.X + rect.Size.X, rect.Origin.Y + rect.Size.Y - rBR.Y) ); queue.AddLineTo(new(rect.Origin.X + rect.Size.X, rect.Origin.Y + rTR.Y)); queue.AddBezierTo( new(rect.Origin.X + rect.Size.X, rect.Origin.Y + rTR.Y * factor), new(rect.Origin.X + rect.Size.X - rTR.X * factor, rect.Origin.Y), new(rect.Origin.X + rect.Size.X - rTR.X, rect.Origin.Y) ); queue.AddLineTo(new(rect.Origin.X + rTL.X, rect.Origin.Y)); queue.AddBezierTo( new(rect.Origin.X + rTL.X * factor, rect.Origin.Y), new(rect.Origin.X, rect.Origin.Y + rTL.Y * factor), new(rect.Origin.X, rect.Origin.Y + rTL.Y) ); queue.AddClose(); } }
public Demo(Nvg nvg) { _nvg = nvg; for (uint i = 0; i < 12; i++) { string file = "./images/image" + i + ".jpg"; _images[i] = _nvg.CreateImage(file, 0); if (_images[i] == 0) { Console.Error.WriteLine("Could not load " + file); Environment.Exit(-1); } } _fontIcons = _nvg.CreateFont("icons", "./fonts/entypo.ttf"); if (_fontIcons == -1) { Console.Error.WriteLine("Could not add font icons."); Environment.Exit(-1); } _fontNormal = _nvg.CreateFont("sans", "./fonts/Roboto-Regular.ttf"); if (_fontIcons == -1) { Console.Error.WriteLine("Could not add font regular."); Environment.Exit(-1); } _fontBold = _nvg.CreateFont("sans-bold", "./fonts/Roboto-Bold.ttf"); if (_fontIcons == -1) { Console.Error.WriteLine("Could not add font bold."); Environment.Exit(-1); } _fontEmoji = _nvg.CreateFont("emoji", "./fonts/NotoEmoji-Regular.ttf"); if (_fontIcons == -1) { Console.Error.WriteLine("Could not add font emoji."); Environment.Exit(-1); } _ = _nvg.AddFallbackFontId(_fontNormal, _fontEmoji); _ = _nvg.AddFallbackFontId(_fontBold, _fontEmoji); }
/// <summary> /// Sets the current scissor rectangle. /// The scissor rectangle is transformed by the current transform. /// </summary> public static void Scissor(this Nvg nvg, Rectangle <float> rect) { Vector2D <float> pos = rect.Origin; Vector2D <float> size = rect.Size; size.X = MathF.Max(0.0f, size.X); size.Y = MathF.Max(0.0f, size.Y); Vector2D <float> lastRow = pos + size * 0.5f; Matrix3X2 <float> transform = Matrix3X2 <float> .Identity; transform.M31 = lastRow.X; transform.M32 = lastRow.Y; nvg.stateStack.CurrentState.Scissor = new( Transforms.NvgTransforms.Multiply(transform, nvg.stateStack.CurrentState.Transform), size * 0.5f ); }
/// <summary> /// Draws multi-line text string at specified location wrapped at the specified width. Only the sub-string up to the end is drawn. /// White space is stripped at the beginning of the rows, the text is split at word boundries or when new-line characters are encountered. /// Words longer than the max width are slit at nearest character (i.e. no hyphenation). /// </summary> public static void TextBox(this Nvg nvg, Vector2D <float> pos, float breakRowWidth, string @string, string end) { State state = nvg.stateStack.CurrentState; int rowCount; Align oldAlign = state.TextAlign; Align hAlign = state.TextAlign & (Align.Left | Align.Centre | Align.Right); Align vAlign = state.TextAlign & (Align.Top | Align.Middle | Align.Bottom | Align.Baseline); if (state.FontId == Fontstash.INVALID) { return; } TextMetrics(nvg, out _, out _, out float lineh); state.TextAlign = Align.Left | vAlign; while ((rowCount = TextBreakLines(nvg, @string, end, breakRowWidth, out TextRow[] rows, 2)) != 0) { for (int i = 0; i < rowCount; i++) { if (hAlign.HasFlag(Align.Left)) { _ = Text(nvg, pos, rows[i].Start, rows[i].End.Length > 0 ? rows[i].End : null); } else if (hAlign.HasFlag(Align.Centre)) { _ = Text(nvg, pos.X + breakRowWidth * 0.5f, pos.Y - rows[i].Width * 0.5f, rows[i].Start, rows[i].End.Length > 0 ? rows[i].End : null); } else if (hAlign.HasFlag(Align.Right)) { _ = Text(nvg, pos.X + breakRowWidth - rows[i].Width, pos.Y, rows[i].Start, rows[i].End.Length > 0 ? rows[i].End : null); } pos.Y += lineh * state.LineHeight; } @string = rows[rowCount - 1].Next; } state.TextAlign = oldAlign; }
/// <summary> /// Creates a new ellipse shaped sub-path. /// </summary> public static void Ellipse(this Nvg nvg, Vector2D <float> c, float rx, float ry) { InstructionQueue queue = nvg.instructionQueue; queue.AddMoveTo(new(c.X - rx, c.Y)); queue.AddBezierTo( new(c.X - rx, c.Y + ry * KAPPA90), new(c.X - rx * KAPPA90, c.Y + ry), new(c.X, c.Y + ry)); queue.AddBezierTo( new(c.X + rx * KAPPA90, c.Y + ry), new(c.X + rx, c.Y + ry * KAPPA90), new(c.X + rx, c.Y)); queue.AddBezierTo( new(c.X + rx, c.Y - ry * KAPPA90), new(c.X + rx * KAPPA90, c.Y - ry), new(c.X, c.Y - ry)); queue.AddBezierTo( new(c.X - rx * KAPPA90, c.Y - ry), new(c.X - rx, c.Y - ry * KAPPA90), new(c.X - rx, c.Y)); queue.AddClose(); }
/// <summary> /// Creates image by loading it from the disk from specified file name. /// </summary> /// <returns>Handle to the image.</returns> public static int CreateImage(this Nvg nvg, string fileName, ImageFlags imageFlags) { Stream stream; try { stream = File.OpenRead(fileName); } catch { return(0); } ImageResult result = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); if (result == null) { return(0); } int image = CreateImageRgba(nvg, (uint)result.Width, (uint)result.Height, imageFlags, result.Data); stream.Close(); stream.Dispose(); return(image); }
/// <summary> /// Returns the vertical metrics based on the current text style.<br/> /// Measured values are returned in local coordinate space. /// </summary> public static void TextMetrics(this Nvg nvg, out float ascender, out float descender, out float lineh) { Fontstash fons = nvg.fontManager.Fontstash; State state = nvg.stateStack.CurrentState; float scale = nvg.fontManager.GetFontScale() * nvg.pixelRatio.DevicePxRatio; float invscale = 1.0f / scale; if (state.FontId == Fontstash.INVALID) { ascender = descender = lineh = -1.0f; return; } fons.SetSize(state.FontSize * scale); fons.SetSpacing(state.LetterSpacing * scale); fons.SetBlur(state.FontBlur * scale); fons.SetAlign((int)state.TextAlign); fons.SetFont(state.FontId); fons.VertMetrics(out ascender, out descender, out lineh); ascender *= invscale; descender *= invscale; lineh *= invscale; }
/// <inheritdoc cref="Paint.ImagePattern(Rectangle{float}, float, int, float)"/> public static Paint ImagePattern(this Nvg _, Rectangle <float> bounds, float angle, int image, float alpha) => Paint.ImagePattern(bounds, angle, image, alpha);
/// <inheritdoc cref="Paint.ImagePattern(float, float, float, float, float, int, float)"/> public static Paint ImagePattern(this Nvg _, float ox, float oy, float width, float height, float angle, int image, float alpha) => Paint.ImagePattern(ox, oy, width, height, angle, image, alpha);
/// <inheritdoc cref="Paint.ImagePattern(Vector2D{float}, Vector2D{float}, float, int, float)"/> public static Paint ImagePattern(this Nvg _, Vector2D <float> pos, Vector2D <float> size, float angle, int image, float alpha) => Paint.ImagePattern(pos, size, angle, image, alpha);
/// <inheritdoc cref="Rotate(float)"/> public static Matrix3X2 <float> TransformRotate(this Nvg _, float a) => Rotate(a);
/// <inheritdoc cref="Scale(Vector2D{float})"/> public static Matrix3X2 <float> TransformScale(this Nvg _, float x, float y) => Scale(new Vector2D <float>(x, y));
/// <inheritdoc cref="Scale(Vector2D{float})"/> public static Matrix3X2 <float> TransformScale(this Nvg _, Vector2D <float> s) => Scale(s);
/// <inheritdoc cref="Translate(Vector2D{float})"/> public static Matrix3X2 <float> TransformTranslate(this Nvg _, Vector2D <float> t) => Translate(t);
/// <inheritdoc cref="RadToDeg(float)"/> public static float RadToDeg(this Nvg _, float rad) => RadToDeg(rad);
/// <inheritdoc cref="DegToRad(float)"/> public static float DegToRad(this Nvg _, float deg) => DegToRad(deg);
/// <inheritdoc cref="Identity"/> public static Matrix3X2 <float> TransformIdentity(this Nvg _) => Identity;