mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-24 21:48:35 +01:00
Compare commits
4 commits
ad1d6a864e
...
a11a63c067
Author | SHA1 | Date | |
---|---|---|---|
a11a63c067 | |||
f445aba45c | |||
293602269b | |||
53b93a34f8 |
12 changed files with 202 additions and 84 deletions
|
@ -23,6 +23,7 @@ Improvements
|
||||||
- Added Padding.Empty
|
- Added Padding.Empty
|
||||||
- Throw an exception when text formatter macros resolve recursively too many times
|
- Throw an exception when text formatter macros resolve recursively too many times
|
||||||
- Allow using StaticSpriteBatch for AutoTiling
|
- Allow using StaticSpriteBatch for AutoTiling
|
||||||
|
- Made TextFormatter string size based on the currently active font rather than the default one
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
- Fixed some end-of-line inconsistencies when using the Right text alignment
|
- Fixed some end-of-line inconsistencies when using the Right text alignment
|
||||||
|
@ -31,6 +32,7 @@ Fixes
|
||||||
Additions
|
Additions
|
||||||
- Allow specifying a maximum amount of characters for a TextField
|
- Allow specifying a maximum amount of characters for a TextField
|
||||||
- Added a multiline editing mode to TextField
|
- Added a multiline editing mode to TextField
|
||||||
|
- Added a formatting code to allow for inline font changes
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
- *Made Image ScaleToImage take ui scale into account*
|
- *Made Image ScaleToImage take ui scale into account*
|
||||||
|
|
|
@ -34,6 +34,13 @@
|
||||||
/processorParam:TextureFormat=Compressed
|
/processorParam:TextureFormat=Compressed
|
||||||
/build:Fonts/TestFontItalic.spritefont
|
/build:Fonts/TestFontItalic.spritefont
|
||||||
|
|
||||||
|
#begin Fonts/MonospacedFont.spritefont
|
||||||
|
/importer:FontDescriptionImporter
|
||||||
|
/processor:FontDescriptionProcessor
|
||||||
|
/processorParam:PremultiplyAlpha=True
|
||||||
|
/processorParam:TextureFormat=Compressed
|
||||||
|
/build:Fonts/MonospacedFont.spritefont
|
||||||
|
|
||||||
#begin Textures/Anim.png
|
#begin Textures/Anim.png
|
||||||
/importer:TextureImporter
|
/importer:TextureImporter
|
||||||
/processor:TextureProcessor
|
/processor:TextureProcessor
|
||||||
|
|
BIN
Demos/Content/Fonts/JetBrainsMono-Regular.ttf
Normal file
BIN
Demos/Content/Fonts/JetBrainsMono-Regular.ttf
Normal file
Binary file not shown.
60
Demos/Content/Fonts/MonospacedFont.spritefont
Normal file
60
Demos/Content/Fonts/MonospacedFont.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
This file contains an xml description of a font, and will be read by the XNA
|
||||||
|
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||||
|
of the font in your game, and to change the characters which are available to draw
|
||||||
|
with.
|
||||||
|
-->
|
||||||
|
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||||
|
<Asset Type="Graphics:FontDescription">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Modify this string to change the font that will be imported.
|
||||||
|
-->
|
||||||
|
<FontName>JetBrainsMono-Regular.ttf</FontName>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Size is a float value, measured in points. Modify this value to change
|
||||||
|
the size of the font.
|
||||||
|
-->
|
||||||
|
<Size>32</Size>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Spacing is a float value, measured in pixels. Modify this value to change
|
||||||
|
the amount of spacing in between characters.
|
||||||
|
-->
|
||||||
|
<Spacing>0</Spacing>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||||
|
will be used when placing characters.
|
||||||
|
-->
|
||||||
|
<UseKerning>true</UseKerning>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||||
|
and "Bold, Italic", and are case sensitive.
|
||||||
|
-->
|
||||||
|
<Style>Regular</Style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If you uncomment this line, the default character will be substituted if you draw
|
||||||
|
or measure text that contains characters which were not included in the font.
|
||||||
|
-->
|
||||||
|
<DefaultCharacter>*</DefaultCharacter>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
CharacterRegions control what letters are available in the font. Every
|
||||||
|
character from Start to End will be built and made available for drawing. The
|
||||||
|
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||||
|
character set. The characters are ordered according to the Unicode standard.
|
||||||
|
See the documentation for more information.
|
||||||
|
-->
|
||||||
|
<CharacterRegions>
|
||||||
|
<CharacterRegion>
|
||||||
|
<Start> </Start>
|
||||||
|
<End>ɏ</End>
|
||||||
|
</CharacterRegion>
|
||||||
|
</CharacterRegions>
|
||||||
|
</Asset>
|
||||||
|
</XnaContent>
|
|
@ -47,7 +47,8 @@ namespace Demos {
|
||||||
CheckboxTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
CheckboxTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
||||||
CheckboxCheckmark = new TextureRegion(this.testTexture, 24, 0, 8, 8),
|
CheckboxCheckmark = new TextureRegion(this.testTexture, 24, 0, 8, 8),
|
||||||
RadioTexture = new NinePatch(new TextureRegion(this.testTexture, 16, 0, 8, 8), 3),
|
RadioTexture = new NinePatch(new TextureRegion(this.testTexture, 16, 0, 8, 8), 3),
|
||||||
RadioCheckmark = new TextureRegion(this.testTexture, 32, 0, 8, 8)
|
RadioCheckmark = new TextureRegion(this.testTexture, 32, 0, 8, 8),
|
||||||
|
AdditionalFonts = {{"Monospaced", new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/MonospacedFont"))}}
|
||||||
};
|
};
|
||||||
var untexturedStyle = new UntexturedStyle(this.SpriteBatch) {
|
var untexturedStyle = new UntexturedStyle(this.SpriteBatch) {
|
||||||
TextScale = style.TextScale,
|
TextScale = style.TextScale,
|
||||||
|
@ -87,7 +88,7 @@ namespace Demos {
|
||||||
this.root.AddChild(new VerticalSpace(3));
|
this.root.AddChild(new VerticalSpace(3));
|
||||||
|
|
||||||
// a paragraph with formatting codes. To see them all or to add more, check the TextFormatting class
|
// a paragraph with formatting codes. To see them all or to add more, check the TextFormatting class
|
||||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain <c Blue>formatting codes</c>, including colors and <i>text styles</i>. The names of all <c Orange>MonoGame Colors</c> can be used, as well as the codes <i>Italic</i>, <b>Bold</b>, <s>Drop Shadow'd</s> and <s><c Pink>mixed formatting</s></c>. \n<i>Even <c #ff611f82>inline custom colors</c> work!</i>"));
|
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain <c Blue>formatting codes</c>, including colors and <i>text styles</i>. The names of all <c Orange>MonoGame Colors</c> can be used, as well as the codes <i>Italic</i>, <b>Bold</b>, <s>Drop Shadow'd</s> and <s><c Pink>mixed formatting</s></c>. You can also add additional fonts for things like\n<f Monospaced>void Code() {\n // Code\n}</f>\n<i>Even <c #ff611f82>inline custom colors</c> work!</i>"));
|
||||||
|
|
||||||
// adding some custom image formatting codes
|
// adding some custom image formatting codes
|
||||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain <i Grass> images and more!"));
|
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain <i Grass> images and more!"));
|
||||||
|
|
|
@ -4,7 +4,7 @@ The **MLEM** package contains a simple text formatting system that supports colo
|
||||||
|
|
||||||
Text formatting makes use of [generic fonts](font_extensions.md).
|
Text formatting makes use of [generic fonts](font_extensions.md).
|
||||||
|
|
||||||
It should also be noted that [MLEM.Ui](ui.md)'s `Paragraph`s support text formatting out of the box.
|
It should also be noted that [MLEM.Ui](ui.md)'s `Paragraph` supports text formatting out of the box.
|
||||||
|
|
||||||
## Formatting codes
|
## Formatting codes
|
||||||
To format your text, you can insert *formatting codes* into it. Almost all of these codes are single letters surrounded by `<>`, and some formatting codes can accept additional parameters after their letter representation.
|
To format your text, you can insert *formatting codes* into it. Almost all of these codes are single letters surrounded by `<>`, and some formatting codes can accept additional parameters after their letter representation.
|
||||||
|
@ -16,6 +16,10 @@ By default, the following formatting options are available:
|
||||||
- Underlined and strikethrough text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`.
|
- Underlined and strikethrough text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`.
|
||||||
- A wobbly sine wave animation using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`.
|
- A wobbly sine wave animation using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`.
|
||||||
|
|
||||||
|
When using [MLEM.Ui](ui.md)'s `Paragraph`, these additional formatting options are available by default:
|
||||||
|
- Hoverable and clickable links using `<l Url>`. Note that this code does not automatically change the color of the text. Reset using `</l>`.
|
||||||
|
- Inline font changes using `<f FontName>`, with custom fonts gathered from `UiStyle.AdditionalFonts`. Reset using `</f>`.
|
||||||
|
|
||||||
## Getting your text ready
|
## Getting your text ready
|
||||||
To get your text ready for rendering with formatting codes, it has to be tokenized. For that, you need to create a new text formatter first. Additionally, you need to have a [generic font](font_extensions.md) ready:
|
To get your text ready for rendering with formatting codes, it has to be tokenized. For that, you need to create a new text formatter first. Additionally, you need to have a [generic font](font_extensions.md) ready:
|
||||||
```cs
|
```cs
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MLEM.Font;
|
using MLEM.Font;
|
||||||
using MLEM.Formatting;
|
using MLEM.Formatting;
|
||||||
|
@ -194,6 +195,10 @@ namespace MLEM.Ui.Style {
|
||||||
/// Note that this sound is only played if the callbacks have any subscribers.
|
/// Note that this sound is only played if the callbacks have any subscribers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SoundEffectInfo ActionSound;
|
public SoundEffectInfo ActionSound;
|
||||||
|
/// <summary>
|
||||||
|
/// A set of additional fonts that can be used for the <c><f FontName></c> formatting code
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, GenericFont> AdditionalFonts = new Dictionary<string, GenericFont>();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -223,6 +223,8 @@ namespace MLEM.Ui {
|
||||||
this.TextFormatter = new TextFormatter();
|
this.TextFormatter = new TextFormatter();
|
||||||
this.TextFormatter.Codes.Add(new Regex("<l(?: ([^>]+))?>"), (f, m, r) => new LinkCode(m, r, 1 / 16F, 0.85F,
|
this.TextFormatter.Codes.Add(new Regex("<l(?: ([^>]+))?>"), (f, m, r) => new LinkCode(m, r, 1 / 16F, 0.85F,
|
||||||
t => this.Controls.MousedElement is Paragraph.Link l1 && l1.Token == t || this.Controls.TouchedElement is Paragraph.Link l2 && l2.Token == t));
|
t => this.Controls.MousedElement is Paragraph.Link l1 && l1.Token == t || this.Controls.TouchedElement is Paragraph.Link l2 && l2.Token == t));
|
||||||
|
this.TextFormatter.Codes.Add(new Regex("<f ([^>]+)>"), (_, m, r) => new FontCode(m, r,
|
||||||
|
f => this.Style.AdditionalFonts != null && this.Style.AdditionalFonts.TryGetValue(m.Groups[1].Value, out var c) ? c : f));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
@ -15,17 +16,17 @@ namespace MLEM.Font {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This field holds the unicode representation of a one em space.
|
/// This field holds the unicode representation of a one em space.
|
||||||
/// This is a character that isn't drawn, but has the same width as <see cref="LineHeight"/>.
|
/// This is a character that isn't drawn, but has the same width as <see cref="LineHeight"/>.
|
||||||
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>.
|
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString(string,bool)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const char OneEmSpace = '\u2003';
|
public const char OneEmSpace = '\u2003';
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This field holds the unicode representation of a non-breaking space.
|
/// This field holds the unicode representation of a non-breaking space.
|
||||||
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>.
|
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString(string,bool)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const char Nbsp = '\u00A0';
|
public const char Nbsp = '\u00A0';
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This field holds the unicode representation of a zero-width space.
|
/// This field holds the unicode representation of a zero-width space.
|
||||||
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations and string splitting, generic fonts implicitly support it in <see cref="MeasureString"/> and <see cref="SplitString"/>.
|
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations and string splitting, generic fonts implicitly support it in <see cref="MeasureString(string,bool)"/> and <see cref="SplitString"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const char Zwsp = '\u200B';
|
public const char Zwsp = '\u200B';
|
||||||
|
|
||||||
|
@ -46,8 +47,8 @@ namespace MLEM.Font {
|
||||||
public abstract float LineHeight { get; }
|
public abstract float LineHeight { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Measures the width of the given character with the default scale for use in <see cref="MeasureString"/>.
|
/// Measures the width of the given character with the default scale for use in <see cref="MeasureString(string,bool)"/>.
|
||||||
/// Note that this method does not support <see cref="Nbsp"/>, <see cref="Zwsp"/> and <see cref="OneEmSpace"/> for most generic fonts, which is why <see cref="MeasureString"/> should be used even for single characters.
|
/// Note that this method does not support <see cref="Nbsp"/>, <see cref="Zwsp"/> and <see cref="OneEmSpace"/> for most generic fonts, which is why <see cref="MeasureString(string,bool)"/> should be used even for single characters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="c">The character whose width to calculate</param>
|
/// <param name="c">The character whose width to calculate</param>
|
||||||
/// <returns>The width of the given character with the default scale</returns>
|
/// <returns>The width of the given character with the default scale</returns>
|
||||||
|
@ -88,44 +89,7 @@ namespace MLEM.Font {
|
||||||
/// <param name="ignoreTrailingSpaces">Whether trailing whitespace should be ignored in the returned size, causing the end of each line to be effectively trimmed</param>
|
/// <param name="ignoreTrailingSpaces">Whether trailing whitespace should be ignored in the returned size, causing the end of each line to be effectively trimmed</param>
|
||||||
/// <returns>The size of the string when drawn with this font</returns>
|
/// <returns>The size of the string when drawn with this font</returns>
|
||||||
public Vector2 MeasureString(string text, bool ignoreTrailingSpaces = false) {
|
public Vector2 MeasureString(string text, bool ignoreTrailingSpaces = false) {
|
||||||
var size = Vector2.Zero;
|
return this.MeasureString(text, ignoreTrailingSpaces, null);
|
||||||
if (text.Length <= 0)
|
|
||||||
return size;
|
|
||||||
var xOffset = 0F;
|
|
||||||
for (var i = 0; i < text.Length; i++) {
|
|
||||||
switch (text[i]) {
|
|
||||||
case '\n':
|
|
||||||
xOffset = 0;
|
|
||||||
size.Y += this.LineHeight;
|
|
||||||
break;
|
|
||||||
case OneEmSpace:
|
|
||||||
xOffset += this.LineHeight;
|
|
||||||
break;
|
|
||||||
case Nbsp:
|
|
||||||
xOffset += this.MeasureChar(' ');
|
|
||||||
break;
|
|
||||||
case Zwsp:
|
|
||||||
// don't add width for a zero-width space
|
|
||||||
break;
|
|
||||||
case ' ':
|
|
||||||
if (ignoreTrailingSpaces && IsTrailingSpace(text, i)) {
|
|
||||||
// if this is a trailing space, we can skip remaining spaces too
|
|
||||||
i = text.Length - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
xOffset += this.MeasureChar(' ');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
xOffset += this.MeasureChar(text[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// increase x size if this line is the longest
|
|
||||||
if (xOffset > size.X)
|
|
||||||
size.X = xOffset;
|
|
||||||
}
|
|
||||||
// include the last line's height too!
|
|
||||||
size.Y += this.LineHeight;
|
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -139,30 +103,13 @@ namespace MLEM.Font {
|
||||||
/// <param name="ellipsis">The characters to add to the end of the string if it is too long</param>
|
/// <param name="ellipsis">The characters to add to the end of the string if it is too long</param>
|
||||||
/// <returns>The truncated string, or the same string if it is shorter than the maximum width</returns>
|
/// <returns>The truncated string, or the same string if it is shorter than the maximum width</returns>
|
||||||
public string TruncateString(string text, float width, float scale, bool fromBack = false, string ellipsis = "") {
|
public string TruncateString(string text, float width, float scale, bool fromBack = false, string ellipsis = "") {
|
||||||
var total = new StringBuilder();
|
return this.TruncateString(text, width, scale, fromBack, ellipsis, null);
|
||||||
var ellipsisWidth = this.MeasureString(ellipsis).X * scale;
|
|
||||||
for (var i = 0; i < text.Length; i++) {
|
|
||||||
if (fromBack) {
|
|
||||||
total.Insert(0, text[text.Length - 1 - i]);
|
|
||||||
} else {
|
|
||||||
total.Append(text[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.MeasureString(total.ToString()).X * scale + ellipsisWidth >= width) {
|
|
||||||
if (fromBack) {
|
|
||||||
return total.Remove(0, 1).Insert(0, ellipsis).ToString();
|
|
||||||
} else {
|
|
||||||
return total.Remove(total.Length - 1, 1).Append(ellipsis).ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Splits a string to a given maximum width, adding newline characters between each line.
|
/// Splits a string to a given maximum width, adding newline characters between each line.
|
||||||
/// Also splits long words and supports zero-width spaces and takes into account existing newline characters in the passed <paramref name="text"/>.
|
/// Also splits long words and supports zero-width spaces and takes into account existing newline characters in the passed <paramref name="text"/>.
|
||||||
/// See <see cref="SplitStringSeparate"/> for a method that differentiates between existing newline characters and splits due to maximum width.
|
/// See <see cref="SplitStringSeparate(string,float,float)"/> for a method that differentiates between existing newline characters and splits due to maximum width.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">The text to split into multiple lines</param>
|
/// <param name="text">The text to split into multiple lines</param>
|
||||||
/// <param name="width">The maximum width that each line should have</param>
|
/// <param name="width">The maximum width that each line should have</param>
|
||||||
|
@ -182,6 +129,73 @@ namespace MLEM.Font {
|
||||||
/// <param name="scale">The scale to use for width measurements</param>
|
/// <param name="scale">The scale to use for width measurements</param>
|
||||||
/// <returns>The split string as an enumerable of split sections</returns>
|
/// <returns>The split string as an enumerable of split sections</returns>
|
||||||
public IEnumerable<string> SplitStringSeparate(string text, float width, float scale) {
|
public IEnumerable<string> SplitStringSeparate(string text, float width, float scale) {
|
||||||
|
return this.SplitStringSeparate(text, width, scale, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Vector2 MeasureString(string text, bool ignoreTrailingSpaces, Func<int, GenericFont> fontFunction) {
|
||||||
|
var size = Vector2.Zero;
|
||||||
|
if (text.Length <= 0)
|
||||||
|
return size;
|
||||||
|
var xOffset = 0F;
|
||||||
|
for (var i = 0; i < text.Length; i++) {
|
||||||
|
var font = fontFunction?.Invoke(i) ?? this;
|
||||||
|
switch (text[i]) {
|
||||||
|
case '\n':
|
||||||
|
xOffset = 0;
|
||||||
|
size.Y += font.LineHeight;
|
||||||
|
break;
|
||||||
|
case OneEmSpace:
|
||||||
|
xOffset += font.LineHeight;
|
||||||
|
break;
|
||||||
|
case Nbsp:
|
||||||
|
xOffset += font.MeasureChar(' ');
|
||||||
|
break;
|
||||||
|
case Zwsp:
|
||||||
|
// don't add width for a zero-width space
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
if (ignoreTrailingSpaces && IsTrailingSpace(text, i)) {
|
||||||
|
// if this is a trailing space, we can skip remaining spaces too
|
||||||
|
i = text.Length - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
xOffset += font.MeasureChar(' ');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
xOffset += font.MeasureChar(text[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// increase x size if this line is the longest
|
||||||
|
if (xOffset > size.X)
|
||||||
|
size.X = xOffset;
|
||||||
|
}
|
||||||
|
// include the last line's height too!
|
||||||
|
size.Y += (fontFunction?.Invoke(text.Length - 1) ?? this).LineHeight;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string TruncateString(string text, float width, float scale, bool fromBack, string ellipsis, Func<int, GenericFont> fontFunction) {
|
||||||
|
var total = new StringBuilder();
|
||||||
|
for (var i = 0; i < text.Length; i++) {
|
||||||
|
if (fromBack) {
|
||||||
|
total.Insert(0, text[text.Length - 1 - i]);
|
||||||
|
} else {
|
||||||
|
total.Append(text[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var font = fontFunction?.Invoke(i) ?? this;
|
||||||
|
if (font.MeasureString(total + ellipsis).X * scale >= width) {
|
||||||
|
if (fromBack) {
|
||||||
|
return total.Remove(0, 1).Insert(0, ellipsis).ToString();
|
||||||
|
} else {
|
||||||
|
return total.Remove(total.Length - 1, 1).Append(ellipsis).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IEnumerable<string> SplitStringSeparate(string text, float width, float scale, Func<int, GenericFont> fontFunction) {
|
||||||
var currWidth = 0F;
|
var currWidth = 0F;
|
||||||
var lastSpaceIndex = -1;
|
var lastSpaceIndex = -1;
|
||||||
var widthSinceLastSpace = 0F;
|
var widthSinceLastSpace = 0F;
|
||||||
|
@ -195,7 +209,8 @@ namespace MLEM.Font {
|
||||||
widthSinceLastSpace = 0;
|
widthSinceLastSpace = 0;
|
||||||
currWidth = 0;
|
currWidth = 0;
|
||||||
} else {
|
} else {
|
||||||
var cWidth = this.MeasureString(c.ToCachedString()).X * scale;
|
var font = fontFunction?.Invoke(i) ?? this;
|
||||||
|
var cWidth = font.MeasureString(c.ToCachedString()).X * scale;
|
||||||
if (c == ' ' || c == OneEmSpace || c == Zwsp) {
|
if (c == ' ' || c == OneEmSpace || c == Zwsp) {
|
||||||
// remember the location of this (breaking!) space
|
// remember the location of this (breaking!) space
|
||||||
lastSpaceIndex = curr.Length;
|
lastSpaceIndex = curr.Length;
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace MLEM.Formatting {
|
||||||
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F));
|
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F));
|
||||||
this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.55F));
|
this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.55F));
|
||||||
this.Codes.Add(new Regex("</(s|u|st|l)>"), (f, m, r) => new ResetFormattingCode(m, r));
|
this.Codes.Add(new Regex("</(s|u|st|l)>"), (f, m, r) => new ResetFormattingCode(m, r));
|
||||||
this.Codes.Add(new Regex("</(b|i)>"), (f, m, r) => new FontCode(m, r, null));
|
this.Codes.Add(new Regex("</(b|i|f)>"), (f, m, r) => new FontCode(m, r, null));
|
||||||
|
|
||||||
// color codes
|
// color codes
|
||||||
foreach (var c in typeof(Color).GetProperties()) {
|
foreach (var c in typeof(Color).GetProperties()) {
|
||||||
|
|
|
@ -60,8 +60,13 @@ namespace MLEM.Formatting {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="defaultPick">The default color, if none is specified</param>
|
/// <param name="defaultPick">The default color, if none is specified</param>
|
||||||
/// <returns>The color to render with</returns>
|
/// <returns>The color to render with</returns>
|
||||||
public Color? GetColor(Color defaultPick) {
|
public Color GetColor(Color defaultPick) {
|
||||||
return this.AppliedCodes.Select(c => c.GetColor(defaultPick)).FirstOrDefault(c => c.HasValue);
|
foreach (var code in this.AppliedCodes) {
|
||||||
|
var color = code.GetColor(defaultPick);
|
||||||
|
if (color.HasValue)
|
||||||
|
return color.Value;
|
||||||
|
}
|
||||||
|
return defaultPick;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -70,7 +75,12 @@ namespace MLEM.Formatting {
|
||||||
/// <param name="defaultPick">The default font, if none is specified</param>
|
/// <param name="defaultPick">The default font, if none is specified</param>
|
||||||
/// <returns>The font to render with</returns>
|
/// <returns>The font to render with</returns>
|
||||||
public GenericFont GetFont(GenericFont defaultPick) {
|
public GenericFont GetFont(GenericFont defaultPick) {
|
||||||
return this.AppliedCodes.Select(c => c.GetFont(defaultPick)).FirstOrDefault(f => f != null);
|
foreach (var code in this.AppliedCodes) {
|
||||||
|
var font = code.GetFont(defaultPick);
|
||||||
|
if (font != null)
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
return defaultPick;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -59,14 +59,14 @@ namespace MLEM.Formatting {
|
||||||
/// <param name="alignment">The text alignment that should be used for width calculations</param>
|
/// <param name="alignment">The text alignment that should be used for width calculations</param>
|
||||||
public void Split(GenericFont font, float width, float scale, TextAlignment alignment = TextAlignment.Left) {
|
public void Split(GenericFont font, float width, float scale, TextAlignment alignment = TextAlignment.Left) {
|
||||||
// a split string has the same character count as the input string but with newline characters added
|
// a split string has the same character count as the input string but with newline characters added
|
||||||
this.modifiedString = font.SplitString(this.String, width, scale);
|
this.modifiedString = string.Join("\n", font.SplitStringSeparate(this.String, width, scale, i => this.GetFontForIndex(font, i)));
|
||||||
this.StoreModifiedSubstrings(font, alignment);
|
this.StoreModifiedSubstrings(font, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Truncates this tokenized string, removing any additional characters that exceed the length from the displayed string.
|
/// Truncates this tokenized string, removing any additional characters that exceed the length from the displayed string.
|
||||||
/// Note that a tokenized string can be re-truncated without losing any of its actual data, as this operation merely modifies the <see cref="DisplayString"/>.
|
/// Note that a tokenized string can be re-truncated without losing any of its actual data, as this operation merely modifies the <see cref="DisplayString"/>.
|
||||||
/// <seealso cref="GenericFont.TruncateString"/>
|
/// <seealso cref="GenericFont.TruncateString(string,float,float,bool,string)"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="font">The font to use for width calculations</param>
|
/// <param name="font">The font to use for width calculations</param>
|
||||||
/// <param name="width">The maximum width, in display pixels based on the font and scale</param>
|
/// <param name="width">The maximum width, in display pixels based on the font and scale</param>
|
||||||
|
@ -74,13 +74,13 @@ namespace MLEM.Formatting {
|
||||||
/// <param name="ellipsis">The characters to add to the end of the string if it is too long</param>
|
/// <param name="ellipsis">The characters to add to the end of the string if it is too long</param>
|
||||||
/// <param name="alignment">The text alignment that should be used for width calculations</param>
|
/// <param name="alignment">The text alignment that should be used for width calculations</param>
|
||||||
public void Truncate(GenericFont font, float width, float scale, string ellipsis = "", TextAlignment alignment = TextAlignment.Left) {
|
public void Truncate(GenericFont font, float width, float scale, string ellipsis = "", TextAlignment alignment = TextAlignment.Left) {
|
||||||
this.modifiedString = font.TruncateString(this.String, width, scale, false, ellipsis);
|
this.modifiedString = font.TruncateString(this.String, width, scale, false, ellipsis, i => this.GetFontForIndex(font, i));
|
||||||
this.StoreModifiedSubstrings(font, alignment);
|
this.StoreModifiedSubstrings(font, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="GenericFont.MeasureString(string,bool)"/>
|
/// <inheritdoc cref="GenericFont.MeasureString(string,bool)"/>
|
||||||
public Vector2 Measure(GenericFont font) {
|
public Vector2 Measure(GenericFont font) {
|
||||||
return font.MeasureString(this.DisplayString);
|
return font.MeasureString(this.DisplayString, false, i => this.GetFontForIndex(font, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -115,18 +115,18 @@ namespace MLEM.Formatting {
|
||||||
var innerOffset = new Vector2(this.initialInnerOffset * scale, 0);
|
var innerOffset = new Vector2(this.initialInnerOffset * scale, 0);
|
||||||
for (var t = 0; t < this.Tokens.Length; t++) {
|
for (var t = 0; t < this.Tokens.Length; t++) {
|
||||||
var token = this.Tokens[t];
|
var token = this.Tokens[t];
|
||||||
var drawFont = token.GetFont(font) ?? font;
|
var drawFont = token.GetFont(font);
|
||||||
var drawColor = token.GetColor(color) ?? color;
|
var drawColor = token.GetColor(color);
|
||||||
for (var l = 0; l < token.SplitDisplayString.Length; l++) {
|
for (var l = 0; l < token.SplitDisplayString.Length; l++) {
|
||||||
var line = token.SplitDisplayString[l];
|
var line = token.SplitDisplayString[l];
|
||||||
for (var i = 0; i < line.Length; i++) {
|
for (var i = 0; i < line.Length; i++) {
|
||||||
var c = line[i];
|
var c = line[i];
|
||||||
if (l == 0 && i == 0)
|
if (l == 0 && i == 0)
|
||||||
token.DrawSelf(time, batch, pos + innerOffset, font, color, scale, depth);
|
token.DrawSelf(time, batch, pos + innerOffset, drawFont, color, scale, depth);
|
||||||
|
|
||||||
var cString = c.ToCachedString();
|
var cString = c.ToCachedString();
|
||||||
token.DrawCharacter(time, batch, c, cString, i, pos + innerOffset, drawFont, drawColor, scale, depth);
|
token.DrawCharacter(time, batch, c, cString, i, pos + innerOffset, drawFont, drawColor, scale, depth);
|
||||||
innerOffset.X += font.MeasureString(cString).X * scale;
|
innerOffset.X += drawFont.MeasureString(cString).X * scale;
|
||||||
}
|
}
|
||||||
// only split at a new line, not between tokens!
|
// only split at a new line, not between tokens!
|
||||||
if (l < token.SplitDisplayString.Length - 1) {
|
if (l < token.SplitDisplayString.Length - 1) {
|
||||||
|
@ -183,17 +183,18 @@ namespace MLEM.Formatting {
|
||||||
var innerOffset = new Vector2(this.initialInnerOffset, 0);
|
var innerOffset = new Vector2(this.initialInnerOffset, 0);
|
||||||
for (var t = 0; t < this.Tokens.Length; t++) {
|
for (var t = 0; t < this.Tokens.Length; t++) {
|
||||||
var token = this.Tokens[t];
|
var token = this.Tokens[t];
|
||||||
|
var tokenFont = token.GetFont(font);
|
||||||
token.InnerOffsets = new float[token.SplitDisplayString.Length - 1];
|
token.InnerOffsets = new float[token.SplitDisplayString.Length - 1];
|
||||||
var area = new List<RectangleF>();
|
var area = new List<RectangleF>();
|
||||||
for (var l = 0; l < token.SplitDisplayString.Length; l++) {
|
for (var l = 0; l < token.SplitDisplayString.Length; l++) {
|
||||||
var size = font.MeasureString(token.SplitDisplayString[l]);
|
var size = tokenFont.MeasureString(token.SplitDisplayString[l]);
|
||||||
var rect = new RectangleF(innerOffset, size);
|
var rect = new RectangleF(innerOffset, size);
|
||||||
if (!rect.IsEmpty)
|
if (!rect.IsEmpty)
|
||||||
area.Add(rect);
|
area.Add(rect);
|
||||||
|
|
||||||
if (l < token.SplitDisplayString.Length - 1) {
|
if (l < token.SplitDisplayString.Length - 1) {
|
||||||
innerOffset.X = token.InnerOffsets[l] = this.GetInnerOffsetX(font, t, l + 1, alignment);
|
innerOffset.X = token.InnerOffsets[l] = this.GetInnerOffsetX(font, t, l + 1, alignment);
|
||||||
innerOffset.Y += font.LineHeight;
|
innerOffset.Y += tokenFont.LineHeight;
|
||||||
} else {
|
} else {
|
||||||
innerOffset.X += size.X;
|
innerOffset.X += size.X;
|
||||||
}
|
}
|
||||||
|
@ -202,24 +203,26 @@ namespace MLEM.Formatting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetInnerOffsetX(GenericFont font, int tokenIndex, int lineIndex, TextAlignment alignment) {
|
private float GetInnerOffsetX(GenericFont defaultFont, int tokenIndex, int lineIndex, TextAlignment alignment) {
|
||||||
if (alignment > TextAlignment.Left) {
|
if (alignment > TextAlignment.Left) {
|
||||||
var token = this.Tokens[tokenIndex];
|
var token = this.Tokens[tokenIndex];
|
||||||
|
var tokenFont = token.GetFont(defaultFont);
|
||||||
// if we're the last line in our line array, then we don't contain a line split, so the line ends in a later token
|
// if we're the last line in our line array, then we don't contain a line split, so the line ends in a later token
|
||||||
var endsLater = lineIndex >= token.SplitDisplayString.Length - 1;
|
var endsLater = lineIndex >= token.SplitDisplayString.Length - 1;
|
||||||
// if the line ends in our token, we should ignore trailing white space
|
// if the line ends in our token, we should ignore trailing white space
|
||||||
var restOfLine = font.MeasureString(token.SplitDisplayString[lineIndex], !endsLater).X;
|
var restOfLine = tokenFont.MeasureString(token.SplitDisplayString[lineIndex], !endsLater).X;
|
||||||
if (endsLater) {
|
if (endsLater) {
|
||||||
for (var i = tokenIndex + 1; i < this.Tokens.Length; i++) {
|
for (var i = tokenIndex + 1; i < this.Tokens.Length; i++) {
|
||||||
var other = this.Tokens[i];
|
var other = this.Tokens[i];
|
||||||
|
var otherFont = other.GetFont(defaultFont);
|
||||||
if (other.SplitDisplayString.Length > 1) {
|
if (other.SplitDisplayString.Length > 1) {
|
||||||
// the line ends in this token (so we also ignore trailing whitespaces)
|
// the line ends in this token (so we also ignore trailing whitespaces)
|
||||||
restOfLine += font.MeasureString(other.SplitDisplayString[0], true).X;
|
restOfLine += otherFont.MeasureString(other.SplitDisplayString[0], true).X;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// the line doesn't end in this token (or it's the last token), so add it fully
|
// the line doesn't end in this token (or it's the last token), so add it fully
|
||||||
var lastToken = i >= this.Tokens.Length - 1;
|
var lastToken = i >= this.Tokens.Length - 1;
|
||||||
restOfLine += font.MeasureString(other.DisplayString, lastToken).X;
|
restOfLine += otherFont.MeasureString(other.DisplayString, lastToken).X;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,5 +233,14 @@ namespace MLEM.Formatting {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GenericFont GetFontForIndex(GenericFont font, int index) {
|
||||||
|
foreach (var token in this.Tokens) {
|
||||||
|
index -= token.Substring.Length;
|
||||||
|
if (index <= 0)
|
||||||
|
return token.GetFont(font);
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue