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);

どこ:

  • __fontsPrivateFontCollection
  • PrivateFontCollection.GetFontBySizeを返す拡張メソッドですFontFamily
  • RectangleF __textBoundingBox = new RectangleF(150, 110, 212, 64);
  • int __minimumSize = 8;
  • int __startingSize = 48;
  • Brush __brush = Brushes.White;
  • int size48から始まり、そのループ内で減少します
  • Graphics g持っSmoothingMode.AntiAliasTextRenderingHint.AntiAlias設定
  • contextは(これはのメソッド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);
}

今、私は2つの問題を抱えています。

まず、テキストが単語内に改行を挿入して折り返すことを要求することがありますが、これはちょうど収まらず、whileループが再びデクリメントする必要がありますGraphics.MeasureCharacterRanges単語内でワードラップするべきではないのに、これがボックス内に収まると考える理由がわかりません。この動作は、使用されている文字セットに関係なく示されます(ラテンアルファベットの単語、およびキリル文字、ギリシャ文字、グルジア文字、アルメニア文字などのUnicode範囲の他の部分で取得します)。Graphics.MeasureCharacterRanges空白文字(またはハイフン)でのみワードラップするように強制するために使用する必要がある設定はありますか?この最初の問題は、ポスト2499067と同じです。

第二に、新しい画像とフォントサイズにスケールアップすると、Graphics.MeasureCharacterRanges高さが大幅にずれてしまいます。私が描いているのRectangleFは画像の視覚的に見える領域に対応しているので、テキストが必要以上にデクリメントされていることを簡単に確認できます。しかし、私がそれにいくつかのテキストを渡すとき、GetBounds呼び出しは私にそれが実際に取っているもののほぼ2倍の高さを与えています。

試行錯誤を使用し__minimumSizeて、whileループを強制的に終了するように設定すると、24ポイントのテキストがバウンディングボックス内に収まることがわかりますGraphics.MeasureCharacterRangesが、画像にレンダリングされたテキストの高さは122px(バウンディング時)であると報告されていますボックスの高さは64ピクセルで、そのボックス内に収まります)。実際、問題を強制することなく、whileループは18ptまで繰り返され、その時点で適切Graphics.MeasureCharacterRangesな値が返されます。

トレースログの抜粋は次のとおりです。

サイズが193×122で制限が212×64であるため、フォントサイズを24に
デクリメントします。サイズが191×117で制限が212×64であるため、フォントサイズを23に
デクリメントします。サイズが200×75で制限があるため、フォントサイズを22にデクリメントします。は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私に間違った結果を与えるのですか?たとえば、ループが21ポイント前後で停止した場合のフォントの行の高さは理解できますが(結果をスクリーンショットしてPaint.Netで測定すると、視覚的にフィットします)、本来よりもはるかに進んでいます。率直に言って、それは間違った気の結果を返しているからです。

4

5 に答える 5

2

4年遅れましたが、この質問は私の症状と完全に一致し、実際に原因を突き止めました.

MeasureString AND 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メソッドではトリガーされない)でインチ->ポイント(* 72/100)の変換をトリガーしていることです。実際の幅の制限を渡すと、この値が調整されるため、測定された幅の制限は実際には本来よりも短くなります。テキストは想定よりも早く折り返されるため、予想よりも長い高さの結果が得られます。残念ながら、変換は実際の高さの結果にも適用されるため、その値も「変換しない」ことをお勧めします。

于 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

次の行を削除してみてください。

fmt.FormatFlags = StringFormatFlags.NoClip;

グリフのオーバーハング部分、および書式設定長方形の外側に到達するラップされていないテキストを表示できます。デフォルトでは、書式設定長方形の外側に到達するすべてのテキストとグリフ部分がクリップされます。

それは私がこれのために思いつくことができる最高です:(

于 2010-04-29T08:22:03.050 に答える
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

画面解像度のようにポイントを dpi に変換するには、72 で割り、DPI を掛ける必要があります。たとえば、graphics.DpiY * text.Width / 72 です。

グラフィックス.DpiY は通常画面解像度で 96 であるため、Red Nightengale は非常に近いものでした。

于 2018-11-10T22:38:09.613 に答える