1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-05-17 06:38:45 +02:00

use a custom character for image codes rather than trying to calculate spaces

This commit is contained in:
Ellpeck 2020-06-20 01:18:27 +02:00
parent deec553b22
commit 90e0ff55d1
8 changed files with 68 additions and 100 deletions

View file

@ -32,18 +32,10 @@ namespace MLEM.Extended.Font {
this.Italic = italic != null ? new GenericBitmapFont(italic) : this;
}
/// <inheritdoc/>
public override Vector2 MeasureString(string text) {
if (text.Length == 1 && this.SingleCharacterWidthFix(text, out var size))
return size;
return this.Font.MeasureString(text);
}
/// <inheritdoc/>
public override Vector2 MeasureString(StringBuilder text) {
if (text.Length == 1 && this.SingleCharacterWidthFix(text.ToString(), out var size))
return size;
return this.Font.MeasureString(text);
/// <inheritdoc />
protected override Vector2 CalcCharSize(char c) {
var region = this.Font.GetCharacterRegion(c);
return region != null ? new Vector2(region.XAdvance, region.Height) : Vector2.Zero;
}
/// <inheritdoc/>
@ -76,23 +68,5 @@ namespace MLEM.Extended.Font {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
}
/// <inheritdoc />
public override bool HasCharacter(char c) {
return this.Font.GetCharacterRegion(c) != null;
}
// this fixes an issue with BitmapFonts where, if only given a single character,
// only the width of the character itself (disregarding spacing) is returned
private bool SingleCharacterWidthFix(string text, out Vector2 size) {
var codePoint = char.ConvertToUtf32(text, 0);
var region = this.Font.GetCharacterRegion(codePoint);
if (region != null) {
size = new Vector2(region.XAdvance, region.Height);
return true;
}
size = default;
return false;
}
}
}

View file

@ -164,7 +164,7 @@ namespace MLEM.Ui.Elements {
// not initialized yet
if (!this.Font.HasValue())
return;
var length = this.Font.Value.MeasureString(this.text).X * this.TextScale;
var length = this.Font.Value.MeasureString(this.text.ToString()).X * this.TextScale;
var maxWidth = this.DisplayArea.Width / this.Scale - this.TextOffsetX * 2;
if (length > maxWidth) {
// if we're moving the caret to the left

View file

@ -46,12 +46,8 @@ namespace MLEM.Ui.Style {
public override GenericFont Italic => this;
public override float LineHeight => 1;
public override Vector2 MeasureString(string text) {
return Vector2.One;
}
public override Vector2 MeasureString(StringBuilder text) {
return Vector2.One;
protected override Vector2 CalcCharSize(char c) {
return Vector2.Zero;
}
public override void DrawString(SpriteBatch batch, string text, Vector2 position, Color color) {
@ -72,10 +68,6 @@ namespace MLEM.Ui.Style {
public override void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
}
public override bool HasCharacter(char c) {
return false;
}
}
}

View file

@ -1,6 +1,7 @@
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Formatting.Codes;
namespace MLEM.Font {
/// <summary>
@ -9,6 +10,13 @@ namespace MLEM.Font {
/// </summary>
public abstract class GenericFont {
/// <summary>
/// This field holds a special, private use area code point for a one em space.
/// This is a character that isn't drawn, but has the same width as <see cref="LineHeight"/>.
/// It is mainly used for <see cref="ImageCode"/>.
/// </summary>
public const char OneEmSpace = '\uF8FF';
/// <summary>
/// The bold version of this font.
/// </summary>
@ -22,12 +30,6 @@ namespace MLEM.Font {
///<inheritdoc cref="SpriteFont.LineSpacing"/>
public abstract float LineHeight { get; }
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
public abstract Vector2 MeasureString(string text);
///<inheritdoc cref="SpriteFont.MeasureString(StringBuilder)"/>
public abstract Vector2 MeasureString(StringBuilder text);
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
public abstract void DrawString(SpriteBatch batch, string text, Vector2 position, Color color);
@ -46,12 +48,8 @@ namespace MLEM.Font {
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
public abstract void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth);
/// <summary>
/// Returns whether this generic font supports the given character
/// </summary>
/// <param name="c">The character</param>
/// <returns>Whether this generic font supports the character</returns>
public abstract bool HasCharacter(char c);
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
protected abstract Vector2 CalcCharSize(char c);
/// <summary>
/// Draws a string with the given text alignment.
@ -87,6 +85,32 @@ namespace MLEM.Font {
this.DrawString(batch, text, position, color, rotation, origin, scale, effects, layerDepth);
}
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
public Vector2 MeasureChar(char c) {
return c == OneEmSpace ? new Vector2(this.LineHeight) : this.CalcCharSize(c);
}
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
public Vector2 MeasureString(string text) {
var size = Vector2.Zero;
var xOffset = 0F;
foreach (var c in text) {
if (c == '\n') {
xOffset = 0;
size.Y += this.LineHeight;
continue;
}
xOffset += this.MeasureChar(c).X;
// 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>
/// Truncates a string to a given width. If the string's displayed area is larger than the maximum width, the string is cut off.
/// Optionally, the string can be cut off a bit sooner, adding the <see cref="ellipsis"/> at the end instead.
@ -107,7 +131,7 @@ namespace MLEM.Font {
total.Append(text[i]);
}
if (this.MeasureString(total).X * scale + ellipsisWidth >= width) {
if (this.MeasureString(total.ToString()).X * scale + ellipsisWidth >= width) {
if (fromBack) {
return total.Remove(0, 1).Insert(0, ellipsis).ToString();
} else {
@ -161,19 +185,6 @@ namespace MLEM.Font {
return total.ToString(0, total.Length - 2);
}
/// <summary>
/// Returns a string made up of the given content characters that is the given length long when displayed.
/// </summary>
/// <param name="width">The width that the string should have if the scale is 1</param>
/// <param name="content">The content that the string should contain. Defaults to a space.</param>
/// <returns></returns>
public string GetWidthString(float width, char content = ' ') {
var strg = content.ToString();
while (this.MeasureString(strg).X < width)
strg += content;
return strg;
}
}
/// <summary>

View file

@ -25,19 +25,14 @@ namespace MLEM.Font {
/// <param name="bold">A bold version of the font</param>
/// <param name="italic">An italic version of the font</param>
public GenericSpriteFont(SpriteFont font, SpriteFont bold = null, SpriteFont italic = null) {
this.Font = font;
this.Bold = bold != null ? new GenericSpriteFont(bold) : this;
this.Italic = italic != null ? new GenericSpriteFont(italic) : this;
this.Font = SetDefaults(font);
this.Bold = bold != null ? new GenericSpriteFont(SetDefaults(bold)) : this;
this.Italic = italic != null ? new GenericSpriteFont(SetDefaults(italic)) : this;
}
/// <inheritdoc/>
public override Vector2 MeasureString(string text) {
return this.Font.MeasureString(text);
}
/// <inheritdoc/>
public override Vector2 MeasureString(StringBuilder text) {
return this.Font.MeasureString(text);
/// <inheritdoc />
protected override Vector2 CalcCharSize(char c) {
return this.Font.MeasureString(c.ToString());
}
/// <inheritdoc/>
@ -70,9 +65,11 @@ namespace MLEM.Font {
batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth);
}
/// <inheritdoc />
public override bool HasCharacter(char c) {
return this.Font.Characters.Contains(c);
private static SpriteFont SetDefaults(SpriteFont font) {
// so that missing character behavior is in line with MG.Extended's
// bitmap fonts, we draw nothing as the default character
font.DefaultCharacter = ' ';
return font;
}
}

View file

@ -12,8 +12,6 @@ namespace MLEM.Formatting.Codes {
public class ImageCode : Code {
private readonly SpriteAnimation image;
private string replacement;
private float gapSize;
/// <inheritdoc />
public ImageCode(Match match, Regex regex, SpriteAnimation image) : base(match, regex) {
@ -27,13 +25,7 @@ namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public override string GetReplacementString(GenericFont font) {
if (this.replacement == null) {
// use non-breaking space so that the image won't be line-splitted
var strg = font.GetWidthString(font.LineHeight, font.HasCharacter('\u00A0') ? '\u00A0' : ' ');
this.replacement = strg.Remove(strg.Length - 1) + ' ';
this.gapSize = font.MeasureString(this.replacement).X;
}
return this.replacement;
return GenericFont.OneEmSpace.ToString();
}
/// <inheritdoc />
@ -43,8 +35,7 @@ namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public override void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
var position = pos + new Vector2(this.gapSize - font.LineHeight, 0) / 2 * scale;
batch.Draw(this.image.CurrentRegion, new RectangleF(position, new Vector2(font.LineHeight * scale)), Color.White.CopyAlpha(color));
batch.Draw(this.image.CurrentRegion, new RectangleF(pos, new Vector2(font.LineHeight * scale)), Color.White.CopyAlpha(color));
}
}

View file

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<font>
<info face="BitPotionExt" size="-16" bold="0" italic="0" charset="" unicode="1" stretchH="100" smooth="0" aa="1" padding="0,0,0,0" spacing="1,1" outline="0"/>
<common lineHeight="10" base="11" scaleW="128" scaleH="128" pages="1" packed="0" alphaChnl="0" redChnl="4" greenChnl="4" blueChnl="4"/>
<common lineHeight="14" base="11" scaleW="128" scaleH="128" pages="1" packed="0" alphaChnl="0" redChnl="4" greenChnl="4" blueChnl="4"/>
<pages>
<page id="0" file="RegularTexture.png" />
</pages>

View file

@ -10,6 +10,7 @@ using MLEM.Extended.Extensions;
using MLEM.Extended.Font;
using MLEM.Extended.Tiled;
using MLEM.Extensions;
using MLEM.Font;
using MLEM.Formatting;
using MLEM.Formatting.Codes;
using MLEM.Input;
@ -19,6 +20,7 @@ using MLEM.Textures;
using MLEM.Ui;
using MLEM.Ui.Elements;
using MLEM.Ui.Style;
using MonoGame.Extended;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Tiled;
using RectangleF = MonoGame.Extended.RectangleF;
@ -118,27 +120,28 @@ namespace Sandbox {
this.SpriteBatch.End();
};*/
var sc = 4;
var formatter = new TextFormatter();
formatter.AddImage("Test", new TextureRegion(tex, 0, 8, 24, 24));
var strg = "<s>This</s> <c CornflowerBlue>is a formatted string</c> with <c #ffff0000>two bits of formatting</c>! It also includesavery<c Pink>long</c>wordthatis<c Blue>formatted</c>aswell. 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>";
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>";
//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>!";
this.tokenized = formatter.Tokenize(font, strg);
this.tokenized.Split(font, 400, 5);
this.tokenized.Split(font, 400, sc);
/*this.OnDraw += (g, time) => {
this.OnDraw += (g, time) => {
this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
this.SpriteBatch.FillRectangle(new RectangleF(400, 20, 400, 1000), Color.Green);
font.DrawString(this.SpriteBatch, this.tokenized.DisplayString, new Vector2(400, 20), Color.White * 0.25F, 0, Vector2.Zero, 5, SpriteEffects.None, 0);
this.tokenized.Draw(time, this.SpriteBatch, new Vector2(400, 20), font, Color.White, 5, 0);
font.DrawString(this.SpriteBatch, this.tokenized.DisplayString, new Vector2(400, 20), Color.White * 0.25F, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
this.tokenized.Draw(time, this.SpriteBatch, new Vector2(400, 20), font, Color.White, sc, 0);
this.SpriteBatch.DrawGrid(new Vector2(30, 30), new Vector2(40, 60), new Point(10, 5), Color.Yellow, 3);
this.SpriteBatch.End();
};*/
};
this.OnUpdate += (g, time) => {
if (this.InputHandler.IsPressed(Keys.W)) {
this.tokenized = formatter.Tokenize(font, strg);
this.tokenized.Split(font, this.InputHandler.IsModifierKeyDown(ModifierKey.Shift) ? 400 : 500, 5);
this.tokenized.Split(font, this.InputHandler.IsModifierKeyDown(ModifierKey.Shift) ? 400 : 500, sc);
}
this.tokenized.Update(time);
};