Exemplo n.º 1
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.Icon"/>.
        /// </summary>
        private Boolean ProcessIconToken(TextLayoutCommandStream output,
                                         ref TextParserToken token, ref LayoutState state, ref TextLayoutSettings settings, ref Int32 index)
        {
            var icon      = default(TextIconInfo);
            var iconIndex = RegisterIconWithCommandStream(output, token.Text, out icon);
            var iconSize  = MeasureToken(null, token.TokenType, token.Text);

            if (state.PositionX + iconSize.Width > (settings.Width ?? Int32.MaxValue))
            {
                state.AdvanceLayoutToNextLine(output, ref settings);
            }

            if (state.PositionY + iconSize.Height > (settings.Height ?? Int32.MaxValue))
            {
                return(false);
            }

            output.WriteIcon(new TextLayoutIconCommand(iconIndex, state.PositionX, state.PositionY, (Int16)iconSize.Width, (Int16)iconSize.Height));
            state.AdvanceLineToNextCommand(iconSize.Width, iconSize.Height, 1, 1);
            index++;

            return(true);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Accumulates sequential text tokens into a single text command.
        /// </summary>
        private Boolean AccumulateText(TextParserTokenStream input, TextLayoutCommandStream output, SpriteFontFace font, ref Int32 index, ref LayoutState state, ref TextLayoutSettings settings)
        {
            var hyphenate = (settings.Options & TextLayoutOptions.Hyphenate) == TextLayoutOptions.Hyphenate;

            var availableWidth = (settings.Width ?? Int32.MaxValue);
            var x      = state.PositionX;
            var y      = state.PositionY;
            var width  = 0;
            var height = 0;

            var accumulatedStart  = input[index].Text.Start + (state.ParserTokenOffset ?? 0);
            var accumulatedLength = 0;
            var accumulatedCount  = 0;

            var lineOverflow      = false;
            var lineBreakPossible = false;

            var tokenText            = default(StringSegment);
            var tokenNext            = default(TextParserToken?);
            var tokenSize            = default(Size2);
            var tokenKerning         = 0;
            var tokenIsBreakingSpace = false;

            while (index < input.Count)
            {
                var token = input[index];
                if (token.TokenType != TextParserTokenType.Text || token.IsNewLine)
                {
                    break;
                }

                if (!IsSegmentForCurrentSource(token.Text))
                {
                    if (accumulatedCount > 0)
                    {
                        break;
                    }

                    EmitChangeSourceIfNecessary(input, output, ref token);
                }

                tokenText    = token.Text.Substring(state.ParserTokenOffset ?? 0);
                tokenNext    = GetNextTextToken(input, index);
                tokenSize    = MeasureToken(font, token.TokenType, tokenText, tokenNext);
                tokenKerning = font.Kerning.Get(tokenText[tokenText.Length - 1], ' ');

                // NOTE: We assume in a couple of places that tokens sizes don't exceed Int16.MaxValue, so try to
                // avoid accumulating tokens larger than that just in case somebody is doing something dumb
                if (width + tokenSize.Width > Int16.MaxValue)
                {
                    break;
                }

                if (token.IsWhiteSpace && (state.LineBreakCommand == null || !token.IsNonBreakingSpace))
                {
                    lineBreakPossible      = true;
                    state.LineBreakCommand = output.Count;
                    state.LineBreakOffset  = accumulatedLength + token.Text.Length - 1;
                    tokenIsBreakingSpace   = true;
                }
                else
                {
                    tokenIsBreakingSpace = false;
                }

                // For most tokens we need to bail out here if there's a line overflow, but
                // if it's a breaking space we need to be sure that it's part of the command stream
                // so that we can go back and replace it in the line break phase!
                var overflowsLine = state.PositionX + tokenSize.Width - tokenKerning > availableWidth;
                if (overflowsLine && !tokenIsBreakingSpace)
                {
                    lineOverflow = true;
                    break;
                }

                if (tokenText.Start != accumulatedStart + accumulatedLength)
                {
                    break;
                }

                width             = width + tokenSize.Width;
                height            = Math.Max(height, tokenSize.Height);
                accumulatedLength = accumulatedLength + tokenText.Length;
                accumulatedCount++;

                state.AdvanceLineToNextCommand(tokenSize.Width, tokenSize.Height, 1, tokenText.Length);
                state.ParserTokenOffset = 0;
                state.LineLengthInCommands--;

                index++;

                // At this point, we need to bail out even for breaking spaces.
                if (overflowsLine && tokenIsBreakingSpace)
                {
                    lineOverflow = true;
                    break;
                }
            }

            if (lineBreakPossible)
            {
                var preLineBreakTextStart  = accumulatedStart;
                var preLineBreakTextLength = state.LineBreakOffset.Value;
                var preLineBreakText       = CreateStringSegmentFromCurrentSource(preLineBreakTextStart, preLineBreakTextLength);
                var preLineBreakSize       = (preLineBreakText.Length == 0) ? Size2.Zero :
                                             MeasureToken(font, TextParserTokenType.Text, preLineBreakText);
                state.BrokenTextSizeBeforeBreak = preLineBreakSize;

                var postLineBreakStart  = accumulatedStart + (state.LineBreakOffset.Value + 1);
                var postLineBreakLength = accumulatedLength - (state.LineBreakOffset.Value + 1);
                var postLineBreakText   = CreateStringSegmentFromCurrentSource(postLineBreakStart, postLineBreakLength);
                var postLineBreakSize   = (postLineBreakText.Length == 0) ? Size2.Zero :
                                          MeasureToken(font, TextParserTokenType.Text, postLineBreakText, GetNextTextToken(input, index - 1));
                state.BrokenTextSizeAfterBreak = postLineBreakSize;
            }

            var bounds = new Rectangle(x, y, width, height);

            EmitTextIfNecessary(output, accumulatedStart, accumulatedLength, ref bounds, ref state);

            if (lineOverflow && !state.ReplaceLastBreakingSpaceWithLineBreak(output, ref settings))
            {
                var overflowingToken = input[index];
                if (overflowingToken.IsWhiteSpace && !overflowingToken.IsNonBreakingSpace)
                {
                    output.WriteLineBreak(new TextLayoutLineBreakCommand(1));
                    state.AdvanceLineToNextCommand(0, 0, 1, 1, isLineBreak: true);
                    state.AdvanceLayoutToNextLine(output, ref settings);

                    if (overflowingToken.Text.Length > 1)
                    {
                        state.ParserTokenOffset = 1;
                    }
                    else
                    {
                        index++;
                    }
                    return(true);
                }

                if (!GetFittedSubstring(font, availableWidth, ref tokenText, ref tokenSize, ref state, hyphenate) && state.LineWidth == 0)
                {
                    return(false);
                }

                var overflowingTokenBounds = (tokenText.Length == 0) ? Rectangle.Empty :
                                             new Rectangle(state.PositionX, state.PositionY, tokenSize.Width, tokenSize.Height);

                var overflowingTextEmitted = EmitTextIfNecessary(output, tokenText.Start, tokenText.Length, ref overflowingTokenBounds, ref state);
                if (overflowingTextEmitted)
                {
                    state.AdvanceLineToNextCommand(tokenSize.Width, tokenSize.Height, 0, tokenText.Length);
                    if (hyphenate)
                    {
                        output.WriteHyphen();
                        state.AdvanceLineToNextCommand(0, 0, 1, 0);
                    }
                }

                state.ParserTokenOffset = (state.ParserTokenOffset ?? 0) + tokenText.Length;
                state.AdvanceLayoutToNextLine(output, ref settings);
            }

            return(true);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Accumulates sequential text tokens into a single text command.
        /// </summary>
        private Boolean AccumulateText(TextParserTokenStream input, TextLayoutCommandStream output, SpriteFontFace font, ref Int32 index, ref LayoutState state, ref TextLayoutSettings settings)
        {
            var hyphenate = (settings.Options & TextLayoutOptions.Hyphenate) == TextLayoutOptions.Hyphenate;

            var availableWidth = (settings.Width ?? Int32.MaxValue);
            var x = state.PositionX;
            var y = state.PositionY;
            var width = 0;
            var height = 0;

            var accumulatedStart = input[index].Text.Start + (state.ParserTokenOffset ?? 0);
            var accumulatedLength = 0;
            var accumulatedCount = 0;

            var lineOverflow = false;
            var lineBreakPossible = false;

            var tokenText = default(StringSegment);
            var tokenNext = default(TextParserToken?);
            var tokenSize = default(Size2);

            while (index < input.Count)
            {
                var token = input[index];
                if (token.TokenType != TextParserTokenType.Text || token.IsNewLine)
                    break;

                if (!IsSegmentForCurrentSource(token.Text))
                {
                    if (accumulatedCount > 0)
                        break;

                    EmitChangeSourceIfNecessary(input, output, ref token);
                }

                tokenText = token.Text.Substring(state.ParserTokenOffset ?? 0);
                tokenNext = GetNextTextToken(input, index);
                tokenSize = MeasureToken(font, token.TokenType, tokenText, tokenNext);

                // NOTE: We assume in a couple of places that tokens sizes don't exceed Int16.MaxValue, so try to
                // avoid accumulating tokens larger than that just in case somebody is doing something dumb
                if (width + tokenSize.Width > Int16.MaxValue)
                    break;

                var overflowsLine = state.PositionX + tokenSize.Width > availableWidth;
                if (overflowsLine)
                {
                    lineOverflow = true;
                    break;
                }

                if (tokenText.Start != accumulatedStart + accumulatedLength)
                    break;

                if (token.IsWhiteSpace && (state.LineBreakCommand == null || !token.IsNonBreakingSpace))
                {
                    lineBreakPossible = true;
                    state.LineBreakCommand = output.Count;
                    state.LineBreakOffset = accumulatedLength + token.Text.Length - 1;
                }

                width = width + tokenSize.Width;
                height = Math.Max(height, tokenSize.Height);
                accumulatedLength = accumulatedLength + tokenText.Length;
                accumulatedCount++;

                state.AdvanceLineToNextCommand(tokenSize.Width, tokenSize.Height, 1, tokenText.Length);
                state.ParserTokenOffset = 0;
                state.LineLengthInCommands--;

                index++;
            }

            if (lineBreakPossible)
            {
                var preLineBreakTextStart = accumulatedStart;
                var preLineBreakTextLength = state.LineBreakOffset.Value;
                var preLineBreakText = CreateStringSegmentFromCurrentSource(preLineBreakTextStart, preLineBreakTextLength);
                var preLineBreakSize = (preLineBreakText.Length == 0) ? Size2.Zero :
                    MeasureToken(font, TextParserTokenType.Text, preLineBreakText);
                state.BrokenTextSizeBeforeBreak = preLineBreakSize;

                var postLineBreakStart = accumulatedStart + (state.LineBreakOffset.Value + 1);
                var postLineBreakLength = accumulatedLength - (state.LineBreakOffset.Value + 1);
                var postLineBreakText = CreateStringSegmentFromCurrentSource(postLineBreakStart, postLineBreakLength);
                var postLineBreakSize = (postLineBreakText.Length == 0) ? Size2.Zero :
                    MeasureToken(font, TextParserTokenType.Text, postLineBreakText, GetNextTextToken(input, index - 1));
                state.BrokenTextSizeAfterBreak = postLineBreakSize;
            }

            var bounds = new Rectangle(x, y, width, height);
            EmitTextIfNecessary(output, accumulatedStart, accumulatedLength, ref bounds, ref state);

            if (lineOverflow && !state.ReplaceLastBreakingSpaceWithLineBreak(output, ref settings))
            {
                var overflowingToken = input[index];
                if (overflowingToken.IsWhiteSpace && !overflowingToken.IsNonBreakingSpace)
                {
                    output.WriteLineBreak(new TextLayoutLineBreakCommand(1));
                    state.AdvanceLineToNextCommand(0, 0, 1, 1, isLineBreak: true);
                    state.AdvanceLayoutToNextLine(output, ref settings);

                    if (overflowingToken.Text.Length > 1)
                    {
                        state.ParserTokenOffset = 1;
                    }
                    else
                    {
                        index++;
                    }
                    return true;
                }

                if (!GetFittedSubstring(font, availableWidth, ref tokenText, ref tokenSize, ref state, hyphenate) && state.LineWidth == 0)
                    return false;

                var overflowingTokenBounds = (tokenText.Length == 0) ? Rectangle.Empty :
                    new Rectangle(state.PositionX, state.PositionY, tokenSize.Width, tokenSize.Height);

                var overflowingTextEmitted = EmitTextIfNecessary(output, tokenText.Start, tokenText.Length, ref overflowingTokenBounds, ref state);
                if (overflowingTextEmitted)
                {
                    state.AdvanceLineToNextCommand(tokenSize.Width, tokenSize.Height, 0, tokenText.Length);
                    if (hyphenate)
                    {
                        output.WriteHyphen();
                        state.AdvanceLineToNextCommand(0, 0, 1, 0);
                    }
                }

                state.ParserTokenOffset = (state.ParserTokenOffset ?? 0) + tokenText.Length;
                state.AdvanceLayoutToNextLine(output, ref settings);
            }

            return true;
        }
Exemplo n.º 4
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.Icon"/>.
        /// </summary>
        private Boolean ProcessIconToken(TextLayoutCommandStream output,
            ref TextParserToken token, ref LayoutState state, ref TextLayoutSettings settings, ref Int32 index)
        {
            var icon = default(TextIconInfo);
            var iconIndex = RegisterIconWithCommandStream(output, token.Text, out icon);
            var iconSize = MeasureToken(null, token.TokenType, token.Text);

            if (state.PositionX + iconSize.Width > (settings.Width ?? Int32.MaxValue))
                state.AdvanceLayoutToNextLine(output, ref settings);

            if (state.PositionY + iconSize.Height > (settings.Height ?? Int32.MaxValue))
                return false;

            output.WriteIcon(new TextLayoutIconCommand(iconIndex, state.PositionX, state.PositionY, (Int16)iconSize.Width, (Int16)iconSize.Height));
            state.AdvanceLineToNextCommand(iconSize.Width, iconSize.Height, 1, 1);
            index++;

            return true;
        }