7

我正在尝试将一些文本呈现到 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 gSmoothingMode.AntiAliasTextRenderingHint.AntiAlias设置
  • context是 a (这是 an方法的System.Web.HttpContext摘录)ProcessRequestIHttpHandler

其他方法是:

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 范围的其他部分,如西里尔文、希腊文、格鲁吉亚文和亚美尼亚文),都会表现出这种行为。我应该使用一些设置来强制Graphics.MeasureCharacterRanges只在空白字符(或连字符)处换行吗?第一个问题与2499067 后相同

其次,在放大到新的图像和字体大小时,Graphics.MeasureCharacterRanges给了我很大的高度。我在其中RectangleF绘制对应于图像的视觉明显区域,因此我可以很容易地看到文本何时减少超过必要。然而,当我给它传递一些文本时,这个GetBounds电话给我的高度几乎是它实际占用的两倍。

使用反复试验设置__minimumSize强制退出 while 循环,我可以看到 24pt 文本适合边界框,但Graphics.MeasureCharacterRanges报告该文本的高度,一旦渲染到图像,是 122px(当边界盒子高 64 像素,适合该盒子)。事实上,在不强制的情况下,while 循环迭代到 18pt,此时Graphics.MeasureCharacterRanges返回一个合适的值。

跟踪日志摘录如下:

将字体大小减小到 24,因为大小为 193×122,限制为 212 ×
64
是 212×64
将字体大小递减为 21,大小为 192×71,限制为 212×64
字体大小递减为 20,大小为 198×68,限制为 212×64
字体大小递减为 19,大小为 185 ×65,限制为 212×64
将 HESSELINK 的 VENNEGOOR 写入 DIN-Black 中 18pt,尺寸为 178×61,限制为 212×64

那么为什么Graphics.MeasureCharacterRanges给我一个错误的结果呢?我可以理解它是,例如,如果循环在 21pt 附近停止时字体的行高(如果我截取结果并在 Paint.Net 中测量它,这在视觉上很合适),但它比它应该做的要远得多因为,坦率地说,它返回了错误的结果。

4

5 回答 5

2

好吧,晚了 4 年,但这个问题完全符合我的症状,我实际上已经找出了原因。

MeasureString AND MeasureCharacterRanges 中肯定存在错误。

简单的答案是:确保将宽度限制(MeasureString 中的 int 宽度或 MeasureCharacterRanges 中 boundingRect 的 Size.Width 属性)除以 0.72。当您返回结果时,将每个维度乘以 0.72 以获得真实结果

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 方法中触发)用于英寸-> 点(* 72/100)。当您传入 ACTUAL 宽度限制时,它正在调整此值,因此 MEASURED 宽度限制实际上比它应该的要短。然后,您的文本会比预期的更早换行,因此您得到的高度结果比预期的要长。不幸的是,转换也适用于实际高度结果,因此“取消转换”该值也是一个好主意。

于 2014-11-16T01:06:31.257 回答
1

我有一个类似的问题。我想知道我要绘制的文本有多大,以及它会出现在哪里,确切地说。我没有遇到换行问题,所以我想我不能帮助你。我在使用所有可用的各种测量技术时遇到了同样的问题,包括最终使用 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;
}
于 2011-08-15T15:45:49.440 回答
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 坐标的布局矩形时存在错误。

于 2010-08-09T09:19:28.030 回答
0

您可以尝试删除以下行吗?

fmt.FormatFlags = StringFormatFlags.NoClip;

允许显示字形的悬垂部分和超出格式化矩形的展开文本。默认情况下,所有超出格式化矩形的文本和字形部分都会被剪裁。

这是我能想到的最好的:(

于 2010-04-29T08:22:03.050 回答
0

要将点数转换为屏幕分辨率的 dpi,您需要除以 72 并乘以 DPI,例如:graphics.DpiY * text.Width / 72

Red Nightengale 非常接近,因为对于屏幕分辨率,graphics.DpiY 通常是 96。

于 2018-11-10T22:38:09.613 回答