Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Длина строки шириной N пикселов.


Автор: tripsin 17.5.2005, 21:18
Как определить, сколько максимально символов заданного шрифта (TrueType или системного) поместится на ширине в пикселах? Т.е. ширина компонента известна, надо определсить длину строки, чтобы она заняла всю ширину компонента.

Автор: Yanis 17.5.2005, 21:43
Код

var
  k: Integer;
begin
  k := 0;
  while Image1.Canvas.TextWidth(stringofchar('a', k)) < Image1.Width do
    inc(k);
  ShowMEssage('Max char count: '+IntToStr(k));
end;

Добавлено @ 21:44
Подсчёт маскимального кол-ва символов помещающихся на Canvas'е TImage.

Автор: s-mike 17.5.2005, 23:17
Вместо придумывания велосипеда для переноса текста можно воспользоватся функцией DrawText.

Автор: tripsin 17.5.2005, 23:27
Спасибо Yanis
А что делать, если у окошка известно тольно hWnd?
Я тут покопался в MSDN и нашел, что с помощью GetTextMetrics можно получить структуру TTextMetric, а в ней есть поля tmAveCharWidth: Longint;(ср.ширина символа) и tmMaxCharWidth: Longint;(макс.ширина символа) в относительных единицах. Спрашивается: как эти единицы в пикселы перевести?


Добавлено @ 23:28
s-mike Мне вот именно надо велосипед придумать smile

Автор: s-mike 17.5.2005, 23:33
Цитата(tripsin @ 17.5.2005, 23:27)
s-mike Мне вот именно надо велосипед придумать smile

Функция DrawText может и TextWidth заменить. И в качестве входного параметра ей как раз и нужно DC окна.

Автор: tripsin 18.5.2005, 11:47
А что общественность думает по поводу вот этого кода? :
Код

var
 DC: HDC;
 Metrics: TTextMetric;
 CharInWidth: Integer;
 OldMapMode: Integer;
begin
  DC := GetWindowDC(ListBox1.Handle); // Получаем контекст окна
    SelectObject(DC, ListBox1.Font.Handle);//Выбираем в него шрифт окна
    OldMapMode := GetMapMode(DC);// Запоминаем старый режим отображения
    SetMapMode(DC, MM_TEXT); //Устанавлмваем отображение в пикселях
    GetTextMetrics(DC, Metrics); //Получаем параметры шрифта
    SetMapMode(DC, OldMapMode);//Восстанавливаем режим отображения
  ReleaseDC(ListBox1.Handle, DC); //Освобождаем контекст
  //Получаем количестов средних символов на ширину окна
  CharsInWidth := ListBox1.ClientWidth div Metrics.tmAveCharWidth;
end;

Автор: s-mike 18.5.2005, 11:53
Цитата(tripsin @ 18.5.2005, 11:47)
А что общественность думает по поводу вот этого кода? :

Больше напоминает изобретание велосипеда smile

Автор: tripsin 18.5.2005, 11:57
Мне кажется это лучше, чем простой подбор.

Автор: s-mike 18.5.2005, 12:08
tripsin, ну зачем же простой подбор? DrawText с флагом DT_CALCRECT возвращает точные размеры текста.

Автор: tripsin 18.5.2005, 16:34
Мне нужно разбить длинную строку, на кусочки такой ширины, чтобы они влезали в окно (вручную). Выводить их сразу не надо и DrawText (при всех его достоинчтвах) мне не нужен.

З.Ы. С DT_CALCRECT расчитывается прямоугольник вывода, а он как раз известен.

Автор: s-mike 18.5.2005, 17:07
tripsin, ты все таки не понял, DrawText нужно вычислять длину строки. Она тебе возвратит минимальный прямоугольник, необходимый для вывода строки. Вот и найди ширину этого прямоугольника. Это и будет ширина строки. Есть еще GetTextExtentPoint32, используемая для TextWidth.

Автор: tripsin 18.5.2005, 18:36
s-mike Это ты не понял. Задача в точности до наоборот. У меня уже есть прямоугольник, и надо посчитать сколько символов заданного шрифта влезет в этот прямоугольник по ширине. На выходе должно быть количество символов, а не размеры прямоугольника.

Автор: s-mike 19.5.2005, 00:07
tripsin, я говорю, что с помощью этих функций, написанных всего одной строкой, можно заменить код, приведенный тут: http://forum.vingrad.ru/index.php?showtopic=52406&view=findpost&p=413987. Я думаю, что использовать среднюю ширину символов не стоит, тут нужна точность. Я думаю так. У нас есть слова, разделенные пробелом. Читаем последовательно слова, суммируем их ширину. При добавлении каждого следующего слова, если слова превышают максимальную длину строки, то новое слово не добавляем, а переходим на следующую строку. Я бы и код написал, жаль времени сейчас нет, поздно уже.

Автор: tripsin 19.5.2005, 08:57
Цитата
Я бы и код написал, жаль времени сейчас нет, поздно уже.

Хм... Я подожду smile

Автор: tripsin 19.5.2005, 09:11
То, что ты описал делает функция WrapText.
Цитата
если слова превышают максимальную длину строки,

Вот ее-то как раз и надо найти.

Автор: s-mike 19.5.2005, 09:33
Цитата
WrapText function
Splits a string into multiple lines as its length approaches a specified size.

Unit

SysUtils

Category

string handling routines

Добавлено @ 09:35
Но она немного не тем занимается, она по буквам разбивает, а не по ширине слов.

Автор: tripsin 19.5.2005, 10:36
Тем-тем. Все зависит от переданных параметров.

function WrapText(const Line, BreakStr: string; BreakChars: TSysCharSet; MaxCol: Integer): string;overload;

По умолчанию BreakChars = [' ', ' -' , #9], т.е. как раз на слова и разбивается. Но считает она не ширину строки в пикселах, а просто, что бы количество символов было меньше MaxCol.

Автор: s-mike 19.5.2005, 10:55
Цитата(tripsin @ 19.5.2005, 10:36)
Но считает она не ширину строки в пикселах, а просто, что бы количество символов было меньше MaxCol.

Я об этом и говорю:
Цитата(s @ 19.5.2005, 09:33)
Но она немного не тем занимается, она по буквам разбивает, а не по ширине слов.

Автор: Girder 19.5.2005, 12:27
Мдя... уж! smile

tripsin обрати внимание на следующие функции: GetCharWidth32, GetCharABCWidths.

Вот например простейший пример для GetCharWidth32:
Код
procedure TForm1.Button2Click(Sender: TObject);
var a:array [0..255] of DWord;
    w,i,wid:DWord;
    s:string;
    DC:HDC;
begin
 DC:=GetDC(Memo1.Handle);
 Wid:=20; //Типо... заданная ширина
 s:='G'+#9+'irder'; //Типо... заданная строка для которой мы хотим узнать... сколько из неё символов влезет :)
 FillChar(a,SizeOf(a),#0);
 GetCharWidth32(DC,0,255,a);
 w:=0;
 for i:=1 to Length(s) do
  begin
   if s[i]=#9 then w:=w+a[ord(s[i])]*4 else w:=w+a[ord(s[i])]; //Ну мне лень было описывает все "спец" символы :)
   if w>Wid then
    begin
     Caption:=IntToStr(i-1); //Типо... ответ
     break;
    end;
  end;
 ReleaseDC(Memo1.Handle,DC);
end;

Автор: tripsin 19.5.2005, 13:53
Girder Твой код не работает! Ты сам-то пробовал. Он до строки "Caption:=IntToStr(i-1); //Типо... ответ" - даже не доходит.
GetCharWidth32(DC,0,255,a); - это че за вызов-то?
Вставь проверку: if not GetCharWidth32(DC,0,255,a) then ShowMessage('Хрена! Не работает!');
Кста. В твоем коде в DC надо выбирать шрифт с помощью SelectObject, иначе будет использоваться системный шрифт.

Ну блииииин!! smile Ну как еще объяснить-то.
Короче нужна функция такого вида:
function Number_Of_Chars_In_Pixel_Width(Pixel_Width_Of_window: Integer; WindowFont: TFont): Integer;
Может хоть так понятнее. Как определить ширину имеющейся строки я и так знаю.

Пока у этой задачи я нашел 2 ответа:
1. Подбор ширины строки в цикле, как показал Yanis(по "среднему" символу, или с помошью функций группы GetCharWidth (но для них нужен конкретный текст)
2. Мой вариант с использованием метрик тескта. Мне только интересно - корректный это код или нет (работает точно).

Автор: Girder 19.5.2005, 14:02
1.
Цитата
Remarks
GetCharWidth32 cannot be used on TrueType fonts. To retrieve character widths for TrueType fonts, use GetCharABCWidths.

The range is inclusive; that is, the returned widths include the widths of the characters specified by the iFirstChar and iLastChar parameters.

If a character does not exist in the current font, it is assigned the width of the default character.
для TrueType - надо использовать GetCharABCWidths
Цитата
Requirements
  Windows NT/2000/XP: Included in Windows NT 3.5 and later.
  Windows 95/98/Me: Unsupported.
для W9X - GetCharWidth
2.
Цитата(tripsin @ 19.5.2005, 14:53)
Кста. В твоем коде в DC надо выбирать шрифт с помощью SelectObject, иначе будет использоваться системный шрифт.
А я что за тебя должен писать весь код? читай:
Цитата(Girder @ 19.5.2005, 13:27)
Вот например простейший пример для GetCharWidth32

3.
Цитата(tripsin @ 19.5.2005, 14:53)
Кста. В твоем коде в DC надо выбирать шрифт с помощью SelectObject, иначе будет использоваться системный шрифт.
Да пробывал. У меня работает.

Автор: Guest 19.5.2005, 15:18
Это я, tripsin smile Girder Прошу прощения за наезд. Код действительно рабочий. Просто у меня Win98. Заменил на GetCharWidth и все заработало. smile
Girder Ты уж не обижайся smile , че-то нервный я стал.

Автор: Girder 19.5.2005, 15:27
Лови... примерную реализацию smile
Код
function www(lFont:tagLogFonta;TypeFont:integer):DWord;
var a:array [0..255] of DWord;
    b:array [0..255] of ABC;
    w,i,wid,n:DWord;
    s:string;
    DC:HDC;
    FontOld,FontNew:HFont;
begin
FontNew:=CreateFontIndirect(lFont);
Result:=0;
DC:=GetDC(0);
FontOld:=SelectObject(DC,FontNew);
Wid:=50;
s:='Gir'+#9+'der';
if TypeFont<>TRUETYPE_FONTTYPE then
  begin
  FillChar(a,SizeOf(a),#0);
  if GetCharWidth32(DC,0,255,a)=false then
    GetCharWidth(DC,0,255,a);
  w:=0;
  for i:=1 to Length(s) do
    begin
    inc(Result);
    if s[i]=#9 then w:=w+a[ord(s[i])]*4 else w:=w+a[ord(s[i])];
    if w>Wid then break;
    end;
  end else
  begin
  FillChar(b,SizeOf(b),#0);
  GetCharABCWidths(DC,0,255,b);
  w:=0;
  for i:=1 to Length(s) do
    begin
    n:=b[ord(s[i])].abcA+b[ord(s[i])].abcB+b[ord(s[i])].abcC;
    if s[i]=#9 then w:=w+n*4 else w:=w+n;
    if w>Wid then
      begin
      Result:=i-1;
      break;
      end;
    end;
  end;
SelectObject(DC,FontOld);
ReleaseDC(0,DC);
DeleteObject(FontNew);
end;

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)