C#使用GDI+实现矩形区域内旋转文字
在 C# 绘图开发中,常需在受限容器内展示特定角度的文本。由于原生 Graphics.DrawString 仅支持基于固定点的旋转,难以满足矩形区域内的自适应排版需求。本文提供一套完整的解决方案,通过封装 GraphicsText 类,自动处理矩阵变换、包围盒计算及坐标映射,从而精准实现任意角度、任意对齐方式的矩形内旋转文字效果。
核心原理与实现思路
GDI+ 原生绘制旋转文字时,若直接设置旋转变换,文本将围绕原点或指定点旋转,容易超出预设的边界框。为解决此问题,我们需要在绘制前进行两步关键预处理:
- 尺寸转换:根据原始字体大小和旋转角度,计算旋转后的最小外接矩形(包围盒)尺寸。
- 坐标映射:根据目标矩形区域和对齐方式(如居中、靠上、靠左),反推出合适的旋转中心点。
通过上述两步,我们将“矩形内旋转”转化为标准的“定点旋转”,从而利用成熟的矩阵变换机制完成绘制。
准备工作
本示例基于 .NET Framework 或 .NET Core/5+ 环境,需引入以下命名空间以支持图形操作:
System.Drawing:基础绘图结构。System.Drawing.Drawing2D:高级图形功能,包括Matrix和StringFormat。System.Windows.Forms:用于构建测试窗口(可选)。
第一步:实现 GraphicsText 封装类
该类内部包含三个核心方法:ConvertSize 用于计算旋转后的尺寸变化;GetRotatePoint 用于确定对齐所需的旋转中心;主方法 DrawString 负责执行最终的绘制动作。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace RotateText
{
public class GraphicsText
{
private Graphics _graphics;
public Graphics Graphics
{
get { return _graphics; }
set { _graphics = value; }
}
/// <summary>
/// 绘制矩形区域内旋转的文本
/// </summary>
public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format, float angle)
{
// 1. 获取未旋转时的字符串尺寸
SizeF size = _graphics.MeasureString(s, font);
// 2. 计算旋转角度下的新包围盒尺寸
SizeF sizeRotate = ConvertSize(size, angle);
// 3. 根据布局矩形和对齐方式,计算实际旋转中心点
PointF rotatePt = GetRotatePoint(sizeRotate, layoutRectangle, format);
// 4. 重置格式为居中,以便后续定点绘制
StringFormat newFormat = new StringFormat(format);
newFormat.Alignment = StringAlignment.Center;
newFormat.LineAlignment = StringAlignment.Center;
// 5. 执行基于点的绘制
DrawString(s, font, brush, rotatePt, newFormat, angle);
}
/// <summary>
/// 基于特定点绘制旋转文本
/// </summary>
public void DrawString(string s, Font font, Brush brush, PointF point, StringFormat format, float angle)
{
// 保存当前状态以防影响其他绘图操作
Matrix mtxSave = _graphics.Transform;
try
{
// 创建旋转变换矩阵并应用到 Graphics 对象
Matrix mtxRotate = new Matrix();
mtxRotate.RotateAt(angle, point);
_graphics.Transform = mtxRotate;
// 在变换后的坐标系中绘制,此时point即为视觉中心
_graphics.DrawString(s, font, brush, point, format);
}
finally
{
// 恢复原始变换矩阵
_graphics.Transform = mtxSave;
}
}
/// <summary>
/// 计算文本旋转后的外接矩形大小
/// </summary>
private SizeF ConvertSize(SizeF size, float angle)
{
Matrix matrix = new Matrix();
matrix.Rotate(angle);
// 定义原始矩形的四个顶点(相对于中心)
PointF[] pts = new PointF[4];
pts[0].X = -size.Width / 2f; pts[0].Y = -size.Height / 2f;
pts[1].X = -size.Width / 2f; pts[1].Y = size.Height / 2f;
pts[2].X = size.Width / 2f; pts[2].Y = size.Height / 2f;
pts[3].X = size.Width / 2f; pts[3].Y = -size.Height / 2f;
// 应用旋转变换
matrix.TransformPoints(pts);
// 计算变换后所有顶点的边界范围
float left = float.MaxValue;
float right = float.MinValue;
float top = float.MaxValue;
float bottom = float.MinValue;
foreach (PointF pt in pts)
{
if (pt.X < left) left = pt.X;
if (pt.X > right) right = pt.X;
if (pt.Y < top) top = pt.Y;
if (pt.Y > bottom) bottom = pt.Y;
}
return new SizeF(right - left, bottom - top);
}
/// <summary>
/// 根据目标矩形和对齐方式计算旋转中心
/// </summary>
private PointF GetRotatePoint(SizeF size, RectangleF layoutRectangle, StringFormat format)
{
PointF pt = new PointF();
// 处理水平对齐
switch (format.Alignment)
{
case StringAlignment.Near: // 左对齐
pt.X = layoutRectangle.Left + size.Width / 2f;
break;
case StringAlignment.Center: // 居中
pt.X = (layoutRectangle.Left + layoutRectangle.Right) / 2f;
break;
case StringAlignment.Far: // 右对齐
pt.X = layoutRectangle.Right - size.Width / 2f;
break;
}
// 处理垂直对齐
switch (format.LineAlignment)
{
case StringAlignment.Near: // 顶端对齐
pt.Y = layoutRectangle.Top + size.Height / 2f;
break;
case StringAlignment.Center: // 垂直居中
pt.Y = (layoutRectangle.Top + layoutRectangle.Bottom) / 2f;
break;
case StringAlignment.Far: // 底端对齐
pt.Y = layoutRectangle.Bottom - size.Height / 2f;
break;
}
return pt;
}
}
}
第二步:集成到 Windows Form 测试用例
为了验证效果,我们在 Form 的 OnPaint 事件中调用上述类。示例涵盖了两种典型场景:一是简单的绕点旋转,二是严格限制在矩形区域内的旋转排版。
using System;
using System.Drawing;
using System.Windows.Forms;
namespace RotateText
{
public partial class FormMain : Form
{
private Font _font = new Font("Arial", 12);
private Brush _brush = new SolidBrush(Color.Black);
private Pen _pen = new Pen(Color.Black, 1f);
private string _text = "Crow Soft";
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
GraphicsText graphicsText = new GraphicsText();
graphicsText.Graphics = e.Graphics;
// 场景1:基于特定点的自由旋转
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
graphicsText.DrawString(_text, _font, _brush, new PointF(100, 80), format, 45f);
graphicsText.DrawString(_text, _font, _brush, new PointF(200, 80), format, -45f);
// 场景2:矩形区域内的旋转排版
// 第一行示例
RectangleF rect = RectangleF.FromLTRB(50, 150, 200, 230);
// 左上对齐 + 30度旋转
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
e.Graphics.DrawRectangle(_pen, rect.Left, rect.Top, rect.Width, rect.Height);
graphicsText.DrawString(_text, _font, _brush, rect, format, 30f);
// 右上对齐 + 负30度旋转
rect.Offset(180, 0);
format.Alignment = StringAlignment.Far;
e.Graphics.DrawRectangle(_pen, rect.Left, rect.Top, rect.Width, rect.Height);
graphicsText.DrawString(_text, _font, _brush, rect, format, -30f);
// ... 此处省略部分重复代码,实际使用中可遍历配置生成更多案例
}
}
}
运行上述代码,您将看到文本严格按照设定的矩形边界和角度分布。具体渲染效果如下所示:
完成判断与注意事项
- 资源释放:示例中的
Pen、Brush和Font属于非托管资源,在实际生产环境中应确保调用Dispose()方法或在using语句块中使用,以防止内存泄漏。 - 性能优化:
MeasureString操作相对耗时,建议在批量绘制相同字体大小的文本时,复用已计算的尺寸结果。 - 精度问题:浮点数运算可能导致像素级偏差,对于对边缘对齐要求极高的 UI 组件,建议开启
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









