Gdi+ Graphics.MeasureCharacterRanges提供错误的大小计算

Gdi+ Graphics.MeasureCharacterRanges提供错误的大小计算,gdi+,system.drawing,drawstring,Gdi+,System.drawing,Drawstring,我正在尝试在Web窗体应用程序中将一些文本渲染到图像的特定部分。文本将由用户输入,因此我想改变字体大小,以确保它适合边界框 我有一些代码在我的概念验证实现中做得很好,但我现在正尝试使用设计器的资产,这些资产更大,我得到了一些奇怪的结果 我正在按如下方式进行尺寸计算: StringFormat fmt = new StringFormat(); fmt.Alignment = StringAlignment.Center; fmt.LineAlignment = StringAlignment.N

我正在尝试在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());
其中:

  • \uu字体
    是一个
    私有字体集合
  • PrivateFontCollection.GetFontBySize
    是一个扩展方法,返回
    FontFamily
  • RectangleF\uuu textbundingbox=新的矩形f(150、110、212、64)
  • int\uu minimumSize=8
  • int\uuuu startingSize=48
  • Brush\uu Brush=刷子。白色
  • int size
    从48开始,并在该循环中递减
  • Graphics g
    具有
    SmoothingMode.AntiAlias
    textrendinghint.AntiAlias
    set
  • 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;
}