1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-06-07 15:23:37 +02:00

added macros to text formatting

This commit is contained in:
Ellpeck 2020-07-01 14:30:47 +02:00
parent 9d5e9d4ccf
commit a17301504d
4 changed files with 66 additions and 1 deletions

View file

@ -50,4 +50,15 @@ Adding custom formatting codes is easy! There are two things that a custom forma
You can then register your formatting code like this:
```cs
formatter.Codes.Add(new Regex("<matchme>"), (form, match, regex) => new MyCustomCode(match, regex));
```
## Macros
The text formatting system additionally supports macros: Regular expressions that cause the matched text to expand into a different string. Macros can be resolved recursively, meaning that you can have macros that resolve into other macros, and so on.
By default, the following macros are available:
- `~` expands into a non-breaking space, much like in LaTeX.
Adding custom macros is very similar to adding custom formatting codes:
```cs
formatter.Macros.Add(new Regex("matchme"), (form, match, regex) => "replacement string");
```

View file

@ -16,6 +16,11 @@ namespace MLEM.Font {
/// It is mainly used for <see cref="ImageCode"/>.
/// </summary>
public const char OneEmSpace = '\uF8FF';
/// <summary>
/// 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"/>.
/// </summary>
public const char Nbsp = '\u00A0';
/// <summary>
/// The bold version of this font.
@ -100,6 +105,9 @@ namespace MLEM.Font {
case OneEmSpace:
xOffset += this.LineHeight;
break;
case Nbsp:
xOffset += this.MeasureChar(' ').X;
break;
default:
xOffset += this.MeasureChar(c).X;
break;

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@ -19,6 +20,12 @@ namespace MLEM.Formatting {
/// The <see cref="Regex"/> defines how the formatting code should be matched.
/// </summary>
public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>();
/// <summary>
/// The macros that this text formatter uses.
/// A macro is a <see cref="Regex"/> that turns a snippet of text into another snippet of text.
/// Macros can resolve recursively and can resolve into formatting codes.
/// </summary>
public readonly Dictionary<Regex, Macro> Macros = new Dictionary<Regex, Macro>();
/// <summary>
/// Creates a new text formatter with a set of default formatting codes.
@ -44,6 +51,9 @@ namespace MLEM.Formatting {
// animation codes
this.Codes.Add(new Regex(@"<a wobbly(?: ([+-.0-9]*) ([+-.0-9]*))?>"), (f, m, r) => new WobblyCode(m, r, float.TryParse(m.Groups[1].Value, out var mod) ? mod : 5, float.TryParse(m.Groups[2].Value, out var heightMod) ? heightMod : 1 / 8F));
this.Codes.Add(new Regex("</a>"), (f, m, r) => new AnimatedCode(m, r));
// macros
this.Macros.Add(new Regex("~"), (f, m, r) => GenericFont.Nbsp.ToCachedString());
}
/// <summary>
@ -53,6 +63,8 @@ namespace MLEM.Formatting {
/// <param name="s">The string to tokenize</param>
/// <returns></returns>
public TokenizedString Tokenize(GenericFont font, string s) {
// resolve macros
s = this.ResolveMacros(s);
var tokens = new List<Token>();
var codes = new List<Code>();
// add the formatting code right at the start of the string
@ -84,6 +96,28 @@ namespace MLEM.Formatting {
return new TokenizedString(font, s, StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray());
}
/// <summary>
/// Resolves the macros in the given string recursively, until no more macros can be resolved.
/// This method is used by <see cref="Tokenize"/>, meaning that it does not explicitly have to be called when using text formatting.
/// </summary>
/// <param name="s">The string to resolve macros for</param>
/// <returns>The final, recursively resolved string</returns>
public string ResolveMacros(string s) {
// resolve macros that resolve into macros
bool matched;
do {
matched = false;
foreach (var macro in this.Macros) {
s = macro.Key.Replace(s, m => {
// if the match evaluator was queried, then we know we matched something
matched = true;
return macro.Value(this, m, macro.Key);
});
}
} while (matched);
return s;
}
private Code GetNextCode(string s, int index, int maxIndex = int.MaxValue) {
var (c, m, r) = this.Codes
.Select(kv => (c: kv.Value, m: kv.Key.Match(s, index), r: kv.Key))
@ -99,5 +133,13 @@ namespace MLEM.Formatting {
return s;
}
/// <summary>
/// Represents a text formatting macro. Used by <see cref="TextFormatter.Macros"/>.
/// </summary>
/// <param name="formatter">The text formatter that created this macro</param>
/// <param name="match">The match for the macro's regex</param>
/// <param name="regex">The regex used to create this macro</param>
public delegate string Macro(TextFormatter formatter, Match match, Regex regex);
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
@ -123,7 +124,10 @@ namespace Sandbox {
var sc = 4;
var formatter = new TextFormatter();
formatter.AddImage("Test", new TextureRegion(tex, 0, 8, 24, 24));
var strg = "Additionally, it <a wobbly>wobbles</a> and has a <s>shadow</s> or a <s #ffff0000 4>weird shadow</s>. We like icons too! <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test> <i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test><i Test>";
formatter.Macros.Add(new Regex("<testmacro>"), (f, m, r) => "<test1>");
formatter.Macros.Add(new Regex("<test1>"), (f, m, r) => "<test2> blue");
formatter.Macros.Add(new Regex("<test2>"), (f, m, r) => "<c Blue>");
var strg = "This text uses a bunch of non-breaking~spaces to see if macros work. Additionally, it uses a macro that resolves into a bunch of other macros and then, at the end, into <testmacro> text</c>.";
//var strg = "Lorem Ipsum <i Test> is simply dummy text of the <i Test> printing and typesetting <i Test> industry. Lorem Ipsum has been the industry's standard dummy text <i Test> ever since the <i Test> 1500s, when <i Test><i Test><i Test><i Test><i Test><i Test><i Test> an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
//var strg = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
//var strg = "This is <u>a test of the underlined formatting code</u>!";