private void ValidateVertexCache() { if (this.vertTextCache != null && this.vertIconsCache != null && this.vertCountCache != null && this.metricsCache != null && !this.updateVertexCache) { // No need to update. return; } this.updateVertexCache = false; int fontNum = this.fonts != null ? this.fonts.Length : 0; // Setting up buffers { int countCacheLen = 1 + fontNum; if (this.vertCountCache == null || this.vertCountCache.Length != countCacheLen) this.vertCountCache = new int[countCacheLen]; int iconVertCount = this.iconCount * 4; if (this.vertIconsCache == null || this.vertIconsCache.Length < iconVertCount) this.vertIconsCache = new VertexC1P3T2[iconVertCount]; this.vertCountCache[0] = iconVertCount; if (this.vertTextCache == null || this.vertTextCache.Length != fontNum) this.vertTextCache = new VertexC1P3T2[fontNum][]; for (int i = 0; i < this.vertTextCache.Length; i++) { int textVertCount = this.fontGlyphCount.Length > i ? this.fontGlyphCount[i] * 4 : 0; this.vertCountCache[i + 1] = textVertCount; if (this.vertTextCache[i] == null || this.vertTextCache[i].Length < textVertCount) this.vertTextCache[i] = new VertexC1P3T2[textVertCount]; } } // Rendering { RenderState state = new RenderState(this); Element elem; int[] vertTextLen = new int[fontNum]; int vertIconLen = 0; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; VertexC1P3T2[] textElemVert = null; int count = state.Font.EmitTextVertices( state.CurrentElemText, ref textElemVert, state.CurrentElemOffset.X, state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); Array.Copy(textElemVert, 0, this.vertTextCache[state.FontIndex], state.CurrentElemTextVertexIndex, count); vertTextLen[state.FontIndex] = state.CurrentElemTextVertexIndex + count; } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconOffset = icon.offset; Vector2 iconSize = icon.size; Rect iconUvRect = icon.uvRect; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.X = state.CurrentElemOffset.X + iconOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y + iconOffset.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].TexCoord = iconUvRect.TopLeft; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.X = state.CurrentElemOffset.X + iconSize.X + iconOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y + iconOffset.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].TexCoord = iconUvRect.TopRight; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.X = state.CurrentElemOffset.X + iconSize.X + iconOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine + iconOffset.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].TexCoord = iconUvRect.BottomRight; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.X = state.CurrentElemOffset.X + iconOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine + iconOffset.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].TexCoord = iconUvRect.BottomLeft; vertIconLen = state.CurrentElemIconVertexIndex + 4; } } this.vertCountCache[0] = vertIconLen; for (int i = 0; i < fontNum; i++) this.vertCountCache[i + 1] = vertTextLen[i]; } // Updating the metrics cache { Vector2 size = Vector2.Zero; List<Rect> lineBounds = new List<Rect>(16); List<Rect> elementBounds = new List<Rect>(this.elements.Length); RenderState state = new RenderState(this); Element elem; Vector2 elemSize; Vector2 elemOffset; int lastElemIndex = -1; int lastLineIndex = 0; bool elemIndexChanged = true; bool lineChanged = true; bool hasBounds; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; elemSize = state.Font.MeasureText(state.CurrentElemText); elemOffset = new Vector2(state.CurrentElemOffset.X, state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine); } else if (elem is IconElement && this.icons != null) { IconElement iconElem = elem as IconElement; bool iconValid = iconElem.IconIndex > 0 && iconElem.IconIndex < this.icons.Length; elemSize = iconValid ? this.icons[iconElem.IconIndex].size : Vector2.Zero; elemOffset = new Vector2(state.CurrentElemOffset.X, state.CurrentElemOffset.Y + state.LineBaseLine - elemSize.Y); } else { elemSize = Vector2.Zero; elemOffset = Vector2.Zero; } hasBounds = elemSize != Vector2.Zero; if (elemIndexChanged) elementBounds.Add(Rect.Empty); if (hasBounds && elementBounds[elementBounds.Count - 1] == Rect.Empty) elementBounds[elementBounds.Count - 1] = new Rect(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); else if (hasBounds) elementBounds[elementBounds.Count - 1] = elementBounds[elementBounds.Count - 1].ExpandedToContain(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); if (lineChanged) lineBounds.Add(Rect.Empty); if (hasBounds && lineBounds[lineBounds.Count - 1] == Rect.Empty) lineBounds[lineBounds.Count - 1] = new Rect(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); else if (hasBounds) lineBounds[lineBounds.Count - 1] = lineBounds[lineBounds.Count - 1].ExpandedToContain(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); size.X = Math.Max(size.X, elemOffset.X + elemSize.X); size.Y = Math.Max(size.Y, elemOffset.Y + elemSize.Y); elemIndexChanged = lastElemIndex != state.CurrentElemIndex; lineChanged = lastLineIndex != state.CurrentLineIndex; lastElemIndex = state.CurrentElemIndex; lastLineIndex = state.CurrentLineIndex; } this.metricsCache = new Metrics(size, lineBounds, elementBounds); } }
/// <summary> /// Renders a text to the specified target Image. /// </summary> /// <param name="text"></param> /// <param name="target"></param> public void RenderToBitmap(string text, PixelData target, float x = 0.0f, float y = 0.0f, PixelData icons = null) { // Rendering int fontNum = this.fonts != null ? this.fonts.Length : 0; RenderState state = new RenderState(this); Element elem; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; state.Font.RenderToBitmap( state.CurrentElemText, target, x + state.CurrentElemOffset.X, y + state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconSize = icon.size; Vector2 iconOffset = icon.offset; Rect iconUvRect = icon.uvRect; Vector2 dataCoord = iconUvRect.Pos * new Vector2(icons.Width, icons.Height); Vector2 dataSize = iconUvRect.Size * new Vector2(icons.Width, icons.Height); PixelData iconLayer = icons.CloneSubImage( MathF.RoundToInt(dataCoord.X), MathF.RoundToInt(dataCoord.Y), MathF.RoundToInt(dataSize.X), MathF.RoundToInt(dataSize.Y)); iconLayer.Rescale( MathF.RoundToInt(iconSize.X), MathF.RoundToInt(iconSize.Y)); iconLayer.DrawOnto(target, BlendMode.Alpha, MathF.RoundToInt(x + state.CurrentElemOffset.X + iconOffset.X), MathF.RoundToInt(y + state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y + iconOffset.Y), iconLayer.Width, iconLayer.Height, 0, 0, state.Color); } } }
/// <summary> /// Measures the formatted text block. /// </summary> /// <returns>A <see cref="Metrics"/> object that describes this FormattedText.</returns> public Metrics Measure() { Vector2 size = Vector2.Zero; List<Rect> lineBounds = new List<Rect>(); List<Rect> elementBounds = new List<Rect>(); RenderState state = new RenderState(this); Element elem; Vector2 elemSize; Vector2 elemOffset; int lastElemIndex = -1; int lastLineIndex = 0; bool elemIndexChanged = true; bool lineChanged = true; bool hasBounds; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; elemSize = state.Font.MeasureText(state.CurrentElemText); elemOffset = new Vector2(state.CurrentElemOffset.X, state.CurrentElemOffset.Y/* + state.LineBaseLine - state.Font.Ascent*/); //if (elemSize.Y != 0.0f) elemSize.Y -= state.LineBaseLine - state.Font.Ascent; } else if (elem is IconElement && this.icons != null) { IconElement iconElem = elem as IconElement; bool iconValid = iconElem.IconIndex > 0 && iconElem.IconIndex < this.icons.Length; elemSize = iconValid ? this.icons[iconElem.IconIndex].size : Vector2.Zero; elemOffset = new Vector2(state.CurrentElemOffset.X, state.CurrentElemOffset.Y/* + state.LineBaseLine - elemSize.Y*/); //if (elemSize.Y != 0.0f) elemSize.Y -= state.LineBaseLine - elemSize.Y; } else { elemSize = Vector2.Zero; elemOffset = Vector2.Zero; } hasBounds = elemSize != Vector2.Zero; if (elemIndexChanged) elementBounds.Add(Rect.Empty); if (hasBounds && elementBounds[elementBounds.Count - 1] == Rect.Empty) elementBounds[elementBounds.Count - 1] = new Rect(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); else if (hasBounds) elementBounds[elementBounds.Count - 1] = elementBounds[elementBounds.Count - 1].ExpandToContain(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); if (lineChanged) lineBounds.Add(Rect.Empty); if (hasBounds && lineBounds[lineBounds.Count - 1] == Rect.Empty) lineBounds[lineBounds.Count - 1] = new Rect(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); else if (hasBounds) lineBounds[lineBounds.Count - 1] = lineBounds[lineBounds.Count - 1].ExpandToContain(elemOffset.X, elemOffset.Y, elemSize.X, elemSize.Y); size.X = Math.Max(size.X, elemOffset.X + elemSize.X); size.Y = Math.Max(size.Y, elemOffset.Y + elemSize.Y); elemIndexChanged = lastElemIndex != state.CurrentElemIndex; lineChanged = lastLineIndex != state.CurrentLineIndex; lastElemIndex = state.CurrentElemIndex; lastLineIndex = state.CurrentLineIndex; } return new Metrics(size, lineBounds, elementBounds); }
/// <summary> /// Renders a text to the specified target Image. /// </summary> /// <param name="text"></param> /// <param name="target"></param> public void RenderToBitmap(string text, System.Drawing.Image target, float x = 0.0f, float y = 0.0f, System.Drawing.Image icons = null) { using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(target)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; // Rendering int fontNum = this.fonts != null ? this.fonts.Length : 0; RenderState state = new RenderState(this); Element elem; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; state.Font.RenderToBitmap( state.CurrentElemText, target, x + state.CurrentElemOffset.X, y + state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconSize = icon.size; Vector2 iconOffset = icon.offset; Rect iconUvRect = icon.uvRect; Vector2 dataCoord = iconUvRect.Pos * new Vector2(icons.Width, icons.Height); Vector2 dataSize = iconUvRect.Size * new Vector2(icons.Width, icons.Height); var attrib = new System.Drawing.Imaging.ImageAttributes(); attrib.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(new[] { new[] {state.Color.R / 255.0f, 0, 0, 0}, new[] {0, state.Color.G / 255.0f, 0, 0}, new[] {0, 0, state.Color.B / 255.0f, 0}, new[] {0, 0, 0, state.Color.A / 255.0f} })); g.DrawImage(icons, new System.Drawing.Rectangle( MathF.RoundToInt(x + state.CurrentElemOffset.X + iconOffset.X), MathF.RoundToInt(y + state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y + iconOffset.Y), MathF.RoundToInt(iconSize.X), MathF.RoundToInt(iconSize.Y)), dataCoord.X, dataCoord.Y, dataSize.X, dataSize.Y, System.Drawing.GraphicsUnit.Pixel, attrib); } } } }
private void ValidateVertexCache() { if (this.vertTextCache != null && this.vertIconsCache != null && !this.updateVertexCache) return; this.updateVertexCache = false; int fontNum = this.fonts != null ? this.fonts.Length : 0; // Setting up vertex buffers if (this.vertIconsCache == null || this.vertIconsCache.Length != this.iconCount * 4) this.vertIconsCache = new VertexC1P3T2[this.iconCount * 4]; if (this.vertTextCache == null || this.vertTextCache.Length != fontNum) this.vertTextCache = new VertexC1P3T2[fontNum][]; for (int i = 0; i < this.vertTextCache.Length; i++) if (this.vertTextCache[i] == null || this.vertTextCache[i].Length != (this.fontGlyphCount.Length > i ? this.fontGlyphCount[i] * 4 : 0)) this.vertTextCache[i] = new VertexC1P3T2[this.fontGlyphCount.Length > i ? this.fontGlyphCount[i] * 4 : 0]; // Rendering RenderState state = new RenderState(this); Element elem; int[] vertTextLen = new int[fontNum]; int vertIconLen = 0; while ((elem = state.NextElement()) != null) { if (elem is TextElement && state.Font != null) { TextElement textElem = elem as TextElement; VertexC1P3T2[] textElemVert = null; state.Font.EmitTextVertices( state.CurrentElemText, ref textElemVert, state.CurrentElemOffset.X, state.CurrentElemOffset.Y + state.LineBaseLine - state.Font.BaseLine, state.Color); Array.Copy(textElemVert, 0, this.vertTextCache[state.FontIndex], state.CurrentElemTextVertexIndex, textElemVert.Length); vertTextLen[state.FontIndex] = state.CurrentElemTextVertexIndex + textElemVert.Length; } else if (elem is IconElement) { IconElement iconElem = elem as IconElement; Icon icon = iconElem.IconIndex >= 0 && iconElem.IconIndex < this.icons.Length ? this.icons[iconElem.IconIndex] : new Icon(); Vector2 iconSize = icon.size; Rect iconUvRect = icon.uvRect; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.X = state.CurrentElemOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 0].TexCoord = iconUvRect.TopLeft; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.X = state.CurrentElemOffset.X + iconSize.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine - iconSize.Y; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 1].TexCoord = iconUvRect.TopRight; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.X = state.CurrentElemOffset.X + iconSize.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 2].TexCoord = iconUvRect.BottomRight; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.X = state.CurrentElemOffset.X; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.Y = state.CurrentElemOffset.Y + state.LineBaseLine; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Pos.Z = 0; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].Color = state.Color; this.vertIconsCache[state.CurrentElemIconVertexIndex + 3].TexCoord = iconUvRect.BottomLeft; vertIconLen = state.CurrentElemIconVertexIndex + 4; } } for (int i = 0; i < fontNum; i++) { if (this.vertTextCache[i].Length > vertTextLen[i]) Array.Resize(ref this.vertTextCache[i], vertTextLen[i]); } if (this.vertIconsCache.Length > vertIconLen) Array.Resize(ref this.vertIconsCache, vertIconLen); }