Exemple #1
0
        /// <summary>
        /// Adds a <see cref="TextLayoutCommandType.ChangeSourceString"/> or <see cref="TextLayoutCommandType.ChangeSourceStringBuilder"/> command to the output
        /// stream if it is necessary to do so for the specified parser token.
        /// </summary>
        private Boolean EmitChangeSourceIfNecessary(TextParserTokenStream input, TextLayoutCommandStream output, ref TextParserToken token)
        {
            if (!IsSegmentForCurrentSource(token.Text))
            {
                var isFirstSource = (sourceString == null && sourceStringBuilder == null);

                sourceString        = token.Text.SourceString;
                sourceStringBuilder = token.Text.SourceStringBuilder;

                // NOTE: To save memory, we can elide the first change source command if it's just going to change to the input source.
                if (!isFirstSource || input.SourceText.SourceString != sourceString || input.SourceText.SourceStringBuilder != sourceStringBuilder)
                {
                    if (sourceString != null)
                    {
                        var sourceIndex = output.RegisterSourceString(sourceString);
                        output.WriteChangeSourceString(new TextLayoutSourceStringCommand(sourceIndex));
                    }
                    else
                    {
                        var sourceIndex = output.RegisterSourceStringBuilder(sourceStringBuilder);
                        output.WriteChangeSourceStringBuilder(new TextLayoutSourceStringBuilderCommand(sourceIndex));
                    }
                    return(true);
                }
            }
            return(false);
        }
        /// <summary>
        /// Lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringBuilder"/> to parse.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        public void Parse(StringBuilder input, TextParserTokenStream output, TextParserOptions options = TextParserOptions.None)
        {
            Contract.Require(input, "input");
            Contract.Require(output, "output");

            output.Clear();
            Parse(new StringSource(input), output, 0, input.Length, options);
        }
        /// <summary>
        /// Incrementally lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="String"/> to parse.</param>
        /// <param name="start">The index of the first character that was changed.</param>
        /// <param name="count">The number of characters that were changed.</param>
        /// <param name="result">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        /// <returns>An <see cref="IncrementalResult"/> structure that represents the result of the operation.</returns>
        /// <remarks>Incremental parsing provides a performance benefit when relatively small changes are being made
        /// to a large source text. Only tokens which are potentially influenced by changes within the specified substring
        /// of the source text are re-parsed by this operation.</remarks>
        public IncrementalResult ParseIncremental(String input, Int32 start, Int32 count, TextParserTokenStream result, TextParserOptions options = TextParserOptions.None)
        {
            Contract.Require(input, "input");
            Contract.Require(result, "output");
            Contract.EnsureRange(start >= 0, "start");

            return ParseIncremental(new StringSource(input), start, count, result, options);
        }
Exemple #4
0
        /// <summary>
        /// Lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringBuilder"/> to parse.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        public void Parse(StringBuilder input, TextParserTokenStream output, TextParserOptions options = TextParserOptions.None)
        {
            Contract.Require(input, nameof(input));
            Contract.Require(output, nameof(output));

            output.Clear();
            Parse(new StringSource(input), output, 0, input.Length, options);
        }
        public void TextParser_ParseIncremental_CorrectlyHandlesReplacedText()
        {
            var textParser = new TextParser();
            var textParserResult = new TextParserTokenStream();
            textParser.Parse(@"lorem ipsum |b|dolor|b| sit amet", textParserResult);

            TheResultingCollection(textParserResult.Select(x => x.Text)).ShouldBeExactly("lorem", " ", "ipsum", " ", null, "dolor", null, " ", "sit", " ", "amet");

            textParser.ParseIncremental(@"lorem ipsum |b|foo bar baz qux|b| sit amet", 15, 21, textParserResult);

            TheResultingCollection(textParserResult.Select(x => x.Text)).ShouldBeExactly("lorem", " ", "ipsum", " ", null, "foo", " ", "bar", " ", "baz", " ", "qux", null, " ", "sit", " ", "amet");
        }
        public void TextParser_ParseIncremental_CorrectlyHandlesAddedText()
        {
            var textParser = new TextParser();
            var textParserResult = new TextParserTokenStream();
            textParser.Parse(@"lorem ipsum |b|dolor|b| sit amet", textParserResult);

            TheResultingCollection(textParserResult.Select(x => x.Text)).ShouldBeExactly("lorem", " ", "ipsum", " ", null, "dolor", null, " ", "sit", " ", "amet");

            textParser.ParseIncremental(@"lorem ipsum hello world!! |b|dolor|b| sit amet", 12, 14, textParserResult);

            TheResultingCollection(textParserResult.Select(x => x.Text)).ShouldBeExactly("lorem", " ", "ipsum", " ", "hello", " ", "world!!", " ", null, "dolor", null, " ", "sit", " ", "amet");
        }
Exemple #7
0
        /// <summary>
        /// Gets the next text token after the specified index, if one exists and there are no intervening
        /// visible tokens (excluding commands).
        /// </summary>
        private TextParserToken?GetNextTextToken(TextParserTokenStream input, Int32 index)
        {
            for (int i = index + 1; i < input.Count; i++)
            {
                var token = input[i];
                if (token.TokenType == TextParserTokenType.Text)
                {
                    return(token);
                }

                if (token.TokenType == TextParserTokenType.Icon)
                {
                    break;
                }
            }
            return(null);
        }
Exemple #8
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.Text"/>.
 /// </summary>
 private Boolean ProcessTextToken(TextParserTokenStream input, TextLayoutCommandStream output, SpriteFontFace currentFontFace,
                                  ref TextParserToken token, ref LayoutState state, ref TextLayoutSettings settings, ref Int32 index)
 {
     if (token.IsNewLine)
     {
         state.AdvanceLayoutToNextLineWithBreak(output, token.SourceLength, ref settings);
         index++;
     }
     else
     {
         if (!AccumulateText(input, output, currentFontFace, ref index, ref state, ref settings))
         {
             return(false);
         }
     }
     return(true);
 }
Exemple #9
0
        /// <summary>
        /// Lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringSource"/> to parse.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="index">The index at which to begin parsing the input string.</param>
        /// <param name="count">the number of characters to parse.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        private void Parse(StringSource input, TextParserTokenStream output, Int32 index, Int32 count, TextParserOptions options = TextParserOptions.None)
        {
            var bound = index + count;

            while (index < bound)
            {
                if (IsStartOfNewline(input, index))
                {
                    output.Add(ConsumeNewlineToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfNonBreakingSpace(input, index))
                {
                    output.Add(ConsumeNonBreakingSpaceToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfBreakingSpace(input, index))
                {
                    output.Add(ConsumeBreakingSpaceToken(input, options, ref index));
                    continue;
                }
                if (IsEscapedPipe(input, index, options))
                {
                    output.Add(ConsumeEscapedPipeToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfCommand(input, index))
                {
                    output.Add(ConsumeCommandToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfWord(input, index))
                {
                    output.Add(ConsumeWordToken(input, options, ref index));
                    continue;
                }
                index++;
            }

            output.SourceText    = input.CreateStringSegment();
            output.ParserOptions = options;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ScrollingTextBlock"/> class.
        /// </summary>
        /// <param name="textRenderer">The text renderer which will be used to draw the scrolling text block's text.</param>
        /// <param name="font">The sprite font which will be used to draw the scrolling text block's text.</param>
        /// <param name="text">The string which is displayed by the scrolling text block.</param>
        /// <param name="width">The width of the scrolling text block's layout area in pixels,
        /// or <see langword="null"/> to give the layout area an unconstrained width.</param>
        /// <param name="height">The height of the scrolling text block's layout area in pixels,
        /// or <see langword="null"/> to give the layout area an unconstrained height.</param>
        public ScrollingTextBlock(TextRenderer textRenderer, SpriteFont font, String text, Int32? width, Int32? height)
        {
            Contract.Require(textRenderer, nameof(textRenderer));
            Contract.Require(font, nameof(font));
            Contract.Require(text, nameof(text));

            this.TextRenderer = textRenderer;
            this.Font = font;

            this.Width = width;
            this.Height = height;

            this.textParserTokens = new TextParserTokenStream();
            this.textLayoutCommands = new TextLayoutCommandStream();

            this.Text = text;
            this.TextRenderer.Parse(text, textParserTokens);
            this.TextRenderer.CalculateLayout(Text, textLayoutCommands,
                new TextLayoutSettings(Font, width, height, TextFlags.Standard));

            Reset();
        }
Exemple #11
0
        /// <summary>
        /// Finds the index of the first and last tokens which are potentially affected by changes in the specified substring of the source text.
        /// </summary>
        private void FindTokensInfluencedBySubstring(TextParserTokenStream result, Int32 start, Int32 count, out Int32 ix1, out Int32 ix2)
        {
            var position = 0;
            var end      = start + count;

            var ix1Found = false;
            var ix2Found = false;

            ix1 = 0;
            ix2 = result.Count - 1;

            for (int i = 0; i < result.Count; i++)
            {
                var token      = result[i];
                var tokenStart = token.SourceOffset;
                var tokenEnd   = tokenStart + token.SourceLength;

                if (!ix1Found && (start >= tokenStart && start <= tokenEnd))
                {
                    ix1      = i;
                    ix1Found = true;
                }

                if (!ix2Found && (end >= tokenStart && end < tokenEnd))
                {
                    ix2      = i;
                    ix2Found = true;
                }

                if (ix1Found && ix2Found)
                {
                    break;
                }

                position += token.SourceLength;
            }
        }
Exemple #12
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);
        }
Exemple #13
0
        /// <summary>
        /// Calculates a layout for the specified text.
        /// </summary>
        /// <param name="input">The parsed text which will be laid out according to the specified settings.</param>
        /// <param name="output">The layout command stream which will be populated with commands by this operation.</param>
        /// <param name="settings">A <see cref="TextLayoutSettings"/> structure which contains the settings for this operation.</param>
        public void CalculateLayout(TextParserTokenStream input, TextLayoutCommandStream output, TextLayoutSettings settings)
        {
            Contract.Require(input, nameof(input));
            Contract.Require(output, nameof(output));

            if (settings.Font == null)
            {
                throw new ArgumentException(UltravioletStrings.InvalidLayoutSettings);
            }

            var state = new LayoutState()
            {
                LineInfoCommandIndex = 1
            };

            output.Clear();
            output.SourceText    = input.SourceText;
            output.ParserOptions = input.ParserOptions;

            var acquiredPointers = !output.HasAcquiredPointers;

            if (acquiredPointers)
            {
                output.AcquirePointers();
            }

            output.WriteBlockInfo();
            output.WriteLineInfo();

            var bold   = (settings.Style == SpriteFontStyle.Bold || settings.Style == SpriteFontStyle.BoldItalic);
            var italic = (settings.Style == SpriteFontStyle.Italic || settings.Style == SpriteFontStyle.BoldItalic);

            if (settings.InitialLayoutStyle != null)
            {
                PrepareInitialStyle(output, ref bold, ref italic, ref settings);
            }

            var currentFont     = settings.Font;
            var currentFontFace = settings.Font.GetFace(SpriteFontStyle.Regular);

            var index      = 0;
            var processing = true;

            while (index < input.Count && processing)
            {
                if (state.PositionY >= (settings.Height ?? Int32.MaxValue))
                {
                    break;
                }

                var token = input[index];

                currentFontFace = default(SpriteFontFace);
                currentFont     = GetCurrentFont(ref settings, bold, italic, out currentFontFace);

                switch (token.TokenType)
                {
                case TextParserTokenType.Text:
                    processing = ProcessTextToken(input, output, currentFontFace, ref token, ref state, ref settings, ref index);
                    break;

                case TextParserTokenType.Icon:
                    processing = ProcessIconToken(output, ref token, ref state, ref settings, ref index);
                    break;

                case TextParserTokenType.ToggleBold:
                    ProcessToggleBoldToken(output, ref bold, ref state, ref index);
                    break;

                case TextParserTokenType.ToggleItalic:
                    ProcessToggleItalicToken(output, ref italic, ref state, ref index);
                    break;

                case TextParserTokenType.PushFont:
                    ProcessPushFontToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PushColor:
                    ProcessPushColorToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PushStyle:
                    ProcessPushStyleToken(output, ref bold, ref italic, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PushGlyphShader:
                    ProcessPushGlyphShaderToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PushLink:
                    ProcessPushLinkToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PopFont:
                    ProcessPopFontToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PopColor:
                    ProcessPopColorToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PopStyle:
                    ProcessPopStyleToken(output, ref bold, ref italic, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PopGlyphShader:
                    ProcessPopGlyphShaderToken(output, ref token, ref state, ref index);
                    break;

                case TextParserTokenType.PopLink:
                    ProcessPopLinkToken(output, ref token, ref state, ref index);
                    break;

                default:
                    if (token.TokenType >= TextParserTokenType.Custom)
                    {
                        ProcessCustomCommandToken(output, ref token, ref state, ref index);
                        break;
                    }
                    else
                    {
                        throw new InvalidOperationException(UltravioletStrings.UnrecognizedLayoutCommand.Format(token.TokenType));
                    }
                }
            }

            state.FinalizeLayout(output, ref settings);

            if (acquiredPointers)
            {
                output.ReleasePointers();
            }

            ClearLayoutStacks();
        }
Exemple #14
0
        /// <summary>
        /// Incrementally lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringSource"/> to parse.</param>
        /// <param name="start">The index of the first character that was changed.</param>
        /// <param name="count">The number of characters that were changed.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        /// <returns>An <see cref="IncrementalResult"/> structure that represents the result of the operation.</returns>
        /// <remarks>Incremental parsing provides a performance benefit when relatively small changes are being made
        /// to a large source text. Only tokens which are potentially influenced by changes within the specified substring
        /// of the source text are re-parsed by this operation.</remarks>
        private IncrementalResult ParseIncremental(StringSource input, Int32 start, Int32 count, TextParserTokenStream output, TextParserOptions options = TextParserOptions.None)
        {
            var inputLengthOld = output.SourceText.Length;
            var inputLengthNew = input.Length;
            var inputLengthDiff = inputLengthNew - inputLengthOld;

            Int32 ix1, ix2;
            FindTokensInfluencedBySubstring(output, start, count - inputLengthDiff, out ix1, out ix2);

            var token1 = output[ix1];
            var token2 = output[ix2];
            
            var invalidatedTokenCount = 1 + (ix2 - ix1);
            output.RemoveRange(ix1, invalidatedTokenCount);

            var lexStart = token1.SourceOffset;
            var lexCount = inputLengthDiff + (token2.SourceOffset + token2.SourceLength) - lexStart;
            var parserBuffer = incrementalParserBuffer.Value;
            Parse(input, parserBuffer, lexStart, lexCount);
            
            output.SourceText = input.CreateStringSegment();
            output.InsertRange(ix1, parserBuffer);

            var affectedOffset = ix1;
            var affectedCount = parserBuffer.Count;

            parserBuffer.Clear();

            return new IncrementalResult(affectedOffset, affectedCount);
        }
        /// <summary>
        /// Gets the next text token after the specified index, if one exists and there are no intervening
        /// visible tokens (excluding commands).
        /// </summary>
        private TextParserToken? GetNextTextToken(TextParserTokenStream input, Int32 index)
        {
            for (int i = index + 1; i < input.Count; i++)
            {
                var token = input[i];
                if (token.TokenType == TextParserTokenType.Text)
                    return token;

                if (token.TokenType == TextParserTokenType.Icon)
                    break;
            }
            return null;
        }
Exemple #16
0
        /// <summary>
        /// Finds the index of the first and last tokens which are potentially affected by changes in the specified substring of the source text.
        /// </summary>
        private void FindTokensInfluencedBySubstring(TextParserTokenStream result, Int32 start, Int32 count, out Int32 ix1, out Int32 ix2)
        {
            var position = 0;
            var end = start + count;

            var ix1Found = false;
            var ix2Found = false;

            ix1 = 0;
            ix2 = result.Count - 1;

            for (int i = 0; i < result.Count; i++)
            {
                var token = result[i];
                var tokenStart = token.SourceOffset;
                var tokenEnd = tokenStart + token.SourceLength;

                if (!ix1Found && (start >= tokenStart && start <= tokenEnd))
                {
                    ix1 = i;
                    ix1Found = true;
                }

                if (!ix2Found && (end >= tokenStart && end < tokenEnd))
                {
                    ix2 = i;
                    ix2Found = true;
                }

                if (ix1Found && ix2Found)
                    break;

                position += token.SourceLength;
            }
        }
        /// <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;
        }
        /// <summary>
        /// Adds a <see cref="TextLayoutCommandType.ChangeSourceString"/> or <see cref="TextLayoutCommandType.ChangeSourceStringBuilder"/> command to the output
        /// stream if it is necessary to do so for the specified parser token.
        /// </summary>
        private Boolean EmitChangeSourceIfNecessary(TextParserTokenStream input, TextLayoutCommandStream output, ref TextParserToken token)
        {
            if (!IsSegmentForCurrentSource(token.Text))
            {
                var isFirstSource = (sourceString == null && sourceStringBuilder == null);

                sourceString = token.Text.SourceString;
                sourceStringBuilder = token.Text.SourceStringBuilder;

                // NOTE: To save memory, we can elide the first change source command if it's just going to change to the input source.
                if (!isFirstSource || input.SourceText.SourceString != sourceString || input.SourceText.SourceStringBuilder != sourceStringBuilder)
                {
                    if (sourceString != null)
                    {
                        var sourceIndex = output.RegisterSourceString(sourceString);
                        output.WriteChangeSourceString(new TextLayoutSourceStringCommand(sourceIndex));
                    }
                    else
                    {
                        var sourceIndex = output.RegisterSourceStringBuilder(sourceStringBuilder);
                        output.WriteChangeSourceStringBuilder(new TextLayoutSourceStringBuilderCommand(sourceIndex));
                    }
                    return true;
                }
            }
            return false;
        }
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.Text"/>.
 /// </summary>
 private Boolean ProcessTextToken(TextParserTokenStream input, TextLayoutCommandStream output, SpriteFontFace currentFontFace,
     ref TextParserToken token, ref LayoutState state, ref TextLayoutSettings settings, ref Int32 index)
 {
     if (token.IsNewLine)
     {
         state.AdvanceLayoutToNextLineWithBreak(output, token.SourceLength, ref settings);
         index++;
     }
     else
     {
         if (!AccumulateText(input, output, currentFontFace, ref index, ref state, ref settings))
             return false;
     }
     return true;
 }
        /// <summary>
        /// Calculates a layout for the specified text.
        /// </summary>
        /// <param name="input">The parsed text which will be laid out according to the specified settings.</param>
        /// <param name="output">The layout command stream which will be populated with commands by this operation.</param>
        /// <param name="settings">A <see cref="TextLayoutSettings"/> structure which contains the settings for this operation.</param>
        public void CalculateLayout(TextParserTokenStream input, TextLayoutCommandStream output, TextLayoutSettings settings)
        {
            Contract.Require(input, "input");
            Contract.Require(output, "output");

            if (settings.Font == null)
                throw new ArgumentException(UltravioletStrings.InvalidLayoutSettings);

            var state = new LayoutState() { LineInfoCommandIndex = 1 };

            output.Clear();
            output.SourceText = input.SourceText;
            output.ParserOptions = input.ParserOptions;

            var acquiredPointers = !output.HasAcquiredPointers;
            if (acquiredPointers)
                output.AcquirePointers();

            output.WriteBlockInfo();
            output.WriteLineInfo();

            var bold = (settings.Style == SpriteFontStyle.Bold || settings.Style == SpriteFontStyle.BoldItalic);
            var italic = (settings.Style == SpriteFontStyle.Italic || settings.Style == SpriteFontStyle.BoldItalic);

            if (settings.InitialLayoutStyle != null)
                PrepareInitialStyle(output, ref bold, ref italic, ref settings);

            var currentFont = settings.Font;
            var currentFontFace = settings.Font.GetFace(SpriteFontStyle.Regular);

            var index = 0;
            var processing = true;

            while (index < input.Count && processing)
            {
                if (state.PositionY >= (settings.Height ?? Int32.MaxValue))
                    break;

                var token = input[index];

                currentFontFace = default(SpriteFontFace);
                currentFont = GetCurrentFont(ref settings, bold, italic, out currentFontFace);

                switch (token.TokenType)
                {
                    case TextParserTokenType.Text:
                        processing = ProcessTextToken(input, output, currentFontFace, ref token, ref state, ref settings, ref index);
                        break;

                    case TextParserTokenType.Icon:
                        processing = ProcessIconToken(output, ref token, ref state, ref settings, ref index);
                        break;

                    case TextParserTokenType.ToggleBold:
                        ProcessToggleBoldToken(output, ref bold, ref state, ref index);
                        break;

                    case TextParserTokenType.ToggleItalic:
                        ProcessToggleItalicToken(output, ref italic, ref state, ref index);
                        break;

                    case TextParserTokenType.PushFont:
                        ProcessPushFontToken(output, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PushColor:
                        ProcessPushColorToken(output, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PushStyle:
                        ProcessPushStyleToken(output, ref bold, ref italic, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PushGlyphShader:
                        ProcessPushGlyphShaderToken(output, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PopFont:
                        ProcessPopFontToken(output, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PopColor:
                        ProcessPopColorToken(output, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PopStyle:
                        ProcessPopStyleToken(output, ref bold, ref italic, ref token, ref state, ref index);
                        break;

                    case TextParserTokenType.PopGlyphShader:
                        ProcessPopGlyphShaderToken(output, ref token, ref state, ref index);
                        break;

                    default:
                        throw new InvalidOperationException(UltravioletStrings.UnrecognizedLayoutCommand.Format(token.TokenType));
                }
            }

            state.FinalizeLayout(output, ref settings);

            if (acquiredPointers)
                output.ReleasePointers();

            ClearLayoutStacks();
        }
Exemple #21
0
        /// <summary>
        /// Incrementally lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringBuilder"/> to parse.</param>
        /// <param name="start">The index of the first character that was changed.</param>
        /// <param name="count">The number of characters that were changed.</param>
        /// <param name="result">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        /// <returns>An <see cref="IncrementalResult"/> structure that represents the result of the operation.</returns>
        /// <remarks>Incremental parsing provides a performance benefit when relatively small changes are being made
        /// to a large source text. Only tokens which are potentially influenced by changes within the specified substring
        /// of the source text are re-parsed by this operation.</remarks>
        public IncrementalResult ParseIncremental(StringBuilder input, Int32 start, Int32 count, TextParserTokenStream result, TextParserOptions options = TextParserOptions.None)
        {
            Contract.Require(input, nameof(input));
            Contract.Require(result, nameof(result));
            Contract.EnsureRange(start >= 0, nameof(start));
            Contract.EnsureRange(count >= 0 && start + count <= input.Length, nameof(count));

            return(ParseIncremental(new StringSource(input), start, count, result, options));
        }
Exemple #22
0
        /// <summary>
        /// Lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringSource"/> to parse.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="index">The index at which to begin parsing the input string.</param>
        /// <param name="count">the number of characters to parse.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        private void Parse(StringSource input, TextParserTokenStream output, Int32 index, Int32 count, TextParserOptions options = TextParserOptions.None)
        {
            var bound = index + count;
            while (index < bound)
            {
                if (IsStartOfNewline(input, index))
                {
                    output.Add(ConsumeNewlineToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfNonBreakingSpace(input, index))
                {
                    output.Add(ConsumeNonBreakingSpaceToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfBreakingSpace(input, index))
                {
                    output.Add(ConsumeBreakingSpaceToken(input, options, ref index));
                    continue;
                }
                if (IsEscapedPipe(input, index, options))
                {
                    output.Add(ConsumeEscapedPipeToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfCommand(input, index))
                {
                    output.Add(ConsumeCommandToken(input, options, ref index));
                    continue;
                }
                if (IsStartOfWord(input, index))
                {
                    output.Add(ConsumeWordToken(input, options, ref index));
                    continue;
                }
                index++;
            }

            output.SourceText = input.CreateStringSegment();
            output.ParserOptions = options;
        }
Exemple #23
0
        /// <summary>
        /// Incrementally lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringSource"/> to parse.</param>
        /// <param name="start">The index of the first character that was changed.</param>
        /// <param name="count">The number of characters that were changed.</param>
        /// <param name="output">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        /// <returns>An <see cref="IncrementalResult"/> structure that represents the result of the operation.</returns>
        /// <remarks>Incremental parsing provides a performance benefit when relatively small changes are being made
        /// to a large source text. Only tokens which are potentially influenced by changes within the specified substring
        /// of the source text are re-parsed by this operation.</remarks>
        private IncrementalResult ParseIncremental(StringSource input, Int32 start, Int32 count, TextParserTokenStream output, TextParserOptions options = TextParserOptions.None)
        {
            var inputLengthOld  = output.SourceText.Length;
            var inputLengthNew  = input.Length;
            var inputLengthDiff = inputLengthNew - inputLengthOld;

            Int32 ix1, ix2;

            FindTokensInfluencedBySubstring(output, start, count - inputLengthDiff, out ix1, out ix2);

            var token1 = output[ix1];
            var token2 = output[ix2];

            var invalidatedTokenCount = 1 + (ix2 - ix1);

            output.RemoveRange(ix1, invalidatedTokenCount);

            var lexStart     = token1.SourceOffset;
            var lexCount     = inputLengthDiff + (token2.SourceOffset + token2.SourceLength) - lexStart;
            var parserBuffer = incrementalParserBuffer.Value;

            Parse(input, parserBuffer, lexStart, lexCount);

            output.SourceText = input.CreateStringSegment();
            output.InsertRange(ix1, parserBuffer);

            var affectedOffset = ix1;
            var affectedCount  = parserBuffer.Count;

            parserBuffer.Clear();

            return(new IncrementalResult(affectedOffset, affectedCount));
        }
        /// <summary>
        /// Updates the cache which contains the element's parsed text.
        /// </summary>
        private void UpdateTextParserCache()
        {
            if (textParserResult != null)
                textParserResult.Clear();

            if (View == null)
                return;

            var content = Content;

            var contentElement = content as UIElement;
            if (contentElement == null)
            {
                if (textParserResult == null)
                    textParserResult = new TextParserTokenStream();

                var contentAsString = default(String);
                var contentFormat = ContentStringFormat;
                if (contentFormat == null)
                {
                    contentAsString = (content == null) ? String.Empty : content.ToString();
                }
                else
                {
                    contentAsString = String.Format(contentFormat, content);
                }

                View.Resources.TextRenderer.Parse(contentAsString, textParserResult);
            }

            InvalidateArrange();
        }
Exemple #25
0
        /// <summary>
        /// Incrementally lexes and parses the specified string.
        /// </summary>
        /// <param name="input">The <see cref="StringBuilder"/> to parse.</param>
        /// <param name="start">The index of the first character that was changed.</param>
        /// <param name="count">The number of characters that were changed.</param>
        /// <param name="result">The parsed token stream.</param>
        /// <param name="options">A set of <see cref="TextParserOptions"/> values that specify how the text should be parsed.</param>
        /// <returns>An <see cref="IncrementalResult"/> structure that represents the result of the operation.</returns>
        /// <remarks>Incremental parsing provides a performance benefit when relatively small changes are being made
        /// to a large source text. Only tokens which are potentially influenced by changes within the specified substring
        /// of the source text are re-parsed by this operation.</remarks>
        public IncrementalResult ParseIncremental(StringBuilder input, Int32 start, Int32 count, TextParserTokenStream result, TextParserOptions options = TextParserOptions.None)
        {
            Contract.Require(input, nameof(input));
            Contract.Require(result, nameof(result));
            Contract.EnsureRange(start >= 0, nameof(start));
            Contract.EnsureRange(count >= 0 && start + count <= input.Length, nameof(count));

            return ParseIncremental(new StringSource(input), start, count, result, options);
        }