/// <summary> /// Sets the formatting for the given range of characters in the line /// </summary> public void SetFormatting(int start, int end, GlyphFormat format, bool onlyChangeColor) { Vector4 bbColor = QuadBoard.GetQuadBoardColor(format.Data.Item4); for (int n = start; n <= end; n++) { if (onlyChangeColor) { var quadBoard = glyphBoards[n]; quadBoard.bbColor = bbColor; glyphBoards[n] = quadBoard; formattedGlyphs[n] = new FormattedGlyph(formattedGlyphs[n].glyph, format); } else if (!formattedGlyphs[n].format.Equals(format)) { IFontStyle fontStyle = FontManager.GetFontStyle(format.Data.Item3); float scale = format.Data.Item2 * fontStyle.FontScale; Glyph glyph = fontStyle[chars[n]]; Vector2 glyphSize = new Vector2(glyph.advanceWidth, fontStyle.Height) * scale; formattedGlyphs[n] = new FormattedGlyph(glyph, format); locData[n] = new GlyphLocData(glyph.MatFrame.Material.size * scale, glyphSize); glyphBoards[n] = glyph.GetQuadBoard(format, bbColor); } } }
/// <summary> /// Updates the position of the right character. /// </summary> private float UpdateCharOffset(Line line, int right, int left, Vector2 pos, float xAlign) { char ch = line.Chars[right]; FormattedGlyph formattedGlyph = line.FormattedGlyphs[right]; IFontStyle fontStyle = FontManager.GetFontStyle(formattedGlyph.format.StyleIndex); float textSize = formattedGlyph.format.TextSize, formatScale = textSize * fontStyle.FontScale, // Quick fix for CJK characters in Space Engineers font data cjkOffset = (formattedGlyph.format.StyleIndex.X == 0 && ch >= 0x4E00) ? (-4f * textSize) : 0f; // Kerning adjustment if (left >= 0) { GlyphFormat leftFmt = line.FormattedGlyphs[left].format, rightFmt = formattedGlyph.format; if (leftFmt.StyleIndex == rightFmt.StyleIndex && leftFmt.TextSize == rightFmt.TextSize) { pos.X += fontStyle.GetKerningAdjustment(line.Chars[left], ch) * formatScale; } } GlyphLocData locData = line.LocData[right]; line.SetOffsetAt(right, new Vector2() { X = pos.X + locData.bbSize.X * .5f + (formattedGlyph.glyph.leftSideBearing * formatScale) + xAlign, Y = pos.Y - (locData.bbSize.Y * .5f) + (fontStyle.BaseLine * formatScale) + cjkOffset }); return(pos.X + locData.chSize.X); }
/// <summary> /// Recalculates the width and height of the line. /// </summary> public void UpdateSize() { _size = Vector2.Zero; if (chars.Count > 0) { for (int n = 0; n < locData.Count; n++) { GlyphLocData sizeData = locData[n]; if (sizeData.chSize.Y > _size.Y) { _size.Y = sizeData.chSize.Y; } float chWidth = sizeData.chSize.X; if (chars[n] == '\t') { FormattedGlyph formattedGlyph = formattedGlyphs[n]; IFontStyle fontStyle = FontManager.GetFontStyle(formattedGlyph.format.StyleIndex); float scale = formattedGlyph.format.TextSize * fontStyle.FontScale; chWidth = formattedGlyphs[n].glyph.advanceWidth * scale; float rem = _size.X % chWidth; if (rem < chWidth * .8f) { chWidth -= rem; } else // if it's really close, just skip to the next stop { chWidth += (chWidth - rem); } sizeData.chSize.X = chWidth; sizeData.bbSize.X = chWidth; locData[n] = sizeData; } _size.X += chWidth; } } }
/// <summary> /// Calculates the horizontal offset needed to ensure that the character specified is within /// the visible range. /// </summary> private float GetCharRangeOffset(Vector2I index) { Line line = lines[index.X]; float offset = _textOffset.X; if (line.Count > 0) { if (index.Y != 0) { GlyphLocData locData = line.LocData[index.Y]; float minOffset = -locData.bbOffset.X + locData.chSize.X * .5f - _fixedSize.X * .5f, maxOffset = minOffset - locData.chSize.X + _fixedSize.X; offset = MathHelper.Clamp(offset, minOffset, maxOffset); } else { offset = 0f; } } return(offset); }
/// <summary> /// Generates underlines for underlined text /// </summary> private void UpdateUnderlines() { int visRange = endLine - startLine; underlines.Clear(); underlines.EnsureCapacity(visRange); for (int ln = startLine; ln <= endLine; ln++) { Line line = lines[ln]; if (line.Count > 0) { GlyphFormatMembers?formatData = line.FormattedGlyphs[0].format.Data; int startCh = 0; for (int ch = 0; ch < lines[ln].Count; ch++) { GlyphFormatMembers?nextFormat = null; if (ch != line.Count - 1) { nextFormat = line.FormattedGlyphs[ch + 1].format.Data; } bool formatEqual = nextFormat != null && formatData.Value.Item1 == nextFormat.Value.Item1 && formatData.Value.Item2 == nextFormat.Value.Item2 && formatData.Value.Item3 == nextFormat.Value.Item3 && formatData.Value.Item4 == nextFormat.Value.Item4; if (!formatEqual) { if (((FontStyles)formatData.Value.Item3.Y & FontStyles.Underline) > 0) { GlyphLocData start = line.LocData[startCh], end = line.LocData[ch]; Vector2 pos = new Vector2 ( (start.bbOffset.X + end.bbOffset.X) * .5f, end.bbOffset.Y - (end.chSize.Y * .5f - (1f * formatData.Value.Item2)) ); Vector2 size = new Vector2 ( (end.bbOffset.X - start.bbOffset.X) + (end.chSize.X + start.chSize.X) * .5f, Math.Max((int)formatData.Value.Item2, 1) ); Vector4 color = QuadBoard.GetQuadBoardColor(formatData.Value.Item4) * .9f; underlines.Add(new UnderlineBoard(size, pos, color)); } startCh = ch; formatData = nextFormat; } } } } if (visRange > 9 && underlines.Capacity > 3 * underlines.Count && underlines.Capacity > visRange) { underlines.TrimExcess(); } }
/// <summary> /// Draws the text board in world space on the XY plane of the matrix, facing in the +Z /// direction. /// </summary> public void Draw(BoundingBox2 box, BoundingBox2 mask, MatrixD[] matrix) { ContainmentType containment; mask.Contains(ref box, out containment); if (containment != ContainmentType.Disjoint) { if (offsetsAreStale) { UpdateOffsets(); } else if (lineRangeIsStale || (endLine == -1 && lines.Count > 0)) { UpdateLineRange(); } if (AutoResize) { _textOffset = Vector2.Zero; } if (updateEvent && (eventTimer.ElapsedTicks / TimeSpan.TicksPerMillisecond) > 500) { TextChanged?.Invoke(); eventTimer.Restart(); updateEvent = false; } BoundingBox2 textMask = box.Intersect(mask); Vector2 offset = box.Center + _textOffset * Scale; IReadOnlyList <Line> lineList = lines.PooledLines; CroppedBox bb = default(CroppedBox); bb.mask = textMask; // Draw glyphs for (int ln = startLine; ln <= endLine && ln < lines.Count; ln++) { Line line = lineList[ln]; for (int ch = 0; ch < line.Count; ch++) { GlyphLocData locData = line.LocData[ch]; Vector2 halfSize = locData.bbSize * Scale * .5f, pos = offset + locData.bbOffset * Scale; bb.bounds = new BoundingBox2(pos - halfSize, pos + halfSize); bb.mask.Value.Contains(ref bb.bounds, out containment); if (containment == ContainmentType.Contains) { line.GlyphBoards[ch].Draw(ref bb, ref matrix[0]); } else if (containment != ContainmentType.Disjoint) { line.GlyphBoards[ch].DrawCroppedTex(ref bb, ref matrix[0]); } } } QuadBoard underlineBoard = QuadBoard.Default; // Draw underlines for (int n = 0; n < underlines.Count; n++) { Vector2 halfSize = underlines[n].size * Scale * .5f, pos = offset + underlines[n].offset * Scale; bb.bounds = new BoundingBox2(pos - halfSize, pos + halfSize); bb.mask.Value.Contains(ref bb.bounds, out containment); underlineBoard.bbColor = underlines[n].color; if (containment == ContainmentType.Contains) { underlineBoard.Draw(ref bb, ref matrix[0]); } else if (containment != ContainmentType.Disjoint) { underlineBoard.DrawCropped(ref bb, ref matrix[0]); } } } }