From a17301504dad89db2b7ab00ec7435f42d3313df2 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 1 Jul 2020 14:30:47 +0200 Subject: [PATCH] added macros to text formatting --- Docs/articles/text_formatting.md | 11 +++++++++ MLEM/Font/GenericFont.cs | 8 ++++++ MLEM/Formatting/TextFormatter.cs | 42 ++++++++++++++++++++++++++++++++ Sandbox/GameImpl.cs | 6 ++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Docs/articles/text_formatting.md b/Docs/articles/text_formatting.md index 1954ac4..0a3f81c 100644 --- a/Docs/articles/text_formatting.md +++ b/Docs/articles/text_formatting.md @@ -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(""), (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"); ``` \ No newline at end of file diff --git a/MLEM/Font/GenericFont.cs b/MLEM/Font/GenericFont.cs index 37872cb..04ae4a2 100644 --- a/MLEM/Font/GenericFont.cs +++ b/MLEM/Font/GenericFont.cs @@ -16,6 +16,11 @@ namespace MLEM.Font { /// It is mainly used for . /// public const char OneEmSpace = '\uF8FF'; + /// + /// This field holds the unicode representation of a non-breaking space. + /// Whereas a regular would have to explicitly support this character for width calculations, generic fonts implicitly support it in . + /// + public const char Nbsp = '\u00A0'; /// /// 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; diff --git a/MLEM/Formatting/TextFormatter.cs b/MLEM/Formatting/TextFormatter.cs index 1441ced..5f7c456 100644 --- a/MLEM/Formatting/TextFormatter.cs +++ b/MLEM/Formatting/TextFormatter.cs @@ -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 defines how the formatting code should be matched. /// public readonly Dictionary Codes = new Dictionary(); + /// + /// The macros that this text formatter uses. + /// A macro is a that turns a snippet of text into another snippet of text. + /// Macros can resolve recursively and can resolve into formatting codes. + /// + public readonly Dictionary Macros = new Dictionary(); /// /// 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(@""), (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(""), (f, m, r) => new AnimatedCode(m, r)); + + // macros + this.Macros.Add(new Regex("~"), (f, m, r) => GenericFont.Nbsp.ToCachedString()); } /// @@ -53,6 +63,8 @@ namespace MLEM.Formatting { /// The string to tokenize /// public TokenizedString Tokenize(GenericFont font, string s) { + // resolve macros + s = this.ResolveMacros(s); var tokens = new List(); var codes = new List(); // 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()); } + /// + /// Resolves the macros in the given string recursively, until no more macros can be resolved. + /// This method is used by , meaning that it does not explicitly have to be called when using text formatting. + /// + /// The string to resolve macros for + /// The final, recursively resolved string + 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; } + /// + /// Represents a text formatting macro. Used by . + /// + /// The text formatter that created this macro + /// The match for the macro's regex + /// The regex used to create this macro + public delegate string Macro(TextFormatter formatter, Match match, Regex regex); + } } \ No newline at end of file diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index d60faa9..e0d8bbf 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -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 wobbles and has a shadow or a weird shadow. We like icons too! "; + formatter.Macros.Add(new Regex(""), (f, m, r) => ""); + formatter.Macros.Add(new Regex(""), (f, m, r) => " blue"); + formatter.Macros.Add(new Regex(""), (f, m, r) => ""); + 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 text."; //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 a test of the underlined formatting code!";