Каков наилучший способ получить строку, соответствующую указанной ширине в пикселях?

Теперь я столкнулся с проблемой получения строки, которая соответствует предоставленной ширине в пикселях.

Например, если у меня есть такое предложение (в JavaScript):

var mystring = "This is my sentence, I need to get just 50 pixels from it."

Если я использую метод MeasureString в С#, я могу получить ширину:

Font font = new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Point);
SizeF size = graphics.MeasureString(mystring, font);

Допустим, ширина этой строки составляет 400 пикселей, но максимальная ширина, которую я могу отобразить на сайте, составляет 50 пикселей.

Если я укорочу строку и измерю ее, пока она не станет менее 50 пикселей в ширину, это сработает, но потребуется много итераций, что вообще не очень хорошее решение.

У кого-нибудь есть хорошее решение для этого?

Спасибо.


person pang    schedule 22.07.2009    source источник
comment
При чем тут JavaScript?   -  person Jon Skeet    schedule 22.07.2009
comment
Большинство функций DrawText всех фреймворков имеют возможность ограничивать текст прямоугольником, почему бы не указать границы и позволить системе справиться с этим?   -  person Shay Erlichmen    schedule 22.07.2009
comment
При чем тут JavaScript? Потому что я могу получить данные из вызова Ajax, так что это также можно сократить с помощью javascript, если есть хорошее решение.   -  person pang    schedule 22.07.2009
comment
@Shay Erlichmen: я проверю, есть ли такое решение.   -  person pang    schedule 22.07.2009
comment
Вы контролируете разметку веб-страницы? Вас беспокоит, где текст усекается? Если ответы «Да» и «Нет» соответственно, вы можете просто обрезать содержимое соответствующего элемента отображения до 50 пикселей и оставить текст в покое.   -  person Christian Hayter    schedule 22.07.2009
comment
Я хочу иметь ... после усеченного текста, поэтому мне нужно знать о тексте длиной 50 пикселей.   -  person pang    schedule 23.07.2009


Ответы (5)


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

person Jon Skeet    schedule 22.07.2009

Я хотел бы добавить в этот раздел: «Если я укорочу строку и измерю ее, пока она не станет меньше 50 пикселей в ширину…»

Почему бы вам не выполнить бинарный поиск, начиная с середины строки. Вот расположение для начала. Неважно, насколько длинна веревка — вам потребуется гораздо меньше времени, чтобы узнать, какая у вас идеальная длина. Сложность уменьшается до log(n) от n.

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

person Community    schedule 22.07.2009

string myString = "This is my sentence, I need to get just 50 pixels from it."
Font font = new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Point); 

int desiredWidth = 50;
int myStringWidth = TextRenderer.MeasureText(myString , font).Width;
string result = mystring.Substring(0, myString.Length * desiredWidth / myStringWidth);

Это решение не учитывает разрывы строк.

person blazkovicz    schedule 14.03.2011

Вы можете аппроксимировать ширину строки как сумму ширины подстрок. Если ваши подстроки ограничены пробелами†, это может быть даже довольно хорошим приближением. Но вы не можете знать, пока не спросите точно, какой ширины будет та или иная конкретная строка, потому что механизм рендеринга текста выполняет такие действия, как кернинг (изменение интервала между символами).

† и вы, вероятно, хотите, чтобы они были, по крайней мере, на европейском языке, потому что гораздо легче читать текст, разбитый только на пробелы, чем текст, разбитый в середине слова, даже если это приводит к тому, что текст выглядит немного более рваным.

person tialaramex    schedule 22.07.2009

Метод GetBoxedString класса StringBoxer «оценивает» количество строк (слова, разделенные пробелами, слова, разделенные Enter, или даже длинные символы), которые вы можете поместить в прямоугольник, и возвращает его (будьте осторожны при копировании/вставке, потому что я не мог поместите весь мой код в сером поле ниже):

общедоступный закрытый класс StringBoxer {

public string GetBoxedString(string s, Size size, Font font)
{

  int longestStringLengthInWidth = 0;
  var result = string.Empty;
  if (size.Height < font.Height)
  {
    return string.Empty;
  }

  using (var bmp = new Bitmap(size.Width, size.Height))
  {
    int index = 0;
    var words = this.SplitString(s);


    var measuredSizeBeforeAddingWord = new SizeF(0, 0);

    using (var graphic = Graphics.FromImage(bmp))
    {
      longestStringLengthInWidth = CalculateLongestStringLength(size, font);

      do
      {
        if (words[index].Length > longestStringLengthInWidth)
        {
          //// If a word is longer than the maximum string length for the specified size then break it into characters and add char 0 at the begining of each of those characters
          var brokenCharacters = words[index].Select(c => ((char)0) + c.ToString()).ToList();
          brokenCharacters.Add(" ");
          words.RemoveAt(index);
          words.InsertRange(index, brokenCharacters);
        }

        var measuredSizeAfterAddingWord = graphic.MeasureString(result + (!words[index].EndsWith("\n") ? words[index] + " " : words[index]), font, size);
        if ((words[index].Contains('\n') || measuredSizeAfterAddingWord == measuredSizeBeforeAddingWord) && measuredSizeAfterAddingWord.Height >= size.Height-font.Height)
        {
          return result.TrimEnd();
        }

        measuredSizeBeforeAddingWord = measuredSizeAfterAddingWord;

        if (words[index].Contains((char)0))
        {
          result += words[index].Replace(((char)0).ToString(), string.Empty);
        }
        else
        {
          result += (!words[index].EndsWith("\n") ? words[index] + " " : words[index]);
        }

        index++;
      }
      while (index < words.Count);
    }
  }

  return result.TrimEnd();
}

private List<string> SplitString(string s)
{
  var words = s.Split(' ').ToList();
  var index = 0;
  do
  {
    // If a word contains Enter key(s) then break it into more words and replace them with the original word.
    if (!words[index].Contains("\n"))
    {
      index++;
      continue;
    }

    var enterSplitWords = (words[index] + " ").Split('\n');
    var brokenWords = enterSplitWords.Select(str => (enterSplitWords.LastOrDefault() != str ? str + "\n" : str).Replace(" ", string.Empty)).ToList();
    words.RemoveAt(index);
    words.InsertRange(index, brokenWords);
    index += brokenWords.Count;
  }
  while (index < words.Count);

  return words;
}

private static int CalculateLongestStringLength(Size size, Font font)
{
  var tempString = string.Empty;
  var longestStringLengthInWidth = 0;
  using (var bmp = new Bitmap(size.Width, size.Height))
  {
    using (var graphic = Graphics.FromImage(bmp))
    {
      do
      {
        if (Math.Floor(graphic.MeasureString(tempString, font, size).Height) <= font.Height)
        {
          longestStringLengthInWidth++;
        }
        else
        {
          break;
        }

        tempString += "x";
      } while (true);
    }
  }

  return longestStringLengthInWidth;
}

}

person user3578181    schedule 21.05.2017