using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using OpenVIII.Encoding.Tags; using System.Collections.Generic; using System.Linq; namespace OpenVIII { /// /// Font Module /// /// /// 21x10 characters; char is always 24x24; 2 files side by side; sysfnt00 is same as sysfld00, /// but sysfnt00 is missing sysfnt01 /// public class Font { #region Fields public static Dictionary ColorID2Blink = new Dictionary { { ColorID.Dark_Grey, new Color(12,15,12,255) }, { ColorID.Grey, new Color(41,49,41,255) }, { ColorID.Yellow, new Color(222,222,8,255) }, { ColorID.Red, new Color(128,24,24,255) }, { ColorID.Green, new Color(0,128,0,255) }, { ColorID.Blue, new Color(53,90,128,255) }, { ColorID.Purple, new Color(128,0,128,255) }, { ColorID.White, new Color(148,148,164,255) } }; public static Dictionary ColorID2Color = new Dictionary { { ColorID.Dark_Grey, new Color(41,49,41,255) }, { ColorID.Grey, new Color(148,148,164,255) }, { ColorID.Yellow, new Color(222,222,8,255) }, { ColorID.Red, new Color(255,24,24,255) }, { ColorID.Green, new Color(0,255,0,255) }, { ColorID.Blue, new Color(106,180,238,255) }, { ColorID.Purple, new Color(255,0,255,255) }, { ColorID.White, Color.White }, }; private byte[] charWidths; private Texture2D menuFont; private Texture2D sysfnt; //21x10 characters; char is always 12x12 private TextureHandler sysfntbig; #endregion Fields #region Constructors public Font() => LoadFonts(); #endregion Constructors #region Enums public enum ColorID { Dark_Grey, Grey, Yellow, Red, Green, Blue, Purple, White, //these are darker versions that are faded to when blinking } public enum Type { sysFntBig, sysfnt, menuFont, } #endregion Enums #region Methods public void LoadFonts() { Memory.Log.WriteLine($"{nameof(Font)} :: {nameof(LoadFonts)} "); var aw = ArchiveWorker.Load(Memory.Archives.A_MENU); var bufferTex = aw.GetBinaryFile("sysfnt.tex"); var tex = new TEX(bufferTex); sysfnt = tex.GetTexture((int)ColorID.White); sysfntbig = TextureHandler.Create("sysfld{0:00}.tex", tex, 2, 1, (int)ColorID.White); var bufferTDW = aw.GetBinaryFile("sysfnt.tdw"); var tim = new TDW(bufferTDW); charWidths = tim.CharWidths; menuFont = tim.GetTexture((ushort)ColorID.White); } public Rectangle RenderBasicText(FF8String buffer, Vector2 pos, Vector2 zoom, Type whichFont = 0, float Fade = 1.0f, int lineSpacing = 0, bool skipdraw = false, ColorID color_ = ColorID.White, bool blink = false) { var colorbak = color_; var color = ColorID2Color[color_]; var faded_color = ColorID2Blink[color_]; if (buffer == null) return new Rectangle(); var ret = new Rectangle(pos.RoundedPoint(), new Point(0)); var destRect = Rectangle.Empty; var real = pos.RoundedPoint(); var charCountWidth = 21; var charSize = 12; //pixelhandler does the 2x scaling on the fly. var size = (new Vector2(0, charSize) * zoom).RoundedPoint(); var baksize = size; var width = 0; var skipletter = false; for (var i = 0; i < buffer.Length; i++) { size = baksize; var c = buffer[i]; if (c == 0) continue; else if (c == (byte)FF8TextTagCode.Dialog) { if (++i < buffer.Length - 1) { c = buffer[i]; switch ((FF8TextTagDialog)c) { // Most of these should be replaced before it gets here becuase they have // values set by other objects. case FF8TextTagDialog.CustomICON: DrawIcon(buffer, zoom, Fade, skipdraw, real, ref size, ref skipletter, ref i, ref c); break; } //if (!skipletter) SetRetRec(pos, ref ret, ref real, size); continue; } } else if (c == (byte)FF8TextTagCode.Key) { if (++i < buffer.Length - 1) { var k = (FF8TextTagKey)buffer[i]; var str = Input2.ButtonString(k); var retpos = RenderBasicText(str, real, zoom, whichFont, Fade, lineSpacing, skipdraw, ColorID.Green); size.X = retpos.Width; //size.Y = retpos.Height; //real.X += retpos.Width; //TODO add key/controller input icons/text here. //if (!skipletter) SetRetRec(pos, ref ret, ref real, size); continue; } } else if (c == (byte)FF8TextTagCode.Color) { if (++i < buffer.Length - 1) { c = buffer[i]; blink = c >= (byte)FF8TextTagColor.Dark_GrayBlink ? true : false; GetColorFromTag(c, out var nc, out var fc); color = nc ?? ColorID2Color[colorbak]; faded_color = fc ?? ColorID2Blink[colorbak]; SetRetRec(pos, ref ret, ref real, size); continue; } } else if (c == (byte)FF8TextTagCode.Line && NewLine(pos, lineSpacing, ref real, size)) { SetRetRec(pos, ref ret, ref real, size); continue; } if (!skipletter) { var deltaChar = GetDeltaChar(c); if (deltaChar >= 0 && charWidths != null && deltaChar < charWidths.Length) { width = charWidths[deltaChar]; size.X = (int)(charWidths[deltaChar] * zoom.X); } else { width = charSize; size.X = (int)(charSize * zoom.X); } var curSize = size; var verticalPosition = deltaChar / charCountWidth; //i.e. 1280 is 100%, 640 is 50% and therefore 2560 is 200% which means multiply by 0.5f or 2.0f destRect = new Rectangle(real, size); if (!skipdraw) { var sourceRect = new Rectangle((deltaChar - (verticalPosition * charCountWidth)) * charSize, verticalPosition * charSize, width, charSize); DrawLetter(whichFont, Fade, blink ? Color.Lerp(color, faded_color, Menu.Blink_Amount) : color, destRect, sourceRect); } } skipletter = false; SetRetRec(pos, ref ret, ref real, size); } ret.Height = size.Y + (real.Y - (int)pos.Y); return ret; } private static void SetRetRec(Vector2 pos, ref Rectangle ret, ref Point real, Point size) { real.X += size.X; var curWidth = real.X - (int)pos.X; if (curWidth > ret.Width) ret.Width = curWidth; } private static void DrawIcon(FF8String buffer, Vector2 zoom, float Fade, bool skipdraw, Point real, ref Point size, ref bool skipletter, ref int i, ref byte c) { if (i + 3 < buffer.Length) { c = buffer[++i]; short ic = c; c = buffer[++i]; ic |= (short)(c << 8); var pal = buffer[++i]; Memory.Icons.Trim((Icons.ID)ic, pal); var icon = Memory.Icons[(Icons.ID)ic]; //Vector2 scale = Memory.Icons.GetTexture((Icons.ID)ic).ScaleFactor; if (icon != null) { var adj = (12 / (float)(icon.Height)); var scale = new Vector2(adj * zoom.X); size.X = (int)(icon.Width * scale.X); size.Y = (int)(icon.Height * scale.X); var destRect = new Rectangle(real, size); //real.X += size.X; //skipletter = true; if (!skipdraw) Memory.Icons.Draw((Icons.ID)ic, pal, destRect, Vector2.Zero, Fade); } } } public Rectangle RenderBasicText(FF8String buffer, Point pos, Vector2 zoom, Type whichFont = 0, float Fade = 1.0f, int lineSpacing = 0, bool skipdraw = false, ColorID color = ColorID.White, bool blink = false) => RenderBasicText(buffer, pos.ToVector2(), zoom, whichFont, Fade, lineSpacing, skipdraw, color, blink); public Rectangle RenderBasicText(FF8String buffer, int x, int y, float zoomWidth = 2.545455f, float zoomHeight = 3.0375f, Type whichFont = 0, float Fade = 1.0f, int lineSpacing = 0, bool skipdraw = false, ColorID color = ColorID.White, bool blink = false) => RenderBasicText(buffer, new Vector2(x, y), new Vector2(zoomWidth, zoomHeight), whichFont, Fade, lineSpacing, skipdraw, color,blink); private static void GetColorFromTag(byte c, out Color? color, out Color? faded_color) { color = null; faded_color = null; ColorID? cid = null; switch ((FF8TextTagColor)c) { case FF8TextTagColor.Blue: case FF8TextTagColor.BlueBlink: cid = ColorID.Blue; break; case FF8TextTagColor.Green: case FF8TextTagColor.GreenBlink: cid = ColorID.Green; break; case FF8TextTagColor.Grey: case FF8TextTagColor.GreyBlink: cid = ColorID.Grey; break; case FF8TextTagColor.Purple: case FF8TextTagColor.PurpleBlink: cid = ColorID.Purple; break; case FF8TextTagColor.Red: case FF8TextTagColor.RedBlink: cid = ColorID.Red; break; case FF8TextTagColor.White: case FF8TextTagColor.WhiteBlink: // since ending cid change reverts cid to white. if you have a custom cid set // this will allow reverting to that. break; case FF8TextTagColor.Yellow: case FF8TextTagColor.YellowBlink: cid = ColorID.Yellow; break; case FF8TextTagColor.Dark_Gray: case FF8TextTagColor.Dark_GrayBlink: cid = ColorID.Dark_Grey; break; } if (cid.HasValue) { color = ColorID2Color[cid.Value]; faded_color = ColorID2Blink[cid.Value]; } } private static int GetDeltaChar(byte c) => (c - 32); private static bool NewLine(Vector2 pos, int lineSpacing, ref Point real, Point size) { bool gonext; real.X = (int)pos.X; real.Y += size.Y + lineSpacing; gonext = true; return gonext; } private void DrawLetter(Type whichFont, float Fade, Color color, Rectangle destRect, Rectangle sourceRect) { switch (whichFont) { case Type.menuFont: case Type.sysfnt: // if you use Memory.SpriteBatchStartAlpha(SamplerState.PointClamp); you won't need // to trim last pixel. but it doesn't look good on low res fonts. //trim pixels to remove texture filtering artifacts. //sourceRect.Width -= 1; //sourceRect.Height -= 1; Memory.SpriteBatch.Draw(whichFont == Type.menuFont ? menuFont : sysfnt, destRect, sourceRect, color * Fade); break; case Type.sysFntBig: if (sysfntbig != null) { if (!sysfntbig.Modded) { var ShadowdestRect = new Rectangle(destRect.Location, destRect.Size); ShadowdestRect.Offset(2, 2); sysfntbig.Draw(ShadowdestRect, sourceRect, Color.Black * Fade * .5f); } sysfntbig.Draw(destRect, sourceRect, color * Fade); } break; } } #endregion Methods } }