代码之家  ›  专栏  ›  技术社区  ›  Owen Blacker Aditya

图形.测量字符范围给出错误的尺寸计算

  •  6
  • Owen Blacker Aditya  · 技术社区  · 14 年前

    我正在尝试在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 方法 IHttpHandler )

    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范围的其他部分,比如西里尔语、希腊语、格鲁吉亚语和亚美尼亚语)。有什么我应该用来强迫的设置吗 图形.测量字符范围 只能用空格字符(或连字符)换行吗?第一个问题与 post 2499067 .

    图形.测量字符范围 RectangleF 我正在绘制一个与图像的可视区域相对应的区域,这样我就可以很容易地看到文本的减量是否超过了需要。但是当我给它一些文本时 GetBounds

    使用试错法设置 __minimumSize 为了强制退出while循环,我可以看到24pt文本适合于边界框 正在报告该文本的高度,一旦渲染到图像中,即为122px(当边界框的高度为64px且适合该框时)。实际上,在不强制执行的情况下,while循环迭代到18pt,此时 返回适合的值。

    跟踪日志摘录如下:

    减小字体大小到24,因为大小是193122,限制是21264
    减小字体大小到23,因为大小是191117,限制是21264
    减小字体大小到22,因为大小是20075,限制是21264
    减小字体大小到21,因为大小是19271,限制是21264

    减小字体大小到19,因为大小是18565,限制是21264
    HESSELINK的VENNEGOOR字体为DIN黑色,18pt,尺寸为17861,极限为21264

    图形.测量字符范围

    5 回复  |  直到 6 年前
        1
  •  1
  •   user12861    13 年前

    我也有类似的问题。我想知道我要画的文字会有多大,它会出现在哪里,确切地说。我没有断线的问题,所以我想我在这方面帮不了你。我遇到了与您相同的问题,所有可用的各种测量技术,包括最终使用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;
    }
    
        2
  •  1
  •   Red Nightingale    10 年前

    MeasureString和MeasureCharacterRanges中肯定有一个bug。

    答案很简单: 确保将宽度限制(度量值中的int width或尺寸、宽度MeasureCharacterRanges中boundingRect的属性。当你得到你的结果时,将每个维度乘以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;
    }
    

    解释(我可以理解)是,与上下文有关的内容触发了inch->point(*72/100)的度量方法转换(在DrawString方法中不触发)。当您传入实际宽度限制时,它正在调整此值,因此测量的宽度限制实际上比应该的短。然后,文本比预期的更早换行,因此得到的高度结果比预期的要长。不幸的是,转换也适用于实际高度结果,因此最好也“取消输入”该值。

        3
  •  0
  •   Codesleuth    14 年前

    你能试着把下面这行去掉吗?

    fmt.FormatFlags = StringFormatFlags.NoClip;
    

    字形的突出部分,以及 展开的文本到达 允许格式化矩形 表演。默认情况下,所有文本和glyph 剪切矩形。

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

        4
  •  0
  •   Pavel    14 年前

    我也有一些问题 MeasureCharacterRanges 方法。它给了我不一致的大小为同一字符串,甚至相同的 Graphics 对象。然后我发现这取决于 layoutRect

    例如,如果 布局 完全为空(所有值都设置为零),我得到了字符串“a”的正确值-大小为 {Width=8.898438, Height=18.10938}

    {Width=9, Height=19} .

    所以我真的认为使用非整数X坐标的布局矩形时有一个bug。

        5
  •  0
  •   JBStCyr    6 年前

    要按屏幕分辨率将点转换为dpi,需要除以72再乘以dpi,例如: