Как измерить ширину цифры в пикселях в заданном шрифте/размере (C#)

Я пытаюсь рассчитать ширину столбцов Excel в пикселях, как описано в это сообщение, используя официальную формулу из спецификации OpenXML. Однако, чтобы применить эту формулу, мне нужно знать Максимальную ширину цифры Обычного шрифта, которая представляет собой ширину самой широкой цифры в пикселях. Спецификация OpenXML дает этот пример в качестве пояснения:

На примере шрифта Calibri максимальная ширина цифр шрифта размером 11 пунктов составляет 7 пикселей (при разрешении 96 точек на дюйм).

Я проверил, что это правильно, визуально изучив 11-точечную цифру Calibri, и она действительно имеет ширину 7 пикселей. Итак, я пытаюсь создать метод, который будет возвращать максимальную ширину цифры для любого шрифта/размера.

Я выполнил рекомендации, сделанные в этом вопросе, но это не дает результатов, которых я ожидаю.

Вот мой тестовый код:

var font = new Font("Calibri", 11.0f, FontStyle.Regular);

for (var i = 0; i < 10; i++)
{
    Debug.WriteLine(TextRenderer.MeasureText(i.ToString(), font));
}

Это сообщает, что все цифры имеют ширину 15.

Любые предложения, пожалуйста?

Спасибо, Тим


person Tim Coulter    schedule 02.12.2009    source источник


Ответы (5)


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

Я внес небольшое изменение в ваш код. Я измеряю длину двух символов подчеркивания. Затем в теле цикла for я измеряю нужный символ символами подчеркивания с обеих сторон и вычитаю длину только двух символов подчеркивания. Для тестового сценария я получаю результат 7. Основная цель этого состояла в том, чтобы определить, учитывает ли MeasureText заполнение на посимвольной основе или заполнение в начале/конце строки. Кажется, что это последнее. Возможно, это, в сочетании с некоторыми другими полученными вами данными, приведет вас к более элегантному решению.

var font = new Font("Calibri", 11.0f, FontStyle.Regular);
int underscoreWidth = TextRenderer.MeasureText("__", font).Width; 
for (var i = 0; i < 10; i++)
   {
      int charWidth = TextRenderer.MeasureText(String.Concat("_", i.ToString(), "_"), font).Width - underscoreWidth;
      Debug.WriteLine(charWidth);
   }
person lJohnson    schedule 02.12.2009
comment
Спасибо за это решение. Как вы сказали, это немного неортодоксально, но на самом деле дает правильный результат, который больше, чем я добился за последние четыре часа :). Дополнительным преимуществом моего приложения является то, что оно делает это без необходимости в объекте Graphics. Еще раз спасибо. - person Tim Coulter; 02.12.2009

TextRenderer не всегда точен, как вы ожидаете:

Из MSDN: http://msdn.microsoft.com/en-us/library/8wafk2kt.aspx

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

У вас есть объект Graphics или объект управления? Если вы используете их, вы можете получить точные результаты так, как вы ожидаете.

Взгляните на http://msdn.microsoft.com/en-us/library/6xe5hazb(VS.80).aspx

person Bob    schedule 02.12.2009
comment
Будьте осторожны с MeasureString — он также дополняет: метод MeasureString предназначен для использования с отдельными строками и включает небольшое количество дополнительного пространства до и после строки, чтобы обеспечить нависающие глифы. Также имейте в виду, что измерение отдельного глифа может не дать точных результатов вне контекста. Сумма ширин символов строки не всегда равна ширине строки из-за кернинга. - person plinth; 02.12.2009
comment
@plinth: Да, я попробовал свой код с помощью MeasureString, и он сообщает, что 7-пиксельные цифры имеют ширину 9,408365 пикселей. - person Tim Coulter; 02.12.2009

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

        float size = 5;
        for (int ix = 0; ix < 10; ++ix) {
            var font = new Font("Calibri", size, FontStyle.Regular);
            string txt = new string('0', 100);
            SizeF sz1 = TextRenderer.MeasureText("00", font) - TextRenderer.MeasureText("0", font);
            Console.WriteLine("{0} {1:N3}", size, sz1.Width);
            size += 2;
        }

Вывод:

5 4.000
7 5.000
9 6.000
11 7.000
13 9.000
15 10.000
17 12.000
19 13.000
21 14.000
23 16.000

Это дает вам 7 пикселей, которые вы ищете. Имейте в виду, что вам придется приспособиться к подсказке TrueType, она может настроить форму цифры так, чтобы ее основные стержни попадали на границы пикселей. Добавьте хотя бы один пиксель.

person Hans Passant    schedule 02.12.2009

Измерение ширины текста может быть кошмаром.. Я не знаю, почему они сделали это таким запутанным..

Вот самый точный способ измерить длину текста:

    protected int _MeasureDisplayStringWidth ( Graphics graphics, string text, Font font )
    {
        if ( text == "" )
            return 0;

        StringFormat format = new StringFormat ( StringFormat.GenericDefault );
        RectangleF rect = new RectangleF ( 0, 0, 1000, 1000 );
        CharacterRange[] ranges = { new CharacterRange ( 0, text.Length ) };
        Region[] regions = new Region[1];

        format.SetMeasurableCharacterRanges ( ranges );
        format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;

        regions = graphics.MeasureCharacterRanges ( text, font, rect, format );
        rect = regions[0].GetBounds ( graphics );

        return (int)( rect.Right );
    }
person Mongus Pong    schedule 02.12.2009
comment
@Fungus: Спасибо, ваш код был ближе всего к правильному ответу, сообщая, что 7-пиксельные цифры имеют ширину 8 пикселей. К сожалению, ошибка в 1 пиксель увеличится до гораздо большей ошибки в моем приложении. Кроме того, кажется, что аргументы конструктора Font влияют на результат. Есть ли правильный способ построить объект Font для представления 11pt Calibri? - person Tim Coulter; 02.12.2009
comment
Хммм... возможно, что это вернет 1 пиксель шире, поскольку он измеряет пространство, которое занимают символы, что затем будет учитывать границу вокруг персонажа. - person Mongus Pong; 02.12.2009

Я не уверен на 100%, что это именно то, что вам нужно.

Но вы можете взглянуть на эту страницу Microsoft:

person BlueTrin    schedule 02.12.2009