我正在尝试将一些文本渲染到Web窗体应用程序中图像的特定部分。文本将由用户输入,因此我想改变字体大小以确保它适合边界框。
我的代码在我的概念验证实现上做得很好,但我现在正在尝试对付设计师的资产,这些资产更大,而且我得到了一些奇怪的结果。
我正在运行大小计算,如下所示:
StringFormat fmt = new StringFormat();
fmt.Alignment = StringAlignment.Center;
fmt.LineAlignment = StringAlignment.Near;
fmt.FormatFlags = StringFormatFlags.NoClip;
fmt.Trimming = StringTrimming.None;
int size = __startingSize;
Font font = __fonts.GetFontBySize(size);
while (GetStringBounds(text, font, fmt).IsLargerThan(__textBoundingBox))
{
context.Trace.Write("MyHandler.ProcessRequest",
"Decrementing font size to " + size + ", as size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
size--;
if (size < __minimumSize)
{
break;
}
font = __fonts.GetFontBySize(size);
}
context.Trace.Write("MyHandler.ProcessRequest", "Writing " + text + " in "
+ font.FontFamily.Name + " at " + font.SizeInPoints + "pt, size is "
+ GetStringBounds(text, font, fmt).Size()
+ " and limit is " + __textBoundingBox.Size());
然后我使用以下行将文本渲染到我从文件系统中提取的图像上:
g.DrawString(text, font, __brush, __textBoundingBox, fmt);
其中:
__fonts
是PrivateFontCollection
,PrivateFontCollection.GetFontBySize
是一种返回FontFamily
RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
int __minimumSize = 8;
int __startingSize = 48;
Brush __brush = Brushes.White;
int size
从48开始,在该循环内减少Graphics g
有SmoothingMode.AntiAlias
和TextRenderingHint.AntiAlias
设置context
是System.Web.HttpContext
(这是ProcessRequest
的{{1}}方法的摘录)其他方法是:
IHttpHandler
现在我有两个问题。
首先,文本有时通过在单词中插入换行符来坚持换行,当它应该不适合并导致while循环再次递减时。我不明白为什么private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange[] range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(new Bitmap(
(int) __textBoundingBox.Width - 1,
(int) __textBoundingBox.Height - 1)))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Region[] regions = g.MeasureCharacterRanges(text, font,
__textBoundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
public static string Size(this RectangleF rect)
{
return rect.Width + "×" + rect.Height;
}
public static bool IsLargerThan(this RectangleF a, RectangleF b)
{
return (a.Width > b.Width) || (a.Height > b.Height);
}
认为当它不应该在一个单词中包装时,这适合于框内。无论使用何种字符集,我都会表现出这种行为(我用拉丁字母词,以及Unicode范围的其他部分,如西里尔语,希腊语,格鲁吉亚语和亚美尼亚语)。是否有一些设置我应该使用强制Graphics.MeasureCharacterRanges
只是在空格字符(或连字符)上换行?第一个问题与post 2499067相同。
其次,在扩展到新的图像和字体大小时,Graphics.MeasureCharacterRanges
给了我极高的高度。我正在绘制的Graphics.MeasureCharacterRanges
对应于图像的视觉上明显的区域,因此我可以很容易地看到文本的减少时间超过了必要的范围。然而,当我传递一些文字时,RectangleF
电话给我的高度几乎是它实际拍摄的两倍。
使用试验和错误设置GetBounds
强制退出while循环,我可以看到24pt文本适合边界框,但__minimumSize
报告该文本的高度,一旦渲染到图像,是122px(当边界框高64像素,它适合该框)。实际上,在没有强制要求的情况下,while循环迭代到18pt,此时Graphics.MeasureCharacterRanges
返回一个适合的值。
跟踪日志摘录如下:
将字体大小减小到24,因为大小为193×122,限制为212×64
将字体大小减小到23,因为大小为191×117,限制为212×64
将字体大小减小到22,因为大小为200×75,限制为212×64
将字体大小减小为21,因为大小为192×71,限制为212×64
将字体大小减小到20,因为大小为198×68,限制为212×64
将字体大小减小到19,因为大小为185×65,限制为212×64
以18pt在DIN-Black中编写HESSELINK的VENNEGOOR,尺寸为178×61,限制为212×64
那么为什么Graphics.MeasureCharacterRanges
给我一个错误的结果呢?我可以理解,如果循环停止在21pt附近(如果我在Paint.Net中截取结果并测量它,在视觉上合适,那么字体的行高),但它远远超过应该做的因为,坦白说,这是错误的结果。
答案 0 :(得分:1)
我有类似的问题。我想知道我正在绘制的文本有多大,以及它将在何处出现,确切地说。我没有断线问题,所以我认为我不能帮助你。我遇到了与所有可用的各种测量技术相同的问题,包括最终使用MeasureCharacterRanges,这对于左侧和右侧都可以正常工作,但对于高度和顶部都没有。 (使用基线可以很好地适用于一些罕见的应用程序。)
我最终得到了一个非常不优雅,低效但有效的解决方案,至少对我的用例而言。我在位图上绘制文本,检查位以查看它们的结束位置,这就是我的范围。因为我主要是绘制小字体和短字符串,所以它对我来说足够快(特别是我添加的memoization)。也许这不是你所需要的,但也许它可以引导你走上正确的轨道。
请注意,目前需要编译项目以允许不安全的代码,因为我正在尝试从中挤出所有效率,但如果您愿意,可以删除该约束。此外,它不像现在那样是线程安全的,你可以轻松添加它,如果你需要它。
Dictionary<Tuple<string, Font, Brush>, Rectangle> cachedTextBounds = new Dictionary<Tuple<string, Font, Brush>, Rectangle>();
/// <summary>
/// Determines bounds of some text by actually drawing the text to a bitmap and
/// reading the bits to see where it ended up. Bounds assume you draw at 0, 0. If
/// drawing elsewhere, you can easily offset the resulting rectangle appropriately.
/// </summary>
/// <param name="text">The text to be drawn</param>
/// <param name="font">The font to use when drawing the text</param>
/// <param name="brush">The brush to be used when drawing the text</param>
/// <returns>The bounding rectangle of the rendered text</returns>
private unsafe Rectangle RenderedTextBounds(string text, Font font, Brush brush) {
// First check memoization
Tuple<string, Font, Brush> t = new Tuple<string, Font, Brush>(text, font, brush);
try {
return cachedTextBounds[t];
}
catch(KeyNotFoundException) {
// not cached
}
// Draw the string on a bitmap
Rectangle bounds = new Rectangle();
Size approxSize = TextRenderer.MeasureText(text, font);
using(Bitmap bitmap = new Bitmap((int)(approxSize.Width*1.5), (int)(approxSize.Height*1.5))) {
using(Graphics g = Graphics.FromImage(bitmap))
g.DrawString(text, font, brush, 0, 0);
// Unsafe LockBits code takes a bit over 10% of time compared to safe GetPixel code
BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* row = (byte*)bd.Scan0;
// Find left, looking for first bit that has a non-zero alpha channel, so it's not clear
for(int x = 0; x < bitmap.Width; x++)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.X = x;
goto foundX;
}
foundX:
// Right
for(int x = bitmap.Width - 1; x >= 0; x--)
for(int y = 0; y < bitmap.Height; y++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Width = x - bounds.X + 1;
goto foundWidth;
}
foundWidth:
// Top
for(int y = 0; y < bitmap.Height; y++)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Y = y;
goto foundY;
}
foundY:
// Bottom
for(int y = bitmap.Height - 1; y >= 0; y--)
for(int x = 0; x < bitmap.Width; x++)
if(((byte*)bd.Scan0)[y*bd.Stride + 4*x + 3] != 0) {
bounds.Height = y - bounds.Y + 1;
goto foundHeight;
}
foundHeight:
bitmap.UnlockBits(bd);
}
cachedTextBounds[t] = bounds;
return bounds;
}
答案 1 :(得分:0)
您可以尝试删除以下行吗?
fmt.FormatFlags = StringFormatFlags.NoClip;
突出部分字形,和 打开文本到达外面 允许格式化矩形 节目。默认情况下所有文字和字形 零件到达格式之外 矩形被剪裁。
这是我能想到的最好的:(
答案 2 :(得分:0)
我也遇到了MeasureCharacterRanges
方法的一些问题。它给了我不同大小的相同字符串甚至相同的Graphics
对象。然后我发现它取决于layoutRect
参数的值 - 我看不出原因,在我看来这是.NET代码中的一个错误。
例如,如果layoutRect
完全为空(所有值都设置为零),我得到字符串“a”的正确值 - 大小为{Width=8.898438, Height=18.10938}
,使用12pt Ms Sans Serif字体。
但是,当我将矩形的'X'属性值设置为非整数(如1.2)时,它给了我{Width=9, Height=19}
。
所以我认为使用非整数X坐标的布局矩形时会出现错误。
答案 3 :(得分:0)
好的,所以晚了4年,但这个问题与我的症状完全吻合,我实际上已经找到了原因。
MeasureString和MeasureCharacterRanges中肯定存在一个错误。
简单的答案是: 确保将宽度限制(MeasureString中的int宽度或MeasureCharacterRanges中boundingRect的Size.Width属性)除以0.72。当你得到结果时,将每个维度乘以0.72得到REAL结果
int measureWidth = Convert.ToInt32((float)width/0.72);
SizeF measureSize = gfx.MeasureString(text, font, measureWidth, format);
float actualHeight = measureSize.Height * (float)0.72;
或
float measureWidth = width/0.72;
Region[] regions = gfx.MeasureCharacterRanges(text, font, new RectangleF(0,0,measureWidth, format);
float actualHeight = 0;
if(regions.Length>0)
{
actualHeight = regions[0].GetBounds(gfx).Size.Height * (float)0.72;
}
解释(我可以弄清楚)是与上下文有关的事情是触发Measure方法中的转换(不会在DrawString方法中触发)为inch-&gt;点(* 72/100) )。当您传入ACTUAL宽度限制时,它正在调整此值,因此MEASURED宽度限制实际上比它应该更短。然后您的文本会比预期更早地包裹,因此您获得的结果比预期的更长。不幸的是,转换也适用于实际的高度结果,因此最好“转换”该值。
答案 4 :(得分:0)
要将屏幕分辨率从点转换为dpi,您需要除以72再乘以DPI,例如: graphics.DpiY * text.Width / 72
红色Nightengale真的很近,因为图形。对于屏幕分辨率,DpiY通常为96。