Пример #1
0
            public void WriteLineInfo(TextLayoutCommandStream output,
                                      Int32 lineWidth, Int32 lineHeight, Int32 lengthInCommands, Int32 lengthInGlyphs, Boolean terminatedByLineBreak, ref TextLayoutSettings settings)
            {
                var offset = 0;

                if (settings.Width.HasValue)
                {
                    if ((settings.Flags & TextFlags.AlignRight) == TextFlags.AlignRight)
                    {
                        offset = (settings.Width.Value - lineWidth);
                    }
                    else if ((settings.Flags & TextFlags.AlignCenter) == TextFlags.AlignCenter)
                    {
                        offset = (settings.Width.Value - lineWidth) / 2;
                    }
                }

                var outputStreamPosition = output.StreamPositionInObjects;

                output.Seek(lineInfoCommandIndex);
                unsafe
                {
                    var ptr = (TextLayoutLineInfoCommand *)output.Data;
                    ptr->Offset                = offset;
                    ptr->LineWidth             = lineWidth;
                    ptr->LineHeight            = lineHeight;
                    ptr->LengthInCommands      = lengthInCommands;
                    ptr->LengthInGlyphs        = lengthInGlyphs;
                    ptr->TerminatedByLineBreak = terminatedByLineBreak;
                }
                output.Seek(outputStreamPosition);

                minLineOffset = (minLineOffset.HasValue) ? Math.Min(minLineOffset.Value, offset) : offset;
            }
Пример #2
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);
        }
Пример #3
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PopLink"/>.
 /// </summary>
 private void ProcessPopLinkToken(TextLayoutCommandStream output,
                                  ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     output.WritePopLink();
     state.AdvanceLineToNextCommand();
     index++;
 }
Пример #4
0
            /// <summary>
            /// Finalizes the layout by writing the block's metadata to the command stream.
            /// </summary>
            /// <param name="output">The <see cref="TextLayoutCommandStream"/> which is being populated.</param>
            /// <param name="settings">The current layout settings.</param>
            public void FinalizeLayout(TextLayoutCommandStream output, ref TextLayoutSettings settings)
            {
                if (LineHeightTentative > 0 || LineHeight > 0)
                {
                    FinalizeLine(output, ref settings);
                }

                WriteBlockInfo(output, ActualWidth, ActualHeight, LineCount, ref settings);

                output.Settings     = settings;
                output.Bounds       = Bounds;
                output.ActualWidth  = ActualWidth;
                output.ActualHeight = ActualHeight;
                output.TotalLength  = TotalLength;
                output.LineCount    = LineCount;

                if (!settings.Width.HasValue)
                {
                    if ((settings.Flags & TextFlags.AlignCenter) == TextFlags.AlignCenter ||
                        (settings.Flags & TextFlags.AlignRight) == TextFlags.AlignRight)
                    {
                        FixHorizontalAlignmentForUnconstrainedLayout(output, ref settings);
                    }
                }
            }
Пример #5
0
            public void WriteBlockInfo(TextLayoutCommandStream output, Int32 blockWidth, Int32 blockHeight, Int32 lengthInLines, ref TextLayoutSettings settings)
            {
                var offset = 0;

                if (settings.Height.HasValue)
                {
                    if ((settings.Flags & TextFlags.AlignBottom) == TextFlags.AlignBottom)
                    {
                        offset = (settings.Height.Value - blockHeight);
                    }
                    else if ((settings.Flags & TextFlags.AlignMiddle) == TextFlags.AlignMiddle)
                    {
                        offset = (settings.Height.Value - blockHeight) / 2;
                    }
                }

                output.Seek(0);
                unsafe
                {
                    var ptr = (TextLayoutBlockInfoCommand *)output.Data;
                    ptr->Offset        = offset;
                    ptr->LengthInLines = lengthInLines;
                }
                output.Seek(output.Count);

                minBlockOffset = (minBlockOffset.HasValue) ? Math.Min(minBlockOffset.Value, offset) : offset;
            }
            public void WriteLineInfo(TextLayoutCommandStream output, 
                Int32 lineWidth, Int32 lineHeight, Int32 lengthInCommands, Int32 lengthInGlyphs, Boolean terminatedByLineBreak, ref TextLayoutSettings settings)
            {
                var offset = 0;

                if (settings.Width.HasValue)
                {
                    if ((settings.Flags & TextFlags.AlignRight) == TextFlags.AlignRight)
                        offset = (settings.Width.Value - lineWidth);
                    else if ((settings.Flags & TextFlags.AlignCenter) == TextFlags.AlignCenter)
                        offset = (settings.Width.Value - lineWidth) / 2;
                }

                var outputStreamPosition = output.StreamPositionInObjects;
                output.Seek(lineInfoCommandIndex);
                unsafe
                {
                    var ptr = (TextLayoutLineInfoCommand*)output.Data;
                    ptr->Offset = offset;
                    ptr->LineWidth = lineWidth;
                    ptr->LineHeight = lineHeight;
                    ptr->LengthInCommands = lengthInCommands;
                    ptr->LengthInGlyphs = lengthInGlyphs;
                    ptr->TerminatedByLineBreak = terminatedByLineBreak;
                }
                output.Seek(outputStreamPosition);

                minLineOffset = (minLineOffset.HasValue) ? Math.Min(minLineOffset.Value, offset) : offset;
            }
Пример #7
0
            /// <summary>
            /// Finalizes the current line by writing the line's metadata to the command stream and resetting
            /// state values which are associated with the current line.
            /// </summary>
            /// <param name="output">The <see cref="TextLayoutCommandStream"/> which is being populated.</param>
            /// <param name="settings">The current layout settings.</param>
            public void FinalizeLine(TextLayoutCommandStream output, ref TextLayoutSettings settings)
            {
                if (lineHeight == 0)
                {
                    lineHeight = lineHeightTentative;
                }

                WriteLineInfo(output, lineWidth, lineHeight, lineLengthInCommands, lineLengthInText, lineIsTerminatedByLineBreak, ref settings);

                positionX     = 0;
                positionY    += lineHeight;
                actualWidth   = Math.Max(actualWidth, lineWidth);
                actualHeight += lineHeight;
                lineCount++;
                lineWidth                   = 0;
                lineHeight                  = 0;
                lineHeightTentative         = 0;
                lineLengthInText            = 0;
                lineLengthInCommands        = 0;
                lineInfoCommandIndex        = output.Count;
                lineBreakCommand            = null;
                lineBreakOffset             = null;
                lineIsTerminatedByLineBreak = false;
                brokenTextSizeBeforeBreak   = null;
                brokenTextSizeAfterBreak    = null;
            }
Пример #8
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.ToggleItalic"/>.
 /// </summary>
 private void ProcessToggleItalicToken(TextLayoutCommandStream output, ref Boolean italic,
                                       ref LayoutState state, ref Int32 index)
 {
     output.WriteToggleItalic();
     state.AdvanceLineToNextCommand();
     italic = !italic;
     index++;
 }
Пример #9
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.ToggleBold"/>.
 /// </summary>
 private void ProcessToggleBoldToken(TextLayoutCommandStream output, ref Boolean bold,
                                     ref LayoutState state, ref Int32 index)
 {
     output.WriteToggleBold();
     state.AdvanceLineToNextCommand();
     bold = !bold;
     index++;
 }
Пример #10
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PopStyle"/>.
 /// </summary>
 private void ProcessPopStyleToken(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic,
                                   ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     output.WritePopStyle();
     state.AdvanceLineToNextCommand();
     PopStyle(ref bold, ref italic);
     index++;
 }
Пример #11
0
        /// <summary>
        /// Registers the specified icon with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterIconWithCommandStream(TextLayoutCommandStream output, StringSegment name, out TextIconInfo icon)
        {
            if (!registeredIcons.TryGetValue(name, out icon))
            {
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedIcon.Format(name));
            }

            return(output.RegisterIcon(name, icon));
        }
Пример #12
0
        /// <summary>
        /// Registers the specified font with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterFontWithCommandStream(TextLayoutCommandStream output, StringSegment name, out SpriteFont font)
        {
            if (!registeredFonts.TryGetValue(name, out font))
            {
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedFont.Format(name));
            }

            return(output.RegisterFont(name, font));
        }
Пример #13
0
        /// <summary>
        /// Deactivates the specified command stream's activated link, if it has one.
        /// </summary>
        /// <param name="stream">The command stream to update.</param>
        /// <param name="element">The element that owns the command stream.</param>
        /// <returns><see langword="true"/> if the command stream's link was deactivated; otherwise, <see langword="false"/>.</returns>
        public static Boolean DeactivateTextLink(TextLayoutCommandStream stream, UIElement element)
        {
            Contract.Require(element, nameof(element));

            if (stream == null || element.View == null)
                return false;

            return element.View.Resources.TextRenderer.DeactivateLink(stream);
        }
Пример #14
0
        /// <summary>
        /// Registers the specified style with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterStyleWithCommandStream(TextLayoutCommandStream output, StringSegment name, out TextStyle style)
        {
            if (!registeredStyles.TryGetValue(name, out style))
            {
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedStyle.Format(name));
            }

            return(output.RegisterStyle(name, style));
        }
Пример #15
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.PushColor"/>.
        /// </summary>
        private void ProcessPushColorToken(TextLayoutCommandStream output,
                                           ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var pushedColor = ParseColor(token.Text);

            output.WritePushColor(new TextLayoutColorCommand(pushedColor));
            state.AdvanceLineToNextCommand();
            index++;
        }
Пример #16
0
        /// <summary>
        /// Registers the specified glyph shader with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterGlyphShaderWithCommandStream(TextLayoutCommandStream output, StringSegment name, out GlyphShader glyphShader)
        {
            if (!registeredGlyphShaders.TryGetValue(name, out glyphShader))
            {
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedGlyphShader.Format(name));
            }

            return(output.RegisterGlyphShader(name, glyphShader));
        }
Пример #17
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.PushLink"/>.
        /// </summary>
        private void ProcessPushLinkToken(TextLayoutCommandStream output,
                                          ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var pushedLinkTargetIndex = RegisterLinkTargetWithCommandStream(output, token.Text);

            output.WritePushLink(new TextLayoutLinkCommand(pushedLinkTargetIndex));
            state.AdvanceLineToNextCommand();
            index++;
        }
Пример #18
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.PushGlyphShader"/>.
        /// </summary>
        private void ProcessPushGlyphShaderToken(TextLayoutCommandStream output,
                                                 ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var pushedGlyphShader      = default(GlyphShader);
            var pushedGlyphShaderIndex = RegisterGlyphShaderWithCommandStream(output, token.Text, out pushedGlyphShader);

            output.WritePushGlyphShader(new TextLayoutGlyphShaderCommand(pushedGlyphShaderIndex));
            state.AdvanceLineToNextCommand();
            index++;
        }
Пример #19
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.Custom"/>.
        /// </summary>
        private void ProcessCustomCommandToken(TextLayoutCommandStream output,
                                               ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var commandID    = (token.TokenType - TextParserTokenType.Custom);
            var commandValue = token.Text.IsEmpty ? default(Int32) : StringSegmentConversion.ParseInt32(token.Text);

            output.WriteCustomCommand(new TextLayoutCustomCommand(commandID, commandValue));
            state.AdvanceLineToNextCommand();
            index++;
        }
Пример #20
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.PushStyle"/>.
        /// </summary>
        private void ProcessPushStyleToken(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic,
                                           ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var pushedStyle      = default(TextStyle);
            var pushedStyleIndex = RegisterStyleWithCommandStream(output, token.Text, out pushedStyle);

            output.WritePushStyle(new TextLayoutStyleCommand(pushedStyleIndex));
            state.AdvanceLineToNextCommand();
            PushStyle(pushedStyle, ref bold, ref italic);
            index++;
        }
Пример #21
0
        /// <summary>
        /// Processes a parser token with type <see cref="TextParserTokenType.PushFont"/>.
        /// </summary>
        private void ProcessPushFontToken(TextLayoutCommandStream output,
                                          ref TextParserToken token, ref LayoutState state, ref Int32 index)
        {
            var pushedFont      = default(SpriteFont);
            var pushedFontIndex = RegisterFontWithCommandStream(output, token.Text, out pushedFont);

            output.WritePushFont(new TextLayoutFontCommand(pushedFontIndex));
            state.AdvanceLineToNextCommand();
            PushFont(pushedFont);
            index++;
        }
Пример #22
0
        /// <summary>
        /// Activates any link at the current cursor position within the specified command stream.
        /// </summary>
        /// <param name="stream">The command stream to update.</param>
        /// <param name="element">The element that owns the command stream.</param>
        /// <param name="data">The event metadata for the routed event which prompted the link activation.</param>
        /// <returns><see langword="true"/> if the command stream's link was deactivated; otherwise, <see langword="false"/>.</returns>
        public static Boolean ActivateTextLink(TextLayoutCommandStream stream, UIElement element, RoutedEventData data)
        {
            Contract.Require(element, nameof(element));

            if (stream == null || element.View == null || !element.View.Resources.TextRenderer.ActivateLinkAtCursor(stream))
                return false;

            element.Focus();
            element.CaptureMouse();

            data.Handled = true;
            return true;
        }
Пример #23
0
        /// <summary>
        /// If the layout has an initial style defined, this method modifies the layout stacks to reflect it.
        /// </summary>
        private void PrepareInitialStyle(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic, ref TextLayoutSettings settings)
        {
            if (settings.InitialLayoutStyle == null)
            {
                return;
            }

            var initialStyle      = default(TextStyle);
            var initialStyleIndex = RegisterStyleWithCommandStream(output, settings.InitialLayoutStyle, out initialStyle);

            output.WritePushStyle(new TextLayoutStyleCommand(initialStyleIndex));
            PushStyle(initialStyle, ref bold, ref italic);
        }
Пример #24
0
 /// <summary>
 /// Initializes a new instance of the <see cref="LineInfo"/> structure.
 /// </summary>
 /// <param name="source">The command stream from which this line was retrieved.</param>
 /// <param name="lineIndex">The index of the line within its layout.</param>
 /// <param name="offsetInCommands">The index of the command that contains the line's metadata.</param>
 /// <param name="offsetInGlyphs">The index of the first glyph in the line.</param>
 /// <param name="x">The x-coordinate of the line's top-left corner relative to its layout area.</param>
 /// <param name="y">The y-coordinate of the line's top-left corner relative to its layout area.</param>
 /// <param name="width">The line's width in pixels.</param>
 /// <param name="height">The line's height in pixels.</param>
 /// <param name="lengthInCommands">The line's length in commands.</param>
 /// <param name="lengthInGlyphs">The line's length in glyphs.</param>
 internal LineInfo(TextLayoutCommandStream source, Int32 lineIndex, Int32 offsetInCommands, Int32 offsetInGlyphs, 
     Int32 x, Int32 y, Int32 width, Int32 height, Int32 lengthInCommands, Int32 lengthInGlyphs)
 {
     this.source = source;
     this.lineIndex = lineIndex;
     this.offsetInCommands = offsetInCommands;
     this.offsetInGlyphs = offsetInGlyphs;
     this.x = x;
     this.y = y;
     this.width = width;
     this.height = height;
     this.lengthInCommands = lengthInCommands;
     this.lengthInGlyphs = lengthInGlyphs;
 }
Пример #25
0
 /// <summary>
 /// Initializes a new instance of the <see cref="LineInfo"/> structure.
 /// </summary>
 /// <param name="source">The command stream from which this line was retrieved.</param>
 /// <param name="lineIndex">The index of the line within its layout.</param>
 /// <param name="offsetInCommands">The index of the command that contains the line's metadata.</param>
 /// <param name="offsetInGlyphs">The index of the first glyph in the line.</param>
 /// <param name="x">The x-coordinate of the line's top-left corner relative to its layout area.</param>
 /// <param name="y">The y-coordinate of the line's top-left corner relative to its layout area.</param>
 /// <param name="width">The line's width in pixels.</param>
 /// <param name="height">The line's height in pixels.</param>
 /// <param name="lengthInCommands">The line's length in commands.</param>
 /// <param name="lengthInGlyphs">The line's length in glyphs.</param>
 internal LineInfo(TextLayoutCommandStream source, Int32 lineIndex, Int32 offsetInCommands, Int32 offsetInGlyphs,
                   Int32 x, Int32 y, Int32 width, Int32 height, Int32 lengthInCommands, Int32 lengthInGlyphs)
 {
     this.source           = source;
     this.lineIndex        = lineIndex;
     this.offsetInCommands = offsetInCommands;
     this.offsetInGlyphs   = offsetInGlyphs;
     this.x                = x;
     this.y                = y;
     this.width            = width;
     this.height           = height;
     this.lengthInCommands = lengthInCommands;
     this.lengthInGlyphs   = lengthInGlyphs;
 }
Пример #26
0
        /// <summary>
        /// Adds a <see cref="TextLayoutCommandType.Text"/> command to the output stream if the specified span of text has a non-zero length.
        /// </summary>
        private Boolean EmitTextIfNecessary(TextLayoutCommandStream output, Int32 start, Int32 length, ref Rectangle bounds, ref LayoutState state)
        {
            if (length == 0)
            {
                return(false);
            }

            output.WriteText(new TextLayoutTextCommand(start, length,
                                                       bounds.X, bounds.Y, (Int16)bounds.Width, (Int16)bounds.Height));

            state.LineLengthInCommands++;

            return(true);
        }
Пример #27
0
        /// <summary>
        /// Executes any link at the current cursor position within the specified command stream.
        /// </summary>
        /// <param name="stream">The command stream to update.</param>
        /// <param name="element">The element that owns the command stream.</param>
        /// <param name="data">The event metadata for the routed event which prompted the link execution.</param>
        public static Boolean ExecuteTextLink(TextLayoutCommandStream stream, UIElement element, RoutedEventData data)
        {
            Contract.Require(element, nameof(element));

            if (stream == null || element.View == null)
                return false;

            if (stream.ActiveLinkIndex.HasValue)
                element.ReleaseMouseCapture();

            if (!element.View.Resources.TextRenderer.ExecuteActivatedLink(stream))
                return false;

            data.Handled = true;
            return true;
        }
Пример #28
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);
 }
Пример #29
0
            private unsafe void FixHorizontalAlignmentForUnconstrainedLayout(TextLayoutCommandStream output, ref TextLayoutSettings settings)
            {
                output.Seek(0);

                while (output.SeekNextLine())
                {
                    var lineInfo = (TextLayoutLineInfoCommand *)output.InternalObjectStream.Data;

                    if ((settings.Flags & TextFlags.AlignRight) == TextFlags.AlignRight)
                    {
                        lineInfo->Offset = (output.ActualWidth - lineInfo->LineWidth);
                    }
                    else if ((settings.Flags & TextFlags.AlignCenter) == TextFlags.AlignCenter)
                    {
                        lineInfo->Offset = (output.ActualWidth - lineInfo->LineWidth) / 2;
                    }
                }
            }
Пример #30
0
            /// <summary>
            /// Advances the layout state to the next line of text after inserting a line break character at the end of the current line.
            /// </summary>
            /// <param name="output">The <see cref="TextLayoutCommandStream"/> which is being populated.</param>
            /// <param name="length">The number of characters in the line break.</param>
            /// <param name="settings">The current layout settings.</param>
            public void AdvanceLayoutToNextLineWithBreak(TextLayoutCommandStream output, Int32 length, ref TextLayoutSettings settings)
            {
                var lineSpacing = settings.Font.GetFace(SpriteFontStyle.Regular).LineSpacing;

                var lineHeightCurrent = lineHeight;

                if (lineHeightCurrent == 0)
                {
                    lineHeightCurrent = lineSpacing;
                }

                output.WriteLineBreak(new TextLayoutLineBreakCommand(length));
                AdvanceLineToNextCommand(0, lineHeightCurrent, 1, length, isLineBreak: true);

                AdvanceLayoutToNextLine(output, ref settings);
                AdvanceLineToNextCommand(0, 0, 0, 0);

                lineHeightTentative = lineSpacing;
            }
Пример #31
0
        /// <summary>
        /// Updates the position of the cursor within the specified command stream.
        /// </summary>
        /// <param name="stream">The command stream to update.</param>
        /// <param name="element">The element that owns the command stream.</param>
        /// <param name="position">The position of the input device.</param>
        public static void UpdateLinkCursor(TextLayoutCommandStream stream, UIElement element, Point2D? position)
        {
            Contract.Require(element, nameof(element));

            if (stream == null || element.View == null)
                return;

            var positionDips = position;
            var positionPixs = positionDips.HasValue ? (Point2)element.View.Display.DipsToPixels(positionDips.Value) : (Point2?)null;

            if (positionDips.HasValue && (element.IsMouseOver || element.IsMouseCaptured))
            {
                element.View.Resources
                    .TextRenderer.UpdateCursor(stream, positionPixs);
            }
            else
            {
                element.View.Resources
                    .TextRenderer.UpdateCursor(stream, null);
            }
        }
Пример #32
0
        /// <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();
        }
            public void WriteBlockInfo(TextLayoutCommandStream output, Int32 blockWidth, Int32 blockHeight, Int32 lengthInLines, ref TextLayoutSettings settings)
            {
                var offset = 0;

                if (settings.Height.HasValue)
                {
                    if ((settings.Flags & TextFlags.AlignBottom) == TextFlags.AlignBottom)
                        offset = (settings.Height.Value - blockHeight);
                    else if ((settings.Flags & TextFlags.AlignMiddle) == TextFlags.AlignMiddle)
                        offset = (settings.Height.Value - blockHeight) / 2;
                }

                output.Seek(0);
                unsafe
                {
                    var ptr = (TextLayoutBlockInfoCommand*)output.Data;
                    ptr->Offset = offset;
                    ptr->LengthInLines = lengthInLines;
                }
                output.Seek(output.Count);

                minBlockOffset = (minBlockOffset.HasValue) ? Math.Min(minBlockOffset.Value, offset) : offset;
            }
Пример #34
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);
        }
Пример #35
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);
        }
Пример #36
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PushFont"/>.
 /// </summary>
 private void ProcessPushFontToken(TextLayoutCommandStream output,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     var pushedFont = default(SpriteFont);
     var pushedFontIndex = RegisterFontWithCommandStream(output, token.Text, out pushedFont);
     output.WritePushFont(new TextLayoutFontCommand(pushedFontIndex));
     state.AdvanceLineToNextCommand();
     PushFont(pushedFont);
     index++;
 }
Пример #37
0
 /// <summary>
 /// Registers the specified link target with the command stream and returns its resulting index.
 /// </summary>
 private Int16 RegisterLinkTargetWithCommandStream(TextLayoutCommandStream output, StringSegment target)
 {
     return(output.RegisterLinkTarget(target.ToString()));
 }
Пример #38
0
        public void TextRenderer_CorrectlyRendersLinks_WithColorizer()
        {
            var spriteBatch = default(SpriteBatch);
            var spriteFont = default(SpriteFont);
            var textRenderer = default(TextRenderer);
            var textStream = default(TextLayoutCommandStream);

            var result = GivenAnUltravioletApplication()
                .WithContent(content =>
                {
                    spriteBatch = SpriteBatch.Create();
                    spriteFont = content.Load<SpriteFont>("Fonts/Garamond");
                    textRenderer = new TextRenderer();
                    textStream = new TextLayoutCommandStream();
                })
                .Render(uv =>
                {
                    uv.GetGraphics().Clear(Color.CornflowerBlue);

                    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);

                    var window = uv.GetPlatform().Windows.GetCurrent();
                    var width = window.DrawableSize.Width;
                    var height = window.DrawableSize.Height;

                    textRenderer.LinkColorizer = (target, visited, hovering, active, currentColor) =>
                    {
                        if (active)
                        {
                            return Color.Magenta;
                        }
                        else
                        {
                            if (visited)
                            {
                                return Color.Yellow;
                            }
                            else
                            {
                                return Color.Lime;
                            }
                        }
                    };
                    textRenderer.LinkStateEvaluator = (target) => String.Equals(target, "visited", StringComparison.InvariantCulture);

                    var settings = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignCenter | TextFlags.AlignMiddle);
                    textRenderer.CalculateLayout(
                        "Links can |link:unvisited|unvisited|link| if you've never clicked them.\n" +
                        "Links can be |link:visited|visisted|link| if you've already clicked them.\n" +
                        "Links can be |link:active|active even if they\ncross multiple lines|link| if the cursor is clicking them.", textStream, settings);

                    textRenderer.UpdateCursor(textStream, new Point2(236, 191));
                    textRenderer.ActivateLinkAtCursor(textStream);

                    textRenderer.Draw(spriteBatch, textStream, Vector2.Zero, Color.White);

                    spriteBatch.End();
                });

            TheResultingImage(result)
                .ShouldMatch(@"Resources/Expected/Graphics/Graphics2D/Text/TextRenderer_CorrectlyRendersLinks_WithColorizer.png");
        }
Пример #39
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, "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();
        }
Пример #40
0
        /// <summary>
        /// If the layout has an initial style defined, this method modifies the layout stacks to reflect it.
        /// </summary>
        private void PrepareInitialStyle(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic, ref TextLayoutSettings settings)
        {
            if (settings.InitialLayoutStyle == null)
                return;

            var initialStyle = default(TextStyle);
            var initialStyleIndex = RegisterStyleWithCommandStream(output, settings.InitialLayoutStyle, out initialStyle);
            output.WritePushStyle(new TextLayoutStyleCommand(initialStyleIndex));
            PushStyle(initialStyle, ref bold, ref italic);
        }
Пример #41
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;
        }
Пример #42
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;
        }
Пример #43
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;
        }
Пример #44
0
        /// <summary>
        /// Adds a <see cref="TextLayoutCommandType.Text"/> command to the output stream if the specified span of text has a non-zero length.
        /// </summary>
        private Boolean EmitTextIfNecessary(TextLayoutCommandStream output, Int32 start, Int32 length, ref Rectangle bounds, ref LayoutState state)
        {
            if (length == 0)
                return false;

            output.WriteText(new TextLayoutTextCommand(start, length, 
                bounds.X, bounds.Y, (Int16)bounds.Width, (Int16)bounds.Height));

            state.LineLengthInCommands++;

            return true;
        }
Пример #45
0
        /// <summary>
        /// Registers the specified style with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterStyleWithCommandStream(TextLayoutCommandStream output, StringSegment name, out TextStyle style)
        {
            if (!registeredStyles.TryGetValue(name, out style))
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedStyle.Format(name));

            return output.RegisterStyle(name, style);
        }
Пример #46
0
        /// <summary>
        /// Registers the specified icon with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterIconWithCommandStream(TextLayoutCommandStream output, StringSegment name, out TextIconInfo icon)
        {
            if (!registeredIcons.TryGetValue(name, out icon))
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedIcon.Format(name));

            return output.RegisterIcon(name, icon);
        }
Пример #47
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.ToggleItalic"/>.
 /// </summary>
 private void ProcessToggleItalicToken(TextLayoutCommandStream output, ref Boolean italic,
     ref LayoutState state, ref Int32 index)
 {
     output.WriteToggleItalic();
     state.AdvanceLineToNextCommand();
     italic = !italic;
     index++;
 }
Пример #48
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PopGlyphShader"/>.
 /// </summary>
 private void ProcessPopGlyphShaderToken(TextLayoutCommandStream output,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     output.WritePopGlyphShader();
     state.AdvanceLineToNextCommand();
     index++;
 }
Пример #49
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.ToggleBold"/>.
 /// </summary>
 private void ProcessToggleBoldToken(TextLayoutCommandStream output, ref Boolean bold,
     ref LayoutState state, ref Int32 index)
 {
     output.WriteToggleBold();
     state.AdvanceLineToNextCommand();
     bold = !bold;
     index++;
 }
Пример #50
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PopStyle"/>.
 /// </summary>
 private void ProcessPopStyleToken(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     output.WritePopStyle();
     state.AdvanceLineToNextCommand();
     PopStyle(ref bold, ref italic);
     index++;
 }
Пример #51
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;
 }
Пример #52
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PushGlyphShader"/>.
 /// </summary>
 private void ProcessPushGlyphShaderToken(TextLayoutCommandStream output,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     var pushedGlyphShader = default(GlyphShader);
     var pushedGlyphShaderIndex = RegisterGlyphShaderWithCommandStream(output, token.Text, out pushedGlyphShader);
     output.WritePushGlyphShader(new TextLayoutGlyphShaderCommand(pushedGlyphShaderIndex));
     state.AdvanceLineToNextCommand();
     index++;
 }
Пример #53
0
        /// <summary>
        /// Updates the cache which contains the element's laid-out text.
        /// </summary>
        /// <param name="availableSize">The amount of space in which the element's text can be laid out.</param>
        private void UpdateTextLayoutCache(Size2D availableSize)
        {
            if (textLayoutCommands != null)
                textLayoutCommands.Clear();

            if (View == null || containingControl == null)
                return;

            var content = Content;

            var contentElement = content as UIElement;
            if (contentElement == null)
            {
                if (textLayoutCommands == null)
                    textLayoutCommands = new TextLayoutCommandStream();

                var font = containingControl.Font;
                if (font.IsLoaded)
                {
                    var availableSizeInPixels = Display.DipsToPixels(availableSize);

                    var cursorpos = textLayoutCommands.CursorPosition;

                    var flags = LayoutUtil.ConvertAlignmentsToTextFlags(HorizontalAlignment, VerticalAlignment);
                    var settings = new TextLayoutSettings(font,
                        (Int32)Math.Ceiling(availableSizeInPixels.Width),
                        (Int32)Math.Ceiling(availableSizeInPixels.Height), flags, containingControl.FontStyle);

                    View.Resources.TextRenderer.CalculateLayout(textParserResult, textLayoutCommands, settings);
                    View.Resources.TextRenderer.UpdateCursor(textLayoutCommands, cursorpos);
                }
            }
        }
Пример #54
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PushStyle"/>.
 /// </summary>
 private void ProcessPushStyleToken(TextLayoutCommandStream output, ref Boolean bold, ref Boolean italic,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     var pushedStyle = default(TextStyle);
     var pushedStyleIndex = RegisterStyleWithCommandStream(output, token.Text, out pushedStyle);
     output.WritePushStyle(new TextLayoutStyleCommand(pushedStyleIndex));
     state.AdvanceLineToNextCommand();
     PushStyle(pushedStyle, ref bold, ref italic);
     index++;
 }
Пример #55
0
            public unsafe Boolean ReplaceLastBreakingSpaceWithLineBreak(TextLayoutCommandStream output, ref TextLayoutSettings settings)
            {
                if (!lineBreakCommand.HasValue || !lineBreakOffset.HasValue)
                {
                    return(false);
                }

                var sizeBeforeBreak     = brokenTextSizeBeforeBreak.Value;
                var sizeAfterBreak      = brokenTextSizeAfterBreak.Value;
                var brokenCommandSize   = Size2.Zero;
                var brokenCommandOffset = 0;
                var brokenCommandLength = 0;

                var newLineHeight = sizeAfterBreak.Height;

                if (newLineHeight == 0)
                {
                    newLineHeight = settings.Font.GetFace(SpriteFontStyle.Regular).LineSpacing;
                }

                // Truncate the command which is being broken.
                output.Seek(lineBreakCommand.Value);
                unsafe
                {
                    var cmd = (TextLayoutTextCommand *)output.Data;

                    brokenCommandOffset = cmd->TextOffset;
                    brokenCommandLength = cmd->TextLength;
                    brokenCommandSize   = cmd->Bounds.Size;

                    cmd->TextLength = lineBreakOffset.Value;
                    cmd->TextWidth  = (Int16)sizeBeforeBreak.Width;
                    cmd->TextHeight = (Int16)sizeBeforeBreak.Height;
                }
                output.SeekNextCommand();

                // Insert a line break, a new line, and the second half of the truncated text.
                var part1Length          = lineBreakOffset.Value;
                var part2Offset          = brokenCommandOffset + (lineBreakOffset.Value + 1);
                var part2Length          = brokenCommandLength - (part1Length + 1);
                var part2IsNotDegenerate = (part2Length > 0);

                var numberOfObjects = part2IsNotDegenerate ? 3 : 2;
                var numberOfBytes   =
                    sizeof(TextLayoutLineBreakCommand) +
                    sizeof(TextLayoutLineInfoCommand) +
                    (part2IsNotDegenerate ? sizeof(TextLayoutTextCommand) : 0);

                var insertionPosition = output.InternalObjectStream.PositionInObjects;

                output.InternalObjectStream.ReserveInsert(numberOfObjects, numberOfBytes);

                *(TextLayoutLineBreakCommand *)output.Data = new TextLayoutLineBreakCommand(1);
                output.InternalObjectStream.FinalizeObject(sizeof(TextLayoutLineBreakCommand));

                *(TextLayoutCommandType *)output.Data = TextLayoutCommandType.LineInfo;
                output.InternalObjectStream.FinalizeObject(sizeof(TextLayoutLineInfoCommand));

                if (part2IsNotDegenerate)
                {
                    var textOffset = part2Offset;
                    var textLength = part2Length;

                    *(TextLayoutTextCommand *)output.InternalObjectStream.Data = new TextLayoutTextCommand(textOffset, textLength,
                                                                                                           0, positionY + lineHeight, (Int16)sizeAfterBreak.Width, (Int16)sizeAfterBreak.Height);
                    output.InternalObjectStream.FinalizeObject(sizeof(TextLayoutTextCommand));
                }

                // Add the line break command to the broken line.
                AdvanceLineToNextCommand(0, 0, 1, 1);

                // Recalculate the parameters for the broken line.
                output.Seek(LineInfoCommandIndex + 1);

                var brokenLineWidth            = 0;
                var brokenLineHeight           = 0;
                var brokenLineLengthInText     = 0;
                var brokenLineLengthInCommands = 0;

                var cmdType = TextLayoutCommandType.None;

                while ((cmdType = *(TextLayoutCommandType *)output.Data) != TextLayoutCommandType.LineInfo)
                {
                    switch (cmdType)
                    {
                    case TextLayoutCommandType.Text:
                    {
                        var cmd = (TextLayoutTextCommand *)output.Data;
                        brokenLineWidth        += cmd->TextWidth;
                        brokenLineHeight        = Math.Max(brokenLineHeight, cmd->TextHeight);
                        brokenLineLengthInText += cmd->TextLength;
                    }
                    break;

                    case TextLayoutCommandType.Icon:
                    {
                        var cmd = (TextLayoutIconCommand *)output.Data;
                        brokenLineWidth        += cmd->Bounds.Width;
                        brokenLineHeight        = Math.Max(brokenLineHeight, cmd->Bounds.Height);
                        brokenLineLengthInText += 1;
                    }
                    break;

                    case TextLayoutCommandType.LineBreak:
                    {
                        var cmd = (TextLayoutLineBreakCommand *)output.Data;
                        brokenLineLengthInText += cmd->Length;
                    }
                    break;
                    }
                    brokenLineLengthInCommands++;
                    output.SeekNextCommand();
                }

                // Finalize the broken line.
                totalLength          = (totalLength - lineLengthInText) + brokenLineLengthInText;
                lineWidth            = brokenLineWidth;
                lineHeight           = brokenLineHeight;
                lineLengthInText     = brokenLineLengthInText;
                lineLengthInCommands = brokenLineLengthInCommands;
                FinalizeLine(output, ref settings);

                // Fixup token bounds and update parameters for new line.
                LineInfoCommandIndex = insertionPosition + 1;
                while (output.StreamPositionInObjects < output.Count)
                {
                    var width            = 0;
                    var height           = 0;
                    var lengthInCommands = 0;
                    var lengthInText     = 0;

                    switch (*(TextLayoutCommandType *)output.Data)
                    {
                    case TextLayoutCommandType.Text:
                    {
                        var cmd = (TextLayoutTextCommand *)output.Data;
                        width            = cmd->TextWidth;
                        height           = cmd->TextHeight;
                        lengthInCommands = 1;
                        lengthInText     = cmd->TextLength;
                        cmd->TextX       = PositionX;
                        cmd->TextY       = PositionY;
                    }
                    break;

                    case TextLayoutCommandType.Icon:
                    {
                        var cmd = (TextLayoutIconCommand *)output.Data;
                        width            = cmd->IconWidth;
                        height           = cmd->IconHeight;
                        lengthInCommands = 1;
                        lengthInText     = 1;
                        cmd->IconX       = PositionX;
                        cmd->IconY       = PositionY;
                    }
                    break;

                    case TextLayoutCommandType.LineBreak:
                    {
                        var cmd = (TextLayoutLineBreakCommand *)output.Data;
                        lengthInText += cmd->Length;
                    }
                    break;
                    }

                    AdvanceLineToNextCommand(width, height, lengthInCommands, lengthInText);
                    output.SeekNextCommand();
                }

                return(true);
            }
Пример #56
0
 /// <summary>
 /// Processes a parser token with type <see cref="TextParserTokenType.PushColor"/>.
 /// </summary>
 private void ProcessPushColorToken(TextLayoutCommandStream output,
     ref TextParserToken token, ref LayoutState state, ref Int32 index)
 {
     var pushedColor = ParseColor(token.Text);
     output.WritePushColor(new TextLayoutColorCommand(pushedColor));
     state.AdvanceLineToNextCommand();
     index++;
 }
Пример #57
0
        /// <summary>
        /// Registers the specified font with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterFontWithCommandStream(TextLayoutCommandStream output, StringSegment name, out SpriteFont font)
        {
            if (!registeredFonts.TryGetValue(name, out font))
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedFont.Format(name));

            return output.RegisterFont(name, font);
        }
Пример #58
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();
        }
Пример #59
0
        /// <summary>
        /// Updates the cache which contains the element's laid-out text.
        /// </summary>
        /// <param name="availableSize">The amount of space in which the element's text can be laid out.</param>
        private void UpdateTextLayoutCache(Size2D availableSize)
        {
            if (textLayoutCommands != null)
                textLayoutCommands.Clear();

            if (View == null || containingControl == null)
                return;

            var content = Content;

            var contentElement = content as UIElement;
            if (contentElement == null)
            {
                if (textLayoutCommands == null)
                    textLayoutCommands = new TextLayoutCommandStream();

                var font = containingControl.Font;
                if (font.IsLoaded)
                {
                    var availableSizeInPixels = Display.DipsToPixels(availableSize);

                    var settings = new TextLayoutSettings(font,
                        (Int32)Math.Ceiling(availableSizeInPixels.Width),
                        (Int32)Math.Ceiling(availableSizeInPixels.Height), TextFlags.Standard, containingControl.FontStyle);

                    View.Resources.TextRenderer.CalculateLayout(textParserResult, textLayoutCommands, settings);
                }
            }
        }
Пример #60
0
        /// <summary>
        /// Registers the specified glyph shader with the command stream and returns its resulting index.
        /// </summary>
        private Int16 RegisterGlyphShaderWithCommandStream(TextLayoutCommandStream output, StringSegment name, out GlyphShader glyphShader)
        {
            if (!registeredGlyphShaders.TryGetValue(name, out glyphShader))
                throw new InvalidOperationException(UltravioletStrings.UnrecognizedGlyphShader.Format(name));

            return output.RegisterGlyphShader(name, glyphShader);
        }