private static string ToPlainText_Slow(AbstractString richText, int firstDollarOffset) { var result = new StringBuilder(); for (int i = 0; i < firstDollarOffset; i++) { result.Append(richText[i]); } int rangeStarted = 0; char?closer = null; for (int i = firstDollarOffset, l = richText.Length; i < l; i++) { var ch = richText[i]; var next = (i < l - 1) ? richText[i + 1] : '\0'; if (closer != null) { if (ch == closer) { if (closer == ')') { var markedText = richText.Substring(rangeStarted, i - rangeStarted); var pipeOffset = markedText.IndexOf('|'); if (pipeOffset >= 0) { result.Append(markedText.Substring(pipeOffset + 1)); } else { result.Append(markedText); } } closer = null; } continue; } else if (ch == '$') { if ((next == '(') || (next == '[')) { closer = (next == '(') ? ')' : ']'; i++; rangeStarted = i + 1; continue; } } else { result.Append(ch); } } return(result.ToString()); }
public static DenseList <RichRule> ParseRules(AbstractString text, ref DenseList <RichParseError> parseErrors) { var result = new DenseList <RichRule>(); int keyStart = 0; int?keyEnd = null, valueStart = null; for (int i = 0, l = text.Length; i <= l; i++) { var ch = (i == l) ? '\0' : text[i]; switch (ch) { case ':': if (valueStart.HasValue) { parseErrors.Add(new RichParseError { Text = text, Offset = i, Message = "Unexpected :" }); } else { keyEnd = i; valueStart = i + 1; } break; case '\0': case ';': if (!valueStart.HasValue) { if (ch != '\0') { parseErrors.Add(new RichParseError { Text = text, Offset = i, Message = "Unexpected ;" }); } } else { result.Add(new RichRule { Key = new AbstractString(in text, keyStart, keyEnd.Value - keyStart), Value = new AbstractString(in text, valueStart.Value, i - valueStart.Value), }); keyStart = i + 1; keyEnd = valueStart = null; } break; }
public static string ToPlainText(AbstractString richText) { if (richText.IsNull) { return(null); } for (int i = 0, l = richText.Length; i < l - 1; i++) { var c = richText[i]; if ((c == '$') && (richText[i + 1] == '[') || (richText[i + 1] == '(')) { return(ToPlainText_Slow(richText, i)); } } return(richText.ToString()); }
public DynamicStringLayout(IGlyphSource font = null, string text = "") { _GlyphSource = font; _Text = text; }
public DynamicStringLayout(SpriteFont font, string text = "") { _GlyphSource = new SpriteFontGlyphSource(font); _Text = text; }
public StringLayout WordWrap(AbstractString text, float wrapAtX, ArraySegment <BitmapDrawCall>?buffer = null, float wrapIndentation = 0f, bool characterWrap = true) { int? thisWordStartIndex = null; int indexOfFirstCharInLine = 0; float?previousCharacterX = null; float thisWordWidth = 0; var newSize = new Vector2(); ArraySegment <BitmapDrawCall> _buffer; if (buffer.HasValue) { _buffer = buffer.Value; } else { _buffer = new ArraySegment <BitmapDrawCall>(new BitmapDrawCall[Count]); } Array.Copy(this.DrawCalls.Array, this.DrawCalls.Offset, _buffer.Array, _buffer.Offset, Count); for (var i = 0; i < Count; i++) { var ch = text[i]; var dc = _buffer.Array[_buffer.Offset + i]; // Detect line break if (previousCharacterX.HasValue && (dc.Position.X <= previousCharacterX.Value)) { indexOfFirstCharInLine = i; } var isWordChar = Char.IsLetterOrDigit(ch) || (ch == '\''); // Start out using texture width (not entirely accurate) var thisCharWidth = dc.TextureRegion.Size.X * dc.Texture.Width; // Then if we can, use the gap between this char and next char if (i < (Count - 1)) { var nextDrawCall = _buffer.Array[_buffer.Offset + i + 1]; // Make sure the next draw call wasn't wrapped if (nextDrawCall.Position.X > dc.Position.X) { thisCharWidth = nextDrawCall.Position.X - dc.Position.X; } } thisWordWidth += thisCharWidth; var needWrap = (dc.Position.X >= wrapAtX) && !(ch == 10 || ch == 13 || ch == ' '); if (needWrap) { int fromOffset = i; if (thisWordStartIndex.HasValue && (thisWordWidth <= (wrapAtX - wrapIndentation))) { // We have a current word and it's not too wide to fit on its own line. fromOffset = thisWordStartIndex.Value; } else if (characterWrap) { // We can character wrap, so continue with a fromOffset of i } else { // Character wrap disallowed, so don't wrap. The user will probably scale the output layout. needWrap = false; } // We'd be wrapping the whole line, which is pointless. if (indexOfFirstCharInLine == fromOffset) { needWrap = false; } if (needWrap) { float xDelta = Position.X - _buffer.Array[_buffer.Offset + fromOffset].Position.X + wrapIndentation; indexOfFirstCharInLine = fromOffset; float firstX = _buffer.Array[_buffer.Offset + fromOffset].Position.X; bool didBreakLine = false; for (var j = fromOffset; j < Count; j++) { var dc2 = _buffer.Array[_buffer.Offset + j]; if ((dc2.Position.X <= firstX) && (j > fromOffset)) { didBreakLine = true; } if (!didBreakLine) { dc2.Position.X += xDelta; } dc2.Position.Y += LineHeight; _buffer.Array[_buffer.Offset + j] = dc2; } } } if (!isWordChar) { if (thisWordStartIndex.HasValue) { thisWordStartIndex = null; } } else { if (!thisWordStartIndex.HasValue) { thisWordStartIndex = i; thisWordWidth = 0f; } } previousCharacterX = dc.Position.X; } for (var i = 0; i < Count; i++) { var dc = _buffer.Array[_buffer.Offset + i]; newSize.X = Math.Max( dc.Position.X + (dc.Texture.Width * dc.TextureRegion.Size.X) - Position.X, newSize.X ); newSize.Y = Math.Max( dc.Position.Y + (dc.Texture.Height * dc.TextureRegion.Size.Y) - Position.Y, newSize.Y ); } var segment = new ArraySegment <BitmapDrawCall>(_buffer.Array, _buffer.Offset, Count); if (segment.Count > text.Length) { throw new InvalidDataException(); } return(new StringLayout( Position, newSize, LineHeight, // FIXME FirstCharacterBounds, LastCharacterBounds, segment )); }
public DynamicStringLayout(SpriteFont font, string text = "") { _Font = font; _Text = text; }
public ArraySegment <BitmapDrawCall> AppendText( IGlyphSource font, AbstractString text, Dictionary <char, KerningAdjustment> kerningAdjustments = null ) { if (!IsInitialized) { throw new InvalidOperationException("Call Initialize first"); } if (font == null) { throw new ArgumentNullException("font"); } if (text.IsNull) { throw new ArgumentNullException("text"); } EnsureBufferCapacity(bufferWritePosition + text.Length); if (kerningAdjustments == null) { kerningAdjustments = StringLayout.GetDefaultKerningAdjustments(font); } var effectiveScale = scale / font.DPIScaleFactor; var drawCall = default(BitmapDrawCall); drawCall.MultiplyColor = color.GetValueOrDefault(Color.White); drawCall.ScaleF = effectiveScale; drawCall.SortKey = sortKey; float x = 0; float?defaultLineSpacing = null; for (int i = 0, l = text.Length; i < l; i++) { var ch = text[i]; bool isWhiteSpace = char.IsWhiteSpace(ch), forcedWrap = false, lineBreak = false, deadGlyph = false; Glyph glyph; KerningAdjustment kerningAdjustment; if (ch == '\r') { if (((i + 1) < l) && (text[i + 1] == '\n')) { i += 1; } lineBreak = true; } else if (ch == '\n') { lineBreak = true; } if (isWhiteSpace) { wordStartWritePosition = -1; wordWrapSuppressed = false; } else { if (wordStartWritePosition < 0) { wordStartWritePosition = bufferWritePosition; wordStartOffset = characterOffset; } } deadGlyph = !font.GetGlyph(ch, out glyph); float effectiveLineSpacing = glyph.LineSpacing; if (deadGlyph) { if (currentLineSpacing.HasValue) { effectiveLineSpacing = currentLineSpacing.Value; } else if (defaultLineSpacing.HasValue) { effectiveLineSpacing = defaultLineSpacing.Value; } else { Glyph space; if (font.GetGlyph(' ', out space)) { defaultLineSpacing = effectiveLineSpacing = space.LineSpacing; } } } if ((kerningAdjustments != null) && kerningAdjustments.TryGetValue(ch, out kerningAdjustment)) { glyph.LeftSideBearing += kerningAdjustment.LeftSideBearing; glyph.Width += kerningAdjustment.Width; glyph.RightSideBearing += kerningAdjustment.RightSideBearing; } x = characterOffset.X + glyph.LeftSideBearing + glyph.RightSideBearing + glyph.Width + glyph.CharacterSpacing; if ((x * effectiveScale) >= lineBreakAtX) { if ( !deadGlyph && (colIndex > 0) && !isWhiteSpace ) { forcedWrap = true; } } if (forcedWrap) { var currentWordSize = x - wordStartOffset.X; if (wordWrap && !wordWrapSuppressed && (currentWordSize * effectiveScale <= lineBreakAtX)) { WrapWord(buffer, wordStartOffset, wordStartWritePosition, bufferWritePosition - 1, effectiveScale, effectiveLineSpacing); wordWrapSuppressed = true; lineBreak = true; } else if (characterWrap) { characterOffset.X = xOffsetOfWrappedLine; characterOffset.Y += effectiveLineSpacing; maxX = Math.Max(maxX, currentLineMaxX * effectiveScale); wordStartWritePosition = bufferWritePosition; wordStartOffset = characterOffset; lineBreak = true; } } if (lineBreak) { if (!forcedWrap) { characterOffset.X = xOffsetOfNewLine; characterOffset.Y += effectiveLineSpacing; maxX = Math.Max(maxX, currentLineMaxX * effectiveScale); } initialLineXOffset = characterOffset.X; currentLineMaxX = 0; currentLineWhitespaceMaxX = 0; currentLineWhitespaceMaxXLeft = 0; rowIndex += 1; colIndex = 0; } if (deadGlyph) { characterSkipCount--; if (characterLimit.HasValue) { characterLimit--; } continue; } // HACK: Recompute after wrapping x = characterOffset.X + glyph.LeftSideBearing + glyph.RightSideBearing + glyph.Width + glyph.CharacterSpacing; characterOffset.X += glyph.CharacterSpacing; lastCharacterBounds = Bounds.FromPositionAndSize( characterOffset * effectiveScale, new Vector2( glyph.LeftSideBearing + glyph.Width + glyph.RightSideBearing, glyph.LineSpacing ) * effectiveScale ); if ((rowIndex == 0) && (colIndex == 0)) { firstCharacterBounds = lastCharacterBounds; } characterOffset.X += glyph.LeftSideBearing; if (colIndex == 0) { characterOffset.X = Math.Max(characterOffset.X, 0); } if (characterSkipCount <= 0) { if (characterLimit.HasValue && characterLimit.Value <= 0) { break; } var glyphPosition = new Vector2( actualPosition.X + (glyph.XOffset + characterOffset.X) * effectiveScale, actualPosition.Y + (glyph.YOffset + characterOffset.Y) * effectiveScale ); if (!isWhiteSpace) { if (bufferWritePosition >= buffer.Count) { EnsureBufferCapacity(bufferWritePosition); } drawCall.Texture = glyph.Texture; drawCall.TextureRegion = glyph.Texture.BoundsFromRectangle(ref glyph.BoundsInTexture); Snap(glyphPosition, out drawCall.Position); // HACK so that the alignment pass can detect rows. We strip this later. if (alignment != HorizontalAlignment.Left) { drawCall.SortKey.Order = rowIndex; } buffer.Array[buffer.Offset + bufferWritePosition] = drawCall; currentLineMaxX = Math.Max(currentLineMaxX, x); maxY = Math.Max(maxY, (characterOffset.Y + effectiveLineSpacing) * effectiveScale); bufferWritePosition += 1; drawCallsWritten += 1; } else { currentLineWhitespaceMaxXLeft = Math.Max(currentLineWhitespaceMaxXLeft, characterOffset.X); currentLineWhitespaceMaxX = Math.Max(currentLineWhitespaceMaxX, x); } characterLimit--; } else { characterSkipCount--; } characterOffset.X += (glyph.Width + glyph.RightSideBearing); currentLineSpacing = glyph.LineSpacing; maxLineSpacing = Math.Max(maxLineSpacing, effectiveLineSpacing); colIndex += 1; } var segment = new ArraySegment <BitmapDrawCall>( buffer.Array, buffer.Offset, drawCallsWritten ); maxX = Math.Max(maxX, currentLineMaxX * effectiveScale); return(segment); }