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); }