Example #1
0
        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);
        }