mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-26 06:28:35 +01:00
added macros to text formatting
This commit is contained in:
parent
9d5e9d4ccf
commit
a17301504d
4 changed files with 66 additions and 1 deletions
|
@ -51,3 +51,14 @@ You can then register your formatting code like this:
|
||||||
```cs
|
```cs
|
||||||
formatter.Codes.Add(new Regex("<matchme>"), (form, match, regex) => new MyCustomCode(match, regex));
|
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");
|
||||||
|
```
|
|
@ -16,6 +16,11 @@ namespace MLEM.Font {
|
||||||
/// It is mainly used for <see cref="ImageCode"/>.
|
/// It is mainly used for <see cref="ImageCode"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const char OneEmSpace = '\uF8FF';
|
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>
|
/// <summary>
|
||||||
/// The bold version of this font.
|
/// The bold version of this font.
|
||||||
|
@ -100,6 +105,9 @@ namespace MLEM.Font {
|
||||||
case OneEmSpace:
|
case OneEmSpace:
|
||||||
xOffset += this.LineHeight;
|
xOffset += this.LineHeight;
|
||||||
break;
|
break;
|
||||||
|
case Nbsp:
|
||||||
|
xOffset += this.MeasureChar(' ').X;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
xOffset += this.MeasureChar(c).X;
|
xOffset += this.MeasureChar(c).X;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -19,6 +20,12 @@ namespace MLEM.Formatting {
|
||||||
/// The <see cref="Regex"/> defines how the formatting code should be matched.
|
/// The <see cref="Regex"/> defines how the formatting code should be matched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>();
|
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>
|
/// <summary>
|
||||||
/// Creates a new text formatter with a set of default formatting codes.
|
/// Creates a new text formatter with a set of default formatting codes.
|
||||||
|
@ -44,6 +51,9 @@ namespace MLEM.Formatting {
|
||||||
// animation codes
|
// 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 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));
|
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>
|
/// <summary>
|
||||||
|
@ -53,6 +63,8 @@ namespace MLEM.Formatting {
|
||||||
/// <param name="s">The string to tokenize</param>
|
/// <param name="s">The string to tokenize</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public TokenizedString Tokenize(GenericFont font, string s) {
|
public TokenizedString Tokenize(GenericFont font, string s) {
|
||||||
|
// resolve macros
|
||||||
|
s = this.ResolveMacros(s);
|
||||||
var tokens = new List<Token>();
|
var tokens = new List<Token>();
|
||||||
var codes = new List<Code>();
|
var codes = new List<Code>();
|
||||||
// add the formatting code right at the start of the string
|
// 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());
|
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) {
|
private Code GetNextCode(string s, int index, int maxIndex = int.MaxValue) {
|
||||||
var (c, m, r) = this.Codes
|
var (c, m, r) = this.Codes
|
||||||
.Select(kv => (c: kv.Value, m: kv.Key.Match(s, index), r: kv.Key))
|
.Select(kv => (c: kv.Value, m: kv.Key.Match(s, index), r: kv.Key))
|
||||||
|
@ -99,5 +133,13 @@ namespace MLEM.Formatting {
|
||||||
return s;
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
@ -123,7 +124,10 @@ namespace Sandbox {
|
||||||
var sc = 4;
|
var sc = 4;
|
||||||
var formatter = new TextFormatter();
|
var formatter = new TextFormatter();
|
||||||
formatter.AddImage("Test", new TextureRegion(tex, 0, 8, 24, 24));
|
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 <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 = "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>!";
|
//var strg = "This is <u>a test of the underlined formatting code</u>!";
|
||||||
|
|
Loading…
Reference in a new issue