UltimateZoneBuilder/Source/Core/Rendering/TextLabel.cs
MaxED 2458ea3d32 Changed, internal, UDMF: local texture offsets and scale are now copied when merging sidedefs.
Changed, internal: made TextLabel and IRenderer2D public properties compatible with DB2 implementation.
Fixed some more cases when sidedefs belonging to linedefs, which were moved on top of existing linedefs, were incorrectly reassigned when applying Edit Selection and Drag Geometry modes.
Fixed, Map Analysis mode: error checks must be updated after switching map format.
2016-05-19 21:44:39 +00:00

447 lines
15 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Drawing;
using SlimDX.Direct3D9;
using SlimDX;
using CodeImp.DoomBuilder.Geometry;
using Font = System.Drawing.Font;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
public interface ITextLabel //mxd. Methods and properties required to render a textlabel
{
// Required to render text label
bool SkipRendering { get; }
Texture Texture { get; }
VertexBuffer VertexBuffer { get; }
// Access/setup
Font Font { get; }
string Text { get; set; }
PixelColor Color { get; set; }
PixelColor BackColor { get; set; }
void Update(float translatex, float translatey, float scalex, float scaley);
}
public class TextLabel : IDisposable, ID3DResource, ITextLabel
{
#region ================== Constants
#endregion
#region ================== Variables
// The text is stored as a polygon in a vertex buffer
private VertexBuffer textbuffer;
private Texture texture;
private Font font; //mxd
// Text settings
private string text;
private Vector2D location; //mxd
private bool transformcoords;
private PixelColor color;
private PixelColor backcolor;
private TextAlignmentX alignx;
private TextAlignmentY aligny;
private bool drawbg; //mxd
//mxd. Label image settings...
private SizeF textsize;
private Size texturesize;
private RectangleF textrect;
private RectangleF bgrect;
private PointF textorigin;
// This keeps track if changes were made
private bool updateneeded;
private bool textureupdateneeded; //mxd
private float lasttranslatex = float.MinValue;
private float lasttranslatey;
private float lastscalex;
private float lastscaley;
//mxd. Rendering
private bool skiprendering;
//mxd. Compatibility
private float scale;
// Disposing
private bool isdisposed;
#endregion
#region ================== Properties
// Properties
public Vector2D Location { get { return location; } set { location = value; updateneeded = true; } } //mxd
public string Text { get { return text; } set { if(text != value) { text = value; textsize = Size.Empty; textureupdateneeded = true; } } }
public Font Font { get { return font; } set { font = value; textsize = Size.Empty; textureupdateneeded = true; } } //mxd
public bool TransformCoords { get { return transformcoords; } set { transformcoords = value; updateneeded = true; } }
public SizeF TextSize { get { if(textureupdateneeded) Update(General.Map.Renderer2D.TranslateX, General.Map.Renderer2D.TranslateY, General.Map.Renderer2D.Scale, -General.Map.Renderer2D.Scale); return textsize; } }
public TextAlignmentX AlignX { get { return alignx; } set { alignx = value; updateneeded = true; } }
public TextAlignmentY AlignY { get { return aligny; } set { aligny = value; updateneeded = true; } }
public PixelColor Color { get { return color; } set { if(!color.Equals(value)) { color = value; textureupdateneeded = true; } } }
public PixelColor BackColor { get { return backcolor; } set { if(!backcolor.Equals(value)) { backcolor = value; textureupdateneeded = true; } } }
public bool DrawBackground { get { return drawbg; } set { if(drawbg != value) { drawbg = value; textureupdateneeded = true; } } } //mxd
public Texture Texture { get { return texture; } } //mxd
public VertexBuffer VertexBuffer { get { return textbuffer; } }
public bool SkipRendering { get { return skiprendering; } } //mxd
//mxd. Compatibility settings
[Obsolete("Backcolor property is deprecated, please use BackColor property instead.")]
public PixelColor Backcolor { get { return BackColor; } set { BackColor = value.WithAlpha(128); } }
[Obsolete("Scale property is deprecated, please assign the font directly using Font property instead.")]
public float Scale
{
get { return scale; }
set
{
scale = value;
font = new Font(new FontFamily(General.Settings.TextLabelFontName), (float)Math.Round(scale * 0.75f), (General.Settings.TextLabelFontBold ? FontStyle.Bold : FontStyle.Regular));
textsize = Size.Empty;
textureupdateneeded = true;
}
}
[Obsolete("Rectangle property is deprecated, please use Location property instead.")]
public RectangleF Rectangle { get { return new RectangleF(location.x, location.y, 0f, 0f); } set { location = new Vector2D(value.X, value.Y); updateneeded = true; } }
// Disposing
public bool IsDisposed { get { return isdisposed; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public TextLabel()
{
// Initialize
this.text = "";
this.font = General.Settings.TextLabelFont; //mxd
this.location = new Vector2D(); //mxd
this.color = new PixelColor(255, 255, 255, 255);
this.backcolor = new PixelColor(128, 0, 0, 0);
this.alignx = TextAlignmentX.Center;
this.aligny = TextAlignmentY.Top;
this.textsize = SizeF.Empty; //mxd
this.texturesize = Size.Empty; //mxd
this.updateneeded = true;
this.textureupdateneeded = true; //mxd
// Register as resource
General.Map.Graphics.RegisterResource(this);
// We have no destructor
GC.SuppressFinalize(this);
}
//mxd. Compatibility constructor...
[Obsolete("TextLabel(int capacity) is deprecated, please use TextLabel() instead.")]
public TextLabel(int unused)
{
// Initialize
this.text = "";
this.font = General.Settings.TextLabelFont;
this.location = new Vector2D();
this.color = new PixelColor(255, 255, 255, 255);
this.backcolor = new PixelColor(128, 0, 0, 0);
this.alignx = TextAlignmentX.Center;
this.aligny = TextAlignmentY.Top;
this.textsize = SizeF.Empty;
this.texturesize = Size.Empty;
this.updateneeded = true;
this.textureupdateneeded = true;
// Register as resource
General.Map.Graphics.RegisterResource(this);
// We have no destructor
GC.SuppressFinalize(this);
}
// Diposer
public void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
UnloadResource();
// Unregister resource
General.Map.Graphics.UnregisterResource(this);
// Done
isdisposed = true;
}
}
#endregion
#region ================== Methods
// This updates the text if needed
public void Update(float translatex, float translatey, float scalex, float scaley)
{
// Check if transformation changed and needs to be updated
if(transformcoords && (translatex != lasttranslatex || translatey != lasttranslatey ||
scalex != lastscalex || scaley != lastscaley))
{
lasttranslatex = translatex; //mxd
lasttranslatey = translatey; //mxd
lastscalex = scalex; //mxd
lastscaley = scaley; //mxd
updateneeded = true;
}
// Update if needed
if(updateneeded || textureupdateneeded)
{
// Only build when there are any vertices
if(text.Length > 0)
{
// Transform?
Vector2D abspos = (transformcoords ? location.GetTransformed(translatex, translatey, scalex, scaley) : location);
// Update text and texture sizes
if(textsize.IsEmpty || texturesize.IsEmpty)
{
textorigin = new PointF(4, 3);
textrect = new RectangleF(textorigin, General.Interface.MeasureString(text, font));
textrect.Width = (float)Math.Round(textrect.Width);
textrect.Height = (float)Math.Round(textrect.Height);
bgrect = new RectangleF(0, 0, textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2);
// Store calculated text size...
textsize = new SizeF(textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2);
// Make PO2 image, for speed and giggles...
texturesize = new Size(General.NextPowerOf2((int)textsize.Width), General.NextPowerOf2((int)textsize.Height));
switch(alignx)
{
case TextAlignmentX.Center: bgrect.X = (texturesize.Width - bgrect.Width) / 2; break;
case TextAlignmentX.Right: bgrect.X = texturesize.Width - bgrect.Width; break;
}
switch(aligny)
{
case TextAlignmentY.Middle: bgrect.Y = (texturesize.Height - bgrect.Height) / 2; break;
case TextAlignmentY.Bottom: bgrect.Y = texturesize.Height - bgrect.Height; break;
}
textrect.X += bgrect.X;
textrect.Y += bgrect.Y;
}
// Align the text horizontally
float beginx = 0;
switch(alignx)
{
case TextAlignmentX.Left: beginx = abspos.x; break;
case TextAlignmentX.Center: beginx = abspos.x - texturesize.Width * 0.5f; break;
case TextAlignmentX.Right: beginx = abspos.x - texturesize.Width; break;
}
// Align the text vertically
float beginy = 0;
switch(aligny)
{
case TextAlignmentY.Top: beginy = abspos.y; break;
case TextAlignmentY.Middle: beginy = abspos.y - texturesize.Height * 0.5f; break;
case TextAlignmentY.Bottom: beginy = abspos.y - texturesize.Height; break;
}
//mxd. Skip when not on screen...
RectangleF abssize = new RectangleF(beginx, beginy, texturesize.Width, texturesize.Height);
Size windowsize = General.Map.Graphics.RenderTarget.ClientSize;
skiprendering = (abssize.Right < 0.1f) || (abssize.Left > windowsize.Width) || (abssize.Bottom < 0.1f) || (abssize.Top > windowsize.Height);
if(skiprendering) return;
//mxd. Update texture if needed
if(textureupdateneeded)
{
// Get rid of old texture
if(texture != null)
{
texture.Dispose();
texture = null;
}
// Create label image
Bitmap img = CreateLabelImage(text, font, color, backcolor, drawbg, textrect, bgrect, texturesize, textorigin);
//texturesize = img.Size;
// Create texture
MemoryStream memstream = new MemoryStream((img.Size.Width * img.Size.Height * 4) + 4096);
img.Save(memstream, ImageFormat.Bmp);
memstream.Seek(0, SeekOrigin.Begin);
texture = Texture.FromStream(General.Map.Graphics.Device, memstream, (int)memstream.Length,
img.Size.Width, img.Size.Height, 1, Usage.None, Format.Unknown,
Pool.Managed, General.Map.Graphics.PostFilter, General.Map.Graphics.MipGenerateFilter, 0);
}
//mxd. Create the buffer
if(textbuffer == null || textbuffer.Disposed)
{
textbuffer = new VertexBuffer(General.Map.Graphics.Device, 4 * FlatVertex.Stride,
Usage.Dynamic | Usage.WriteOnly, VertexFormat.None, Pool.Default);
}
//mxd. Lock the buffer
using(DataStream stream = textbuffer.Lock(0, 4 * FlatVertex.Stride, LockFlags.Discard | LockFlags.NoSystemLock))
{
FlatQuad quad = new FlatQuad(PrimitiveType.TriangleStrip, beginx, beginy, beginx + texturesize.Width, beginy + texturesize.Height);
stream.WriteRange(quad.Vertices);
}
// Done filling the vertex buffer
textbuffer.Unlock();
}
else
{
// No faces in polygon
textsize = SizeF.Empty; //mxd
texturesize = Size.Empty; //mxd
skiprendering = true; //mxd
}
// Text updated
updateneeded = false;
textureupdateneeded = false; //mxd
}
}
//mxd
private static Bitmap CreateLabelImage(string text, Font font, PixelColor color, PixelColor backcolor, bool drawbg, RectangleF textrect, RectangleF bgrect, Size texturesize, PointF textorigin)
{
Bitmap result = new Bitmap(texturesize.Width, texturesize.Height);
using(Graphics g = Graphics.FromImage(result))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.CompositingQuality = CompositingQuality.HighQuality;
// Draw text
using(StringFormat sf = new StringFormat())
{
sf.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoWrap;
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// Draw text with BG
if(drawbg)
{
GraphicsPath p = new GraphicsPath();
float radius = textorigin.X;
const float outlinewidth = 1;
RectangleF pathrect = bgrect;
pathrect.Width -= 1;
pathrect.Height -= 1;
// Left line
p.AddLine(pathrect.Left, pathrect.Bottom - radius + outlinewidth, pathrect.Left, pathrect.Top + radius);
p.AddArc(pathrect.Left, pathrect.Top, radius, radius, 180, 90);
// Top line
p.AddLine(pathrect.Left + radius, pathrect.Top, pathrect.Right - radius, pathrect.Top);
p.AddArc(pathrect.Right - radius, pathrect.Top, radius, radius, 270, 90);
// Right line
p.AddLine(pathrect.Right, pathrect.Top + radius, pathrect.Right, pathrect.Bottom - radius);
p.AddArc(pathrect.Right - radius, pathrect.Bottom - radius, radius, radius, 0, 90);
// Bottom line
p.AddLine(pathrect.Left + radius, pathrect.Bottom, pathrect.Left + radius, pathrect.Bottom);
p.AddArc(pathrect.Left, pathrect.Bottom - radius, radius, radius, 90, 90);
// Fill'n'draw bg
using(SolidBrush brush = new SolidBrush(color.ToColor()))
g.FillPath(brush, p);
using(Pen pen = new Pen(backcolor.ToColor(), outlinewidth))
g.DrawPath(pen, p);
// Draw text
textrect.Inflate(4, 2);
using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
g.DrawString(text, font, brush, textrect, sf);
}
// Draw plain text
else
{
RectangleF plainbgrect = textrect;
if(text.Length > 1) plainbgrect.Inflate(6, 2);
RectangleF plaintextrect = textrect;
plaintextrect.Inflate(6, 4);
using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
g.FillRectangle(brush, plainbgrect);
using(SolidBrush brush = new SolidBrush(color.ToColor()))
g.DrawString(text, font, brush, plaintextrect, sf);
}
}
}
return result;
}
// This unloads the resources
public void UnloadResource()
{
// Clean up
if(textbuffer != null)
{
textbuffer.Dispose();
textbuffer = null;
}
if(texture != null) //mxd
{
texture.Dispose();
texture = null;
}
// Need to update before we can render
updateneeded = true;
textureupdateneeded = true; //mxd
}
// This (re)loads the resources
public void ReloadResource() { }
#endregion
}
}