protected override void OnDrawing(UltravioletTime time) { var window = Ultraviolet.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; stringFormatter.Reset(); stringFormatter.AddArgument(songPlayer.Position.Minutes); stringFormatter.AddArgument(songPlayer.Position.Seconds); stringFormatter.AddArgument(songPlayer.Duration.Minutes); stringFormatter.AddArgument(songPlayer.Duration.Seconds); stringFormatter.Format("{0:pad:2}:{1:pad:2} / {2:pad:2}:{3:pad:2}", stringBuffer); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var attribution = "|c:FFFFFF00|Now Playing|c|\n\n" + "\"|c:FFFFFF00|Deep Haze|c|\" by Kevin MacLeod (incompetech.com)\n" + "Licensed under Creative Commons: By Attribution 3.0\n" + "|c:FF808080|http://creativecommons.org/licenses/by/3.0/|c|\n\n\n"; var settings = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); textRenderer.CalculateLayout(attribution, textLayoutCommands, settings); textRenderer.Draw(spriteBatch, textLayoutCommands, Vector2.Zero, Color.White); var timerSize = spriteFont.Regular.MeasureString(stringBuffer); var timerPosition = new Vector2( (Int32)(textLayoutCommands.Bounds.Left + ((textLayoutCommands.Bounds.Width - timerSize.Width) / 2f)), (Int32)(textLayoutCommands.Bounds.Bottom - timerSize.Height)); spriteBatch.DrawString(spriteFont.Regular, stringBuffer, timerPosition, Color.White); spriteBatch.End(); base.OnDrawing(time); }
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 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); } } }
/// <summary> /// Gets the currently active font. /// </summary> private SpriteFont GetCurrentFont(ref TextLayoutSettings settings, Boolean bold, Boolean italic, out SpriteFontFace face) { var font = (fontStack.Count == 0) ? settings.Font : fontStack.Peek().Value; face = font.GetFace(bold, italic); return(font); }
/// <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; }
protected override void OnDrawingForeground(UltravioletTime time, SpriteBatch spriteBatch) { spriteBatch.Draw(blankTexture, new RectangleF(64, 64, Width - 128, Height - 128), Color.Black * 0.75f * TransitionPosition); var settings = new TextLayoutSettings(font, Width, Height, TextFlags.AlignCenter | TextFlags.AlignMiddle); textRenderer.Draw(spriteBatch, "Welcome to the game!", Vector2.Zero, Color.White * TransitionPosition, settings); base.OnDrawingForeground(time, spriteBatch); }
public void DrawObject(SpriteBatch batch, bool debug = false, TextRenderer rend = null, TextLayoutSettings settings = new TextLayoutSettings()) { if(debug) { if (rend != null) { rend.Draw(batch, "yo", body2D.GetPosition().ToScreenVector(), TwistedLogik.Ultraviolet.Color.Gold, settings); } else { Console.WriteLine("Hey yo you didn't give me a TextRenderer"); } } }
protected override void OnDrawingForeground(UltravioletTime time, SpriteBatch spriteBatch) { spriteBatch.Draw(blankTexture, new RectangleF(0, 0, Width, Height), new Color(180, 0, 0)); #if ANDROID || IOS var text = "This is SampleScreen1\nTap to open SampleScreen2"; #else var text = "This is SampleScreen1\nPress right arrow key to open SampleScreen2"; #endif var settings = new TextLayoutSettings(font, Width, Height, TextFlags.AlignCenter | TextFlags.AlignMiddle); textRenderer.Draw(spriteBatch, text, Vector2.Zero, Color.White * TransitionPosition, settings); base.OnDrawingForeground(time, spriteBatch); }
protected override void OnDrawingForeground(UltravioletTime time, SpriteBatch spriteBatch) { var offset = GetScreenOffset(); spriteBatch.Draw(blankTexture, new RectangleF(offset, 0, Width, Height), new Color(0, 0, 180)); #if ANDROID var text = "This is SampleScreen2\nTap to open SampleScreen1"; #else var text = "This is SampleScreen2\nPress left arrow key to open SampleScreen1"; #endif var settings = new TextLayoutSettings(font, Width, Height, TextFlags.AlignCenter | TextFlags.AlignMiddle); textRenderer.Draw(spriteBatch, text, new Vector2(offset, 0), Color.White, settings); base.OnDrawingForeground(time, spriteBatch); }
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; }
private void DrawAlignedText() { var window = Ultraviolet.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; var settingsTopLeft = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignTop | TextFlags.AlignLeft); textRenderer.Draw(spriteBatch, "Aligned top left", Vector2.Zero, Color.White, settingsTopLeft); var settingsTopCenter = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignTop | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, "Aligned top center", Vector2.Zero, Color.White, settingsTopCenter); var settingsTopRight = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignTop | TextFlags.AlignRight); textRenderer.Draw(spriteBatch, "Aligned top right", Vector2.Zero, Color.White, settingsTopRight); var settingsBottomLeft = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignBottom | TextFlags.AlignLeft); textRenderer.Draw(spriteBatch, "Aligned bottom left", Vector2.Zero, Color.White, settingsBottomLeft); var settingsBottomCenter = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignBottom | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, "Aligned bottom center", Vector2.Zero, Color.White, settingsBottomCenter); var settingsBottomRight = new TextLayoutSettings(spriteFontSegoe, width, height, TextFlags.AlignBottom | TextFlags.AlignRight); textRenderer.Draw(spriteBatch, "Aligned bottom right", Vector2.Zero, Color.White, settingsBottomRight); }
/// <summary> /// Gets the currently active font. /// </summary> private SpriteFont GetCurrentFont(ref TextLayoutSettings settings, Boolean bold, Boolean italic, out SpriteFontFace face) { var font = (fontStack.Count == 0) ? settings.Font : fontStack.Peek().Value; face = font.GetFace(bold, italic); return font; }
/// <summary> /// Advances the layout state to the next line of text. /// </summary> /// <param name="output">The <see cref="TextLayoutCommandStream"/> which is being populated.</param> /// <param name="settings">The current layout settings.</param> public void AdvanceLayoutToNextLine(TextLayoutCommandStream output, ref TextLayoutSettings settings) { FinalizeLine(output, ref settings); output.WriteLineInfo(); }
/// <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); }
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; } }
public void TextRenderer_CanAlignTextWithinAnArea() { var spriteBatch = default(SpriteBatch); var spriteFont = default(SpriteFont); var textRenderer = default(TextRenderer); var result = GivenAnUltravioletApplication() .WithContent(content => { spriteBatch = SpriteBatch.Create(); spriteFont = content.Load<SpriteFont>("Fonts/SegoeUI"); textRenderer = new TextRenderer(); }) .Render(uv => { var window = uv.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settingsTopLeft = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignTop | TextFlags.AlignLeft); textRenderer.Draw(spriteBatch, "Aligned top left", Vector2.Zero, Color.White, settingsTopLeft); var settingsTopCenter = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignTop | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, "Aligned top center", Vector2.Zero, Color.White, settingsTopCenter); var settingsTopRight = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignTop | TextFlags.AlignRight); textRenderer.Draw(spriteBatch, "Aligned top right", Vector2.Zero, Color.White, settingsTopRight); var settingsMiddleLeft = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignLeft); textRenderer.Draw(spriteBatch, "Aligned middle left", Vector2.Zero, Color.White, settingsMiddleLeft); var settingsMiddleCenter = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, "Aligned middle center", Vector2.Zero, Color.White, settingsMiddleCenter); var settingsMiddleRight = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignRight); textRenderer.Draw(spriteBatch, "Aligned middle right", Vector2.Zero, Color.White, settingsMiddleRight); var settingsBottomLeft = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignBottom | TextFlags.AlignLeft); textRenderer.Draw(spriteBatch, "Aligned bottom left", Vector2.Zero, Color.White, settingsBottomLeft); var settingsBottomCenter = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignBottom | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, "Aligned bottom center", Vector2.Zero, Color.White, settingsBottomCenter); var settingsBottomRight = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignBottom | TextFlags.AlignRight); textRenderer.Draw(spriteBatch, "Aligned bottom right", Vector2.Zero, Color.White, settingsBottomRight); spriteBatch.End(); }); TheResultingImage(result) .ShouldMatch(@"Resources\Expected\Graphics\Graphics2D\Text\TextRenderer_CanAlignTextWithinAnArea.png"); }
public void TextRenderer_CanRenderColoredStrings() { var spriteBatch = default(SpriteBatch); var spriteFont = default(SpriteFont); var textRenderer = default(TextRenderer); var result = GivenAnUltravioletApplication() .WithContent(content => { spriteBatch = SpriteBatch.Create(); spriteFont = content.Load<SpriteFont>("Fonts/SegoeUI"); textRenderer = new TextRenderer(); }) .Render(uv => { spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settings = new TextLayoutSettings(spriteFont, null, null, TextFlags.Standard); textRenderer.Draw(spriteBatch, "Hello, |c:FFFF0000|world|c|! This is a |c:FF00FF00|colored|c| |c:FF0000FF|string|c|!", Vector2.Zero, Color.White, settings); spriteBatch.End(); }); TheResultingImage(result) .ShouldMatch(@"Resources\Expected\Graphics\Graphics2D\Text\TextRenderer_CanRenderColoredStrings.png"); }
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; }
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; } } }
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); }
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 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"); }
protected override void OnDrawing(UltravioletTime time) { spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settings = new TextLayoutSettings(spriteFont, null, null, TextFlags.Standard); if (Ultraviolet.Platform == UltravioletPlatform.Android || Ultraviolet.Platform == UltravioletPlatform.iOS) { textRenderer.Draw(spriteBatch, "Tap the screen to reset the scrolling text.", Vector2.One * 8f, Color.White, settings); } else { textRenderer.Draw(spriteBatch, $"Press {Ultraviolet.GetInput().GetActions().ResetScrollingText.Primary} to reset the scrolling text.", Vector2.One * 8f, Color.White, settings); } var window = Ultraviolet.GetPlatform().Windows.GetPrimary(); var x = (window.DrawableSize.Width - textBlock.Width.Value) / 2; var y = (window.DrawableSize.Height - textBlock.Height.Value) / 2; textBlock.Draw(time, spriteBatch, new Vector2(x, y), Color.White); spriteBatch.End(); base.OnDrawing(time); }
/// <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 text layout stream. /// </summary> private void UpdateTextLayoutStream(Size2D availableSize) { pendingTextLayout = false; textLayoutStream.Clear(); if (View == null) return; if (textParserStream.Count == 0) { UpdateSelectionAndCaret(); return; } var owner = TemplatedParent as Control; if (owner == null || !owner.Font.IsLoaded) return; var textFlags = TextFlags.AlignTop; var textWrapping = (owner == null) ? TextWrapping.NoWrap : owner.GetValue<TextWrapping>(TextBox.TextWrappingProperty); var textAlignment = (owner == null) ? TextAlignment.Left : owner.GetValue<TextAlignment>(TextBox.TextAlignmentProperty); var layoutWidth = (textWrapping == TextWrapping.Wrap) ? (Int32?)Display.DipsToPixels(availableSize.Width) : null; var layoutHeight = (Int32?)null; switch (textAlignment) { case TextAlignment.Left: textFlags |= TextFlags.AlignLeft; break; case TextAlignment.Center: textFlags |= TextFlags.AlignCenter; break; case TextAlignment.Right: textFlags |= TextFlags.AlignRight; break; } var settings = new TextLayoutSettings(owner.Font, layoutWidth, layoutHeight, textFlags); View.Resources.TextRenderer.CalculateLayout(textParserStream, textLayoutStream, settings); UpdateSelectionAndCaret(); }
public void TextRenderer_CorrectlyCalculatesBoundingBoxOfFormattedText() { var spriteBatch = default(SpriteBatch); var spriteFont = default(SpriteFont); var textRenderer = default(TextRenderer); var blankTexture = default(Texture2D); var result = GivenAnUltravioletApplication() .WithContent(content => { spriteBatch = SpriteBatch.Create(); spriteFont = content.Load<SpriteFont>("Fonts/Garamond"); textRenderer = new TextRenderer(); blankTexture = Texture2D.Create(1, 1); blankTexture.SetData(new[] { Color.White }); }) .Render(uv => { const string text = "Lorem ipsum dolor sit amet,\n" + "|b|consectetur adipiscing elit.|b|\n" + "\n" + "|i|Pellentesque egestas luctus sapien|i|\n" + "|b||i|in malesuada.|i||b|\n" + "\n"; var window = uv.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; uv.GetGraphics().Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settings = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); var bounds = textRenderer.Draw(spriteBatch, text, Vector2.Zero, Color.White, settings); spriteBatch.Draw(blankTexture, bounds, Color.Red * 0.5f); spriteBatch.End(); }); TheResultingImage(result) .ShouldMatch(@"Resources\Expected\Graphics\Graphics2D\Text\TextRenderer_CorrectlyCalculatesBoundingBoxOfFormattedText.png"); }
/// <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); }
public void TextRenderer_CanRenderStyledStrings() { var spriteBatch = default(SpriteBatch); var spriteFont = default(SpriteFont); var textRenderer = default(TextRenderer); var result = GivenAnUltravioletApplication() .WithContent(content => { spriteBatch = SpriteBatch.Create(); spriteFont = content.Load<SpriteFont>("Fonts/Garamond"); textRenderer = new TextRenderer(); }) .Render(uv => { spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settings = new TextLayoutSettings(spriteFont, null, null, TextFlags.Standard); textRenderer.Draw(spriteBatch, "This string is regular\n" + "|b|This string is bold|b|\n" + "|i|This string is italic|i|\n" + "|b||i|This string is bold italic|i||b|", Vector2.Zero, Color.White, settings); spriteBatch.End(); }); TheResultingImage(result) .ShouldMatch(@"Resources\Expected\Graphics\Graphics2D\Text\TextRenderer_CanRenderStyledStrings.png"); }
/// <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); } } }
public void TextRenderer_CorrectlyAlignsKernedTextAcrossTokenBoundaries() { var spriteBatch = default(SpriteBatch); var spriteFont = default(SpriteFont); var textRenderer = default(TextRenderer); var result = GivenAnUltravioletApplication() .WithContent(content => { spriteBatch = SpriteBatch.Create(); spriteFont = content.Load<SpriteFont>("Fonts/Garamond"); textRenderer = new TextRenderer(); }) .Render(uv => { const string text = "||c:AARRGGBB| - Changes the color of text.\n" + "|c:FFFF0000|red|c| |c:FFFF8000|orange|c| |c:FFFFFF00|yellow|c| |c:FF00FF00|green|c| |c:FF0000FF|blue|c| |c:FF6F00FF|indigo|c| |c:FFFF00FF|magenta|c|"; var window = uv.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var settings = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); textRenderer.Draw(spriteBatch, text, Vector2.Zero, Color.White, settings); spriteBatch.End(); }); TheResultingImage(result) .ShouldMatch(@"Resources\Expected\Graphics\Graphics2D\Text\TextRenderer_CorrectlyAlignsKernedTextAcrossTokenBoundaries.png"); }
protected override void OnDrawing(UltravioletTime time) { var window = Ultraviolet.GetPlatform().Windows.GetPrimary(); var width = window.ClientSize.Width; var height = window.ClientSize.Height; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); var attribution = #if ANDROID "|c:FFFFFF00|Tap the screen|c| to activate one of the sound effect players.\n\n" + #else "Press the |c:FFFFFF00|1-8 number keys|c| to activate one of the sound effect players.\n\n" + #endif "\"|c:FFFFFF00|grenade.wav|c|\" by ljudman (http://freesound.org/people/ljudman)\n" + "Licensed under Creative Commons: Sampling+\n" + "|c:FF808080|http://creativecommons.org/licenses/sampling+/1.0/|c|"; var settings = new TextLayoutSettings(spriteFont, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); textRenderer.CalculateLayout(attribution, textLayoutCommands, settings); textRenderer.Draw(spriteBatch, textLayoutCommands, Vector2.Zero, Color.White); spriteBatch.End(); base.OnDrawing(time); }
private void DrawGamePadState(Int32 playerIndex, Rectangle area) { var input = Ultraviolet.GetInput(); var device = input.GetGamePadForPlayer(playerIndex); var font = content.Load<SpriteFont>(GlobalFontID.SegoeUI); var x = area.X; var y = area.Y; var textArea = RectangleF.Empty; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise); textFormatter.Reset(); textFormatter.AddArgument(playerIndex + 1); textFormatter.AddArgument(device == null ? "(not connected)" : device.Name); textFormatter.AddArgument((device == null) ? "" : device.GetButtonState(TwistedLogik.Ultraviolet.Input.GamePadButton.A).ToString()); textFormatter.Format("|c:FFFFFF00|Player {0}|c|\n{1}", textBuffer); var headerSettings = new TextLayoutSettings(font, area.Width, area.Height, TextFlags.AlignCenter | TextFlags.AlignTop); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, headerSettings); y += (Int32)textArea.Height + font.Regular.LineSpacing; if (device != null) { textFormatter.Reset(); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.LeftShoulder) ? "LeftShoulder" : "LeftShoulderDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.RightShoulder) ? "RightShoulder" : "RightShoulderDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.A) ? "AButton" : "AButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.B) ? "BButton" : "BButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.X) ? "XButton" : "XButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.Y) ? "YButton" : "YButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.Back) ? "BackButton" : "BackButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.Start) ? "StartButton" : "StartButtonDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.DPadUp) ? "DPadUp" : "DPadUpDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.DPadDown) ? "DPadDown" : "DPadDownDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.DPadLeft) ? "DPadLeft" : "DPadLeftDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.DPadRight) ? "DPadRight" : "DPadRightDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.LeftStick) ? "LeftJoystick" : "LeftJoystickDisabled"); textFormatter.AddArgument(device.IsButtonDown(GamePadButton.RightStick) ? "RightJoystick" : "RightJoystickDisabled"); textFormatter.Format( "|c:FFFFFF00|Buttons|c|\n\n" + "|icon:{0}| |icon:{1}|\n" + "|icon:{2}| |icon:{3}| |icon:{4}| |icon:{5}|\n" + "|icon:{6}| |icon:{7}|\n" + "|icon:{8}| |icon:{9}| |icon:{10}| |icon:{11}|\n" + "|icon:{12}| |icon:{13}|\n\n" + "|c:FFFFFF00|Axes|c|", textBuffer); var buttonSettings = new TextLayoutSettings(font, area.Width, area.Height, TextFlags.AlignCenter | TextFlags.AlignTop); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, buttonSettings); y += (Int32)textArea.Height + font.Regular.LineSpacing; var axesLeftSettings = new TextLayoutSettings(font, area.Width, area.Height, TextFlags.AlignLeft | TextFlags.AlignTop); var axesRightSettings = new TextLayoutSettings(font, area.Width, area.Height, TextFlags.AlignRight | TextFlags.AlignTop); textFormatter.Reset(); textFormatter.AddArgument(device.LeftTrigger); textFormatter.Format("|icon:LeftTrigger|{0:decimals:2}", textBuffer); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, axesLeftSettings); textFormatter.Reset(); textFormatter.AddArgument(device.RightTrigger); textFormatter.Format("{0:decimals:2}|icon:RightTrigger|", textBuffer); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, axesRightSettings); y += (Int32)textArea.Height; textFormatter.Reset(); textFormatter.AddArgument(device.LeftJoystickX); textFormatter.AddArgument(device.LeftJoystickY); textFormatter.Format("|icon:LeftJoystick|\nX={0:decimals:2}\nY={1:decimals:2}", textBuffer); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, axesLeftSettings); textFormatter.Reset(); textFormatter.AddArgument(device.RightJoystickX); textFormatter.AddArgument(device.RightJoystickY); textFormatter.Format("|icon:RightJoystick|\nX={0:decimals:2}\nY={1:decimals:2}", textBuffer); textArea = textRenderer.Draw(spriteBatch, textBuffer, new Vector2(x, y), Color.White, axesRightSettings); } spriteBatch.End(); }
/// <summary> /// Performs layout calculations for this cell. /// </summary> /// <param name="renderer">The text renderer used to lay out and render the table's text.</param> /// <param name="width">The cell's width in pixels.</param> /// <param name="height">The cell's height in pixels.</param> internal void PerformLayout(TextRenderer renderer, Int32 width, Int32 height) { var settings = new TextLayoutSettings(row.Table.Font, width, null, textFlags); renderer.CalculateLayout(FormattedText, layout, settings); }
/// <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> /// 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> /// 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> /// Called when the scene is being rendered. /// </summary> /// <param name="time">Time elapsed since the last call to Draw.</param> protected override void OnDrawing(UltravioletTime time) { spriteBatch.Begin(); textFormatter.Reset(); textFormatter.AddArgument(Ultraviolet.GetGraphics().FrameRate); textFormatter.AddArgument(GC.GetTotalMemory(false) / 1024); textFormatter.AddArgument(Environment.Is64BitProcess ? "64-bit" : "32-bit"); textFormatter.Format("{0:decimals:2} FPS\nAllocated: {1:decimals:2} kb\n{2}", textBuffer); spriteBatch.DrawString(spriteFont, textBuffer, Vector2.One * 8f, Color.White); var size = Ultraviolet.GetPlatform().Windows.GetCurrent().Compositor.Size; var settings = new TextLayoutSettings(spriteFont, size.Width, size.Height, TextFlags.AlignCenter | TextFlags.AlignMiddle); textRenderer.Draw(spriteBatch, "Welcome to the |c:FFFF00C0|Ultraviolet Framework|c|!", Vector2.Zero, Color.White, settings); spriteBatch.End(); base.OnDrawing(time); }
/// <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> /// 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(); }
private void DrawColoredAndStyledText() { var window = Ultraviolet.GetPlatform().Windows.GetPrimary(); var width = window.DrawableSize.Width; var height = window.DrawableSize.Height; if (textLayoutCommands.Settings.Width != width || textLayoutCommands.Settings.Height != height) { const string text = "Ultraviolet Formatting Commands\n" + "\n" + "||c:AARRGGBB| - Changes the color of text.\n" + "|c:FFFF0000|red|c| |c:FFFF8000|orange|c| |c:FFFFFF00|yellow|c| |c:FF00FF00|green|c| |c:FF0000FF|blue|c| |c:FF6F00FF|indigo|c| |c:FFFF00FF|magenta|c|\n" + "\n" + "||font:name| - Changes the current font.\n" + "We can |font:segoe|transition to a completely different font|font| within a single line\n" + "\n" + "||b| and ||i| - Changes the current font style.\n" + "|b|bold|b| |i|italic|i| |b||i|bold italic|i||b|\n" + "\n" + "||style:name| - Changes to a preset style.\n" + "|style:preset1|this is preset1|style| |style:preset2|this is preset2|style|\n" + "\n" + "||icon:name| - Draws an icon in the text.\n" + "[|icon:ok| OK] [|icon:cancel| Cancel]"; var settings = new TextLayoutSettings(spriteFontGaramond, width, height, TextFlags.AlignMiddle | TextFlags.AlignCenter); textRenderer.CalculateLayout(text, textLayoutCommands, settings); } textRenderer.Draw(spriteBatch, textLayoutCommands, Vector2.Zero, Color.White); }
/// <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> /// Updates the cache that contains the result of laying out the label's text. /// </summary> /// <param name="availableSize">The size of the space that is available for laying out text.</param> private void UpdateTextLayoutResult(Size2D availableSize) { textLayoutCommands.Clear(); if (textParserResult.Count > 0 && Font.IsLoaded) { var unconstrainedWidth = Double.IsPositiveInfinity(availableSize.Width) && HorizontalAlignment != HorizontalAlignment.Stretch; var unconstrainedHeight = Double.IsPositiveInfinity(availableSize.Height) && VerticalAlignment != VerticalAlignment.Stretch; var constraintX = unconstrainedWidth ? null : (Int32?)Math.Ceiling(Display.DipsToPixels(availableSize.Width)); var constraintY = unconstrainedHeight ? null : (Int32?)Math.Ceiling(Display.DipsToPixels(availableSize.Height)); var flags = LayoutUtil.ConvertAlignmentsToTextFlags(HorizontalContentAlignment, VerticalContentAlignment); var settings = new TextLayoutSettings(Font, constraintX, constraintY, flags, FontStyle); View.Resources.TextRenderer.CalculateLayout(textParserResult, textLayoutCommands, settings); } }
/// <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> /// 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> /// 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; }