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; }
/// <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.PopLink"/>. /// </summary> private void ProcessPopLinkToken(TextLayoutCommandStream output, ref TextParserToken token, ref LayoutState state, ref Int32 index) { output.WritePopLink(); state.AdvanceLineToNextCommand(); index++; }
/// <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); } } }
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; }
/// <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; }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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)); }
/// <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)); }
/// <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); }
/// <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)); }
/// <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++; }
/// <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)); }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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; }
/// <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); }
/// <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; }
/// <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; }
/// <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); }
/// <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; }
/// <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); }
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; } } }
/// <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; }
/// <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); } }
/// <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; }
/// <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); }
/// <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); }
/// <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++; }
/// <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())); }
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"); }
/// <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(); }
/// <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); }
/// <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; }
/// <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> /// 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; }
/// <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); }
/// <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); }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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++; }
/// <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> /// 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++; }
/// <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); } } }
/// <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++; }
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); }
/// <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++; }
/// <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); }
/// <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(); }
/// <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); } } }
/// <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); }