2010-04-21 3 views
5

Я пытаюсь отобразить текст в определенной части изображения в приложении Web Forms. Текст будет введен пользователем, поэтому я хочу изменить размер шрифта, чтобы он соответствовал рамке ограничения.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()); 

то я использую следующую строку для отображения текста на изображение Я тянущий из файловой системы:

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, таких как кириллица, греческий, грузинский и армянский). Есть ли какая-то настройка, которую я должен использовать, чтобы заставить Graphics.MeasureCharacterRanges быть только переносом слов на символы пробела (или дефисы)? Эта первая проблема такая же, как у post 2499067.

Во-вторых, при масштабировании до нового изображения и размера шрифта Graphics.MeasureCharacterRanges дает мне высоты, которые дико отключены. RectangleF Я рисую внутри, соответствует визуально видимой области изображения, поэтому я могу легко увидеть, когда текст декрементируется больше, чем это необходимо. Но когда я передаю ему какой-то текст, звонок GetBounds дает мне высоту, которая почти вдвое больше, чем она на самом деле берет.

Используя пробную версию и ошибку, чтобы установить __minimumSize для принудительного выхода из цикла while, я вижу, что текст 24pt помещается в ограничительную рамку, но Graphics.MeasureCharacterRanges сообщает, что высота этого текста, после визуализации изображения, составляет 122px (когда ограничивающий прямоугольник имеет высоту 64px и он помещается внутри этого поля). Действительно, не заставляя дело, цикл while повторяется до 18pt, после чего Graphics.MeasureCharacterRanges возвращает значение, которое подходит.

Журнал трассировки отрывок следующим образом:

Уменьшение номера размера шрифта до 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
Decre альный размер шрифта до 19, а размер 185 × 65 и предел 212 × 64
Записи Веннегора из Гесселинка в DIN-черном на 18 пунктах, размер 178 × 61 и предел 212 × 64

Так почему Graphics.MeasureCharacterRanges дает мне неправильный результат? Я мог понять, что это, скажем, высота строки шрифта, если цикл остановился около 21pt (что бы визуально соответствовало, если бы я снимал снимки результатов и измерял их в Paint.Net), но он продвигается намного дальше, чем он должен делать потому что, честно говоря, он возвращает неправильные результаты.

+0

+1 для одного из самых документированных вопросов, которые я видел через некоторое время! – SouthShoreAK

ответ

0

Не могли бы вы попытаться удалить следующую строку?

fmt.FormatFlags = StringFormatFlags.NoClip; 

консольные части глифов, и развернутый текст, достигающие вне прямоугольника форматирования разрешены шоу. По умолчанию весь текст и глиф частей, выходящих за рамки форматирования прямоугольник обрезаны.

Это лучшее, что я могу придумать для этого :(

+0

Спасибо за сообщение. Я действительно смотрел на это, подумав, что это может быть проблема, но сообщаемая высота почти вдвое превышает фактическую высоту, поэтому я не думаю, что это может быть так. Похоже, что отличие StringFormatFlags.NoClip заключается в том, что если чаша буквы P (например) просто выдает за пределы ограничивающей рамки, тогда ее можно отображать, а не обрезать. Это, похоже, не проблема, с которой я столкнулся. Но спасибо: o) –

0

Я также имел некоторые проблемы с методом MeasureCharacterRanges. Это дает мне несогласованные размеры для одной и той же строки, и даже один и тот же Graphics объект. Тогда Я обнаружил, что это зависит от значения layoutRect Parametr - Я не могу понять, почему, на мой взгляд, это ошибка в коде .NET

Например, если layoutRect был совершенно пуст (все значения установлены в ноль). , Я получил правильные значения для строки «a» - размер был {Width=8.898438, Height=18.10938} с использованием 12pt Ms Sans Serif шрифт.

Однако, когда я установил значение свойства «X» прямоугольника на нецелое число (например, 1.2), он дал мне {Width=9, Height=19}.

Так что я действительно думаю, что есть ошибка, когда вы используете прямоугольник макета с нецелым координатом X.

+0

Интересно - похоже, что он всегда округляет ваш размер вверх. К сожалению, мой layoutRect всегда интегрален - он определяется как private static readonly RectangleF __textBoundingBox = new RectangleF (150, 110, 212, 64); и его значение никогда не изменяется (очевидно, поскольку оно отмечено только для чтения). Это определенно ошибка в коде .Net, но это не похоже на вашу ошибку, и моя ошибка такая же. –

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; 
} 
+0

Хорошая работа! Я должен буду попробовать попробовать и посмотреть, решит ли это мою проблему. Хотя мне немного не нравится полагаться на «небезопасный» код. Как быстро вы находите этот код?Тот факт, что я использую его, проверяет, является ли текст слишком большим для ограничивающего прямоугольника с фиксированным размером и уменьшает размер шрифта в цикле 'while' (проверка' while too large или size gt hard-floor-limit') поэтому я не хочу выполнять дорогостоящую операцию в этом цикле 'while' ... –

+0

Скорость сильно зависит от размера шрифта. Он выделяет растровое изображение, отображает его, затем ищет границы. Меньшие шрифты намного быстрее, и вы теряете с большими шрифтами что-то вроде O (размер^2), я думаю. Может захотеть угадать небольшой размер и работать до размера, который слишком велик, а не наоборот. Вы можете использовать MeasureCharacterRanges в качестве первого предположения для нижней границы, так как оно всегда кажется слишком маленьким. Там много места для умного, ищущего нужный размер, двоичный поиск с умными угадываниями и т. Д. Мне не нужно что-либо из этого для моей цели, поэтому я не играл с ним. – user12861

+0

Что касается небезопасного, вы можете сделать это без этого, но скорость получает удар. Как прокомментировано, это почти на 90% быстрее, чем использование GetPixel, но вы все равно можете делать между ними метод LockBits без небезопасного кода. Я не пробовал это, так как я был в порядке с небезопасным кодом и хотел сжать немного легко получившейся производительности. Есть много способов попытаться выжать производительность в зависимости от того, как именно вы используете этот материал, но все они сложны. Вся эта вещь отвратительна, я полностью понимаю. Я бы подумал, что будет лучший способ, но мой поиск в Интернете был таким же непродуктивным, как и ваш. – user12861

0

Хорошо, так что 4 года спустя, но этот вопрос ТОЧНО соответствовал моим симптомам, и я действительно разработал причину.

Существует, безусловно, ошибка в MeasureString AND MeasureCharacterRanges.

Простой ответ: Убедитесь, что вы разделили ограничение ширины (int width в MeasureString или свойство Size.Width объекта boundingRect в MeasureCharacterRanges) на 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; 
} 

Объяснение (что я могу понять), что-то делать с контекстом запуск преобразование в методах измерения (которое не запускается в методе DrawString) для дюймовой точки (* 72/100). Когда вы передаете ограничение ширины ACTUAL, оно корректирует это значение, поэтому ограничение ширины MEASURED, по сути, короче, чем должно быть. Затем ваш текст обертывается раньше, чем предполагается, и поэтому вы получаете более высокий результат роста, чем ожидалось. К сожалению, преобразование относится и к фактическому результату роста, так что неплохо также «отменить» это значение.

+0

На ДЕЙСТВИТЕЛЬНО раздражающей стороне примечание, у меня нет абсолютно никакой идеи, почему MeasureString принимает int для ширины. Если вы настроите единицу измерения графического контекста на дюймы, то (из-за int) вы можете установить только ограничение ширины на 1 дюйм или 2 дюйма и т. Д. Совершенно смешно! –

Смежные вопросы