Gdi+ Graphics.MeasureCharacterRanges提供错误的大小计算
我正在尝试在Web窗体应用程序中将一些文本渲染到图像的特定部分。文本将由用户输入,因此我想改变字体大小,以确保它适合边界框 我有一些代码在我的概念验证实现中做得很好,但我现在正尝试使用设计器的资产,这些资产更大,我得到了一些奇怪的结果 我正在按如下方式进行尺寸计算:Gdi+ Graphics.MeasureCharacterRanges提供错误的大小计算,gdi+,system.drawing,drawstring,Gdi+,System.drawing,Drawstring,我正在尝试在Web窗体应用程序中将一些文本渲染到图像的特定部分。文本将由用户输入,因此我想改变字体大小,以确保它适合边界框 我有一些代码在我的概念验证实现中做得很好,但我现在正尝试使用设计器的资产,这些资产更大,我得到了一些奇怪的结果 我正在按如下方式进行尺寸计算: StringFormat fmt = new StringFormat(); fmt.Alignment = StringAlignment.Center; fmt.LineAlignment = StringAlignment.N
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());
其中:
是一个\uu字体
私有字体集合
是一个扩展方法,返回PrivateFontCollection.GetFontBySize
FontFamily
RectangleF\uuu textbundingbox=新的矩形f(150、110、212、64)代码>
int\uu minimumSize=8代码>
int\uuuu startingSize=48代码>
Brush\uu Brush=刷子。白色代码>
从48开始,并在该循环中递减int size
具有Graphics g
和SmoothingMode.AntiAlias
settextrendinghint.AntiAlias
是一个context
(这是System.Web.HttpContext
IHttpHandler的
方法的摘录)ProcessRequest
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);
}
现在我有两个问题
首先,文本有时坚持通过在单词中插入换行符来进行换行,因为换行符不合适,导致while循环再次减少。我不明白为什么Graphics.MeasureCharacterRanges
认为这适合于框中,而不应该是单词中的单词包装。无论使用何种字符集,这种行为都会表现出来(我用拉丁字母单词以及Unicode范围的其他部分,如西里尔语、希腊语、格鲁吉亚语和亚美尼亚语)。我是否应该使用一些设置来强制图形。MeasureCharacterRanges
仅以空格字符(或连字符)换行?第一个问题与第二个问题相同
其次,在扩展到新的图像和字体大小时,Graphics.MeasureCharacterRanges
让我的高度大大降低。我正在绘制的矩形f
对应于图像的一个视觉上明显的区域,因此我可以很容易地看到文本的减小量是否超过了需要。然而,当我传递一些文本时,GetBounds
调用给我的高度几乎是实际高度的两倍
使用试用和错误设置\uuu minimumSize
强制退出while循环,我可以看到24pt文本适合于边界框,但图形。MeasureCharacterRanges
报告,一旦渲染到图像,该文本的高度为122px(当边界框高为64px且适合于该框时). 实际上,在不强制执行的情况下,while循环迭代到18pt,此时Graphics.MeasureCharacterRanges
返回一个适合的值
跟踪日志摘录如下:
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());
减小字体大小至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
HESSELINK在18pt处以DIN黑色书写文脉,尺寸为178×61,极限为212×64
那么为什么
Graphics.MeasureCharacterRanges
给了我一个错误的结果呢?我可以理解,比如说,如果循环停止在21磅左右(如果我截屏结果并在Paint.Net中测量它,这将是视觉上合适的),那么它就是字体的线条高度,但这远远超出了它应该做的,因为坦率地说,它返回了错误的该死的结果。你能尝试删除下面的线条吗
fmt.FormatFlags = StringFormatFlags.NoClip;
字形的悬垂部分,以及
到达外部的未包装文本
允许格式化矩形
显示默认情况下,所有文本和图示符
到达格式之外的部分
矩形被剪裁
这是我能想到的最好的办法:(我对
MeasureCharacterRanges
方法也有一些问题。它给了我相同字符串甚至相同的图形
对象不一致的大小。然后我发现它取决于layoutRect
参数的值-我不明白为什么,在我看来这是.NET代码中的一个错误
例如,如果layoutRect
是完全空的(所有值都设置为零),我得到了字符串“a”的正确值-大小为{Width=8.898438,Height=18.10938}
使用12pt Ms无衬线字体
但是,当我将矩形的“X”属性的值设置为非整数(如1.2)时,它给出了{Width=9,Height=19}
因此,我真的认为使用非整数X坐标的布局矩形时存在错误。我有一个类似的问题。我想知道我正在绘制的文本将有多大,以及它将出现在哪里。我没有断线问题,所以我想我无法帮到你。我有与你相同的问题所有可用的各种测量技术,包括最终的MeasureCharacterRanges,它对左侧和右侧都适用,但对高度和顶部则完全不适用。(不过,在一些罕见的应用中,使用基线可以很好地工作。) 我最终得到了一个非常不雅观、低效但有效的解决方案,至少在我的用例中是这样。我在位图上绘制文本,检查位以查看它们的最终位置,这就是我的范围。因为我主要绘制小字体和短字符串,这对我来说已经足够快了
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;
}
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;
}