У меня есть некоторый код, который делает пользовательский рисунок. В основном это - программа заливки формы, которая имеет WYSIWYG-редактор. Редактор позволяет уровню масштабирования быть установленным. У меня есть проблемы с шириной моих маркировок, переходящих к различным размерам относительно всего остального на форме.
Пример кода, который я использую для вывода текста, ниже. Я вполне уверен, проблема связана с изменением в размере шрифта не совпадение с тем, как все остальное масштабируется. Уровень масштабирования должен измениться достаточно для столкновения шрифта до следующего размера перед текстовыми изменениями, даже при том, что все остальное на форме перемещает несколькими пикселями с каждым изменения.
Это приводит к двум различным проблемам - текст может или посмотреть путь к маленькому с большим пробелом, или текст будет двумя большими и перекроет следующее управление. Вещи выглядят действительно плохо, когда у меня есть сплошная линия текста. Одна маркировка слова не изменяется достаточно для порождения любых проблем.
Я думал об ограничении уровней масштабирования - прямо сейчас у меня есть ползунок в 1%-х инкрементах. Но я не вижу, что любой набор уровней лучше, чем кто-либо другой. Мои формы имеют несколько маркировок в различных размерах шрифта, которые переходят между короче и дольше в разное время.
Функция MultDiv округляет результат. Я мог усечь это значение, чтобы удостовериться, что я всегда меньше по сравнению с дольше, но это выглядит так же, как плохо, потому что разрывы выглядят что намного больше на те уровни масштабирования.
Примечания по коду:
Это в настоящее время находится на Delphi 7. Это - наш последний проект, который не продвинулся, таким образом, ответы, связанные с более новыми версиями Delphi, приветствуются.
Мы изучающий это, я действительно видел, что функция ExtDrawText существует. Однако изменение на ту функцию, казалось, не имело значение.
Право на ограничительную рамку установлено на 0, и текст оттянут без отсечения, потому что инструмент, который мы используем для создания определения формы, не отслеживает правильную границу текста. Мы просто визуально выравниваем его до корректного местоположения.
procedure OutputText(Canvas: TCanvas; LineNumber: integer; CurrentZoomLevel: integer; FontSize: integer; Text: string);
const
FormatFlags = DT_BOTTOM + DT_SINGLELINE + DT_NOPREFIX + DT_LEFT + DT_NOCLIP;
var
OutputBox: TRect;
ZoomedLineHeight: integer;
begin
ZoomedLineHeight := MulDiv(UnZoomedLineHeight, CurrentZoomLevel, 96);
Canvas.Font.Height := -MulDiv(FontSize, CurrentZoomLevel, 96);
OutputBox.Left := ZoomedLineHeight;
OutputBox.Right := 0;
OutputBox.Top := (LineNumber * ZoomedLineHeight);
OutputBox.Bottom := OutputBox.Top + ZoomedLineHeight;
DrawText(Canvas.Handle, PChar(Text), length(Text), OutputBox, FormatFlags);
end;
Править:
Используя ответ mghie вот мое измененное тестовое приложение. Кода изменения масштаба не стало с установкой MapMode. Однако функция TextOut все еще, кажется, выбирает полный размер шрифта. Ничто, кажется, не изменилось для текста кроме, я не должен вычислять высоту шрифта сам - режим карты делает это для меня.
Я действительно находил эту веб-страницу "Системами координат GDI", который был очень полезен, но она не обратилась к размеру текста.
Вот мое тестовое приложение. Это изменяет размер, как Вы изменяете размер формы, и потянули сетку, таким образом, Вы видите, как конец текста переходит вокруг.
procedure DrawGrid(Canvas: TCanvas);
var
StartPt: TPoint;
EndPt: TPoint;
LineCount: integer;
HeaderString: string;
OutputBox: TRect;
begin
Canvas.Pen.Style := psSolid;
Canvas.Pen.Width := 1;
StartPt.X := 0;
StartPt.Y := LineHeight;
EndPt.X := Canvas.ClipRect.Right;
EndPt.Y := LineHeight;
LineCount := 0;
while (StartPt.Y < Canvas.ClipRect.Bottom) do
begin
StartPt.Y := StartPt.Y + LineHeight;
EndPt.Y := EndPt.Y + LineHeight;
Inc(LineCount);
if LineCount mod 5 = 0 then
Canvas.Pen.Color := clRed
else
Canvas.Pen.Color := clBlack;
Canvas.MoveTo(StartPt.X, StartPt.Y);
Canvas.LineTo(EndPt.X, EndPt.Y);
end;
StartPt.X := 0;
StartPt.Y := 2 * LineHeight;
EndPt.X := 0;
EndPt.Y := Canvas.ClipRect.Bottom;
LineCount := 0;
while StartPt.X < Canvas.ClipRect.Right do
begin
StartPt.X := StartPt.X + LineHeight;
EndPt.X := EndPt.X + LineHeight;
Inc(LineCount);
if LineCount mod 5 = 0 then
Canvas.Pen.Color := clRed
else
Canvas.Pen.Color := clBlack;
Canvas.MoveTo(StartPt.X, StartPt.Y);
Canvas.LineTo(EndPt.X, EndPt.Y);
if Canvas.Pen.Color = clRed then
begin
HeaderString := IntToStr(LineCount);
OutputBox.Left := StartPt.X - (4 * LineHeight);
OutputBox.Right := StartPt.X + (4 * LineHeight);
OutputBox.Top := 0;
OutputBox.Bottom := OutputBox.Top + (LineHeight * 2);
DrawText(Canvas.Handle, PChar(HeaderString), Length(HeaderString),
OutputBox, DT_BOTTOM + DT_SINGLELINE + DT_NOPREFIX + DT_CENTER);
end;
end;
end;
procedure OutputText(Canvas: TCanvas; LineNumber: integer; Text: string);
const
FormatFlags = DT_BOTTOM + DT_SINGLELINE + DT_NOPREFIX + DT_LEFT + DT_NOCLIP;
var
OutputBox: TRect;
begin
OutputBox.Left := LineHeight;
OutputBox.Right := 0;
OutputBox.Top := (LineNumber * LineHeight);
OutputBox.Bottom := OutputBox.Top + LineHeight;
Windows.TextOut(Canvas.Handle, OutputBox.Left, OutputBox.Top, PChar(Text), Length(Text));
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoubleBuffered := false;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
procedure TForm1.FormPaint(Sender: TObject);
const
ShortString = 'Short';
MediumString = 'This is a little longer';
LongString = 'Here is something that is really long here is where I see the problem with zooming.';
PhysicalHeight = 500;
PhysicalWidth = 400;
var
DC: HDC;
OldMode, i, xy: integer;
LF: TLogFont;
OldFont: HFONT;
begin
Canvas.Brush.Style := bsClear;
FillChar(LF, SizeOf(TLogFont), 0);
LF.lfOutPrecision := OUT_TT_ONLY_PRECIS;
LF.lfFaceName := 'Arial';
LF.lfHeight := -12;
DC := Self.Canvas.Handle;
OldMode := SetMapMode(DC, MM_ISOTROPIC);
// OldMode := SetMapMode(DC, MM_HIMETRIC);
SetWindowExtEx(DC, PhysicalWidth, PhysicalHeight, nil);
SetViewportExtEx(DC, Self.Width, Self.Height, nil);
try
OldFont := Windows.SelectObject(DC, CreateFontIndirect(LF));
DrawGrid(Self.Canvas);
OutputText(Self.Canvas, 3, ShortString);
OutputText(Self.Canvas, 4, MediumString);
OutputText(Self.Canvas, 5, LongString);
DeleteObject(SelectObject(DC, OldFont));
finally
SetMapMode(DC, OldMode);
end;
end;
Основная проблема заключается в том, что вы пытаетесь увеличить текст, изменив его Высота
. Поскольку Windows API использует целочисленную систему координат, из этого следует, что возможны только определенные значения высоты шрифта. Если, например, у вас есть шрифт высотой 20 пикселей при значении шкалы 100%, то вы можете установить только значения шкалы, кратные 5%. Хуже того, даже со шрифтами TrueType не все из них дадут удовлетворительные результаты.
У Windows уже много лет есть средства, чтобы справиться с этим, но, к сожалению, VCL не оборачивает их (и которые на самом деле не используются внутри, либо) - режимы отображения. Windows NT представила преобразований , но SetMapMode ()
был доступен в 16-битной Windows уже IIRC.
Установив такой режим, как MM_HIMETRIC
или MM_HIENGLISH
(в зависимости от того, измеряете ли вы измерения в метрах или фарлонгах), вы можете рассчитать высоту шрифта и ограничивающий прямоугольник, а поскольку пиксели очень маленькие, можно будет точно увеличивать или уменьшать масштаб.
Установив MM_ISOTROPIC
или MM_ANISOTROPIC
режимы OTOH вы можете продолжать использовать те же значения для высоты шрифта и ограничивающего прямоугольника, и вместо этого вы должны регулировать матрицу преобразования между пространством страницы и пространством устройства всякий раз, когда значение масштабирования изменения.
Набор компонентов SynEdit раньше имел элемент управления предварительным просмотром печати (в SynEditPrintPreview. pas), в котором использовался режим сопоставления MM_ANISOTROPIC
, чтобы обеспечить предварительный просмотр печатаемого текста при различных уровнях масштабирования. Это может быть полезно в качестве примера, если он все еще находится в SynEdit или если вы можете найти старые версии.
Правка:
Для вашего удобства небольшая демонстрация, протестированная с Delphi 4 и Delphi 2009:
procedure TForm1.FormCreate(Sender: TObject);
begin
ClientWidth := 1000;
ClientHeight := 1000;
DoubleBuffered := False;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
DC: HDC;
OldMode, i, xy: integer;
LF: TLogFont;
OldFont: HFONT;
begin
Canvas.Brush.Style := bsClear;
FillChar(LF, SizeOf(TLogFont), 0);
LF.lfOutPrecision := OUT_TT_ONLY_PRECIS;
LF.lfFaceName := 'Arial';
DC := Canvas.Handle;
OldMode := SetMapMode(DC, MM_HIMETRIC);
try
SetViewportOrgEx(DC, ClientWidth div 2, ClientHeight div 2, nil);
Canvas.Ellipse(-8000, -8000, 8000, 8000);
for i := 42 to 200 do begin
LF.lfHeight := -5 * i;
LF.lfEscapement := 100 * i;
OldFont := Windows.SelectObject(DC, CreateFontIndirect(LF));
xy := 2000 - 100 * (i - 100);
Windows.TextOut(DC, -xy, xy, 'foo bar baz', 11);
DeleteObject(SelectObject(DC, OldFont));
end;
finally
SetMapMode(DC, OldMode);
end;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
Второе редактирование:
Я подумал об этом немного больше и думаю, что для вашей проблемы масштабирование в пользовательском коде может быть единственным способом реализовать это.
Давайте рассмотрим это на примере. Если у вас есть строка текста шириной 500 пикселей с высотой шрифта 20 пикселей при коэффициенте масштабирования 100%, вам придется увеличить уровень масштабирования до 105%, чтобы получить строку текста размером 525 на 21. размер пикселей. Для всех промежуточных целочисленных уровней масштабирования у вас будет целочисленная ширина и нецелочисленная высота этого текста. Но вывод текста так не работает, вы не можете установить ширину строки текста и попросить систему вычислить ее высоту. Таким образом, единственный способ сделать это - установить высоту шрифта до 20 пикселей для увеличения от 100% до 104%, но установить шрифт высотой 21 пиксель для увеличения от 105% до 109% и так далее. Тогда текст будет слишком узким для большинства значений масштабирования. Или установите высоту шрифта на 21 пиксель, начиная со 103% масштабирования, и живите, когда текст будет слишком широким.
Но с небольшой дополнительной работой вы можете добиться увеличения ширины текста на 5 пикселей для каждого шага масштабирования. Вызов API ExtTextOut ()
принимает в качестве последнего параметра необязательный целочисленный массив происхождения символов. Большинство известных мне примеров кода не используют это, но вы можете использовать его для вставки дополнительных пикселей между некоторыми символами, чтобы увеличить ширину строки текста до желаемого значения, или для перемещения символов ближе друг к другу, чтобы уменьшить ширину. Это будет примерно так:
GetTextExtentExPoint ()
, чтобы вычислить массив позиций символов по умолчанию. Последним допустимым значением должна быть ширина всей строки. ExtTextOut ()
. Это не проверено и может содержать некоторые ошибки или упущения, но, надеюсь, это позволит вы можете плавно масштабировать ширину текста независимо от высоты текста. Может быть, ваше приложение того стоит?
Решение, предложенное mghie , хорошо работает с графикой, но не работает при масштабировании шрифтов.
Есть еще один метод масштабирования с противоположными свойствами: SetWorldTransform
. Этот метод хорошо работает при масштабировании шрифтов TrueType, но не работает при рисовании графики с помощью GDI.
Поэтому я предлагаю переключить режим DC с помощью метода mghie для рисования линий и использовать SetWorldTransform
при рисовании текста.
Результаты не так понятны, но выглядят даже лучше ...
Вот код для обработчика событий OnPaint, например, из текста вопроса, который использует оба метода:
procedure TForm1.FormPaint(Sender: TObject);
const
ShortString = 'Short';
MediumString = 'This is a little longer';
LongString = 'Here is something that is really long here is where I see the problem with zooming.';
PhysicalHeight = 500;
PhysicalWidth = 400;
var
DC: HDC;
OldMode, i, xy: integer;
LF: TLogFont;
OldFont: HFONT;
NewFont: HFONT;
oldGraphicMode : integer;
transform : TXForm;
begin
Canvas.Brush.Style := bsClear;
SetMapperFlags(DC, 1);
FillChar(LF, SizeOf(TLogFont), 0);
LF.lfOutPrecision := OUT_TT_ONLY_PRECIS;
LF.lfFaceName := 'Arial';
LF.lfHeight := -12;
LF.lfQuality := DRAFT_QUALITY;
DC := Self.Canvas.Handle;
// Mode switch for drawing graphics
OldMode := SetMapMode(DC, MM_ISOTROPIC);
try
SetWindowExtEx(DC, PhysicalWidth, PhysicalHeight, nil);
SetViewportExtEx(DC, Self.Width, Self.Height, nil);
DrawGrid(Self.Canvas);
finally
SetMapMode(DC, OldMode);
end;
// Mode switch for text output
oldGraphicMode := Windows.SetGraphicsMode(DC, GM_ADVANCED);
try
//x' = x * eM11 + y * eM21 + eDx,
transform.eM11 := Width / PhysicalWidth;
transform.eM21 := 0;
transform.eDx := 0;
//y' = x * eM12 + y * eM22 + eDy,
transform.eM12 := 0;
transform.eM22 := Height / PhysicalHeight;
transform.eDy := 0;
Windows.SetWorldTransform(DC, transform);
try
NewFont := CreateFontIndirect(LF);
try
OldFont := Windows.SelectObject(DC, NewFont);
try
OutputText(Self.Canvas, 3, ShortString);
OutputText(Self.Canvas, 4, MediumString);
OutputText(Self.Canvas, 5, LongString);
finally
Windows.SelectObject(DC, OldFont);
end;
finally
Windows.DeleteObject(NewFont);
end;
finally
transform.eM11 := 1;
transform.eM22 := 1;
Windows.SetWorldTransform(DC, transform);
end;
finally
Windows.SetGraphicsMode(DC, oldGraphicMode);
end;
end;
Хорошо, основываясь на предложении Мги изменить промежутки между символами, вот что я придумал. В итоге я не использовал массив интервалов между символами, а вместо этого использовал SetTextCharacterExtra и SetTextJustification.
Функция SetTExtCharacterExtra имеет следующее примечание:
Эта функция поддерживается, в основном, для совместимость с существующим приложения. Новые приложения должны как правило, избегайте вызова этой функции, потому что это несовместимо с сложные сценарии (сценарии, требующие формирование текста; арабская вязь - это Пример этого).
Рекомендуемый подход заключается в том, чтобы вместо того, чтобы вызывать эту функцию и а потом "ТекстОут", заявки должны звонить ExtTextOut и использовать его lpDx параметр чтобы поставлять ширины.
Я могу изменить свой код, чтобы использовать это, но пока этот подход работает достаточно хорошо. Ниже приведена моя модифицированная функция.
const
LineHeight = 20;
procedure DrawGrid(Output: TCanvas; ZoomLevel: integer);
var
StartPt: TPoint;
EndPt: TPoint;
ZoomedStartPt: TPoint;
ZoomedEndPt: TPoint;
ZoomedIncrement: integer;
LineCount: integer;
HeaderString: string;
OutputBox: TRect;
begin
ZoomedIncrement := MulDiv(LineHeight, ZoomLevel, 100);
if (ZoomedIncrement = 0) then
exit;
Output.Pen.Style := psSolid;
Output.Pen.Width := 1;
StartPt.X := 0;
StartPt.Y := LineHeight;
EndPt.X := 1000;
EndPt.Y := LineHeight;
LineCount := 0;
while StartPt.Y < 1000 do
begin
StartPt.Y := StartPt.Y + LineHeight;
EndPt.Y := EndPt.Y + LineHeight;
Inc(LineCount);
if LineCount mod 5 = 0 then
Output.Pen.Color := clRed
else
Output.Pen.Color := clBlack;
ZoomedStartPt.X := MulDiv(StartPt.X, ZoomLevel, 100);
ZoomedStartPt.Y := MulDiv(StartPt.Y, ZoomLevel, 100);
ZoomedEndPt.X := MulDiv(EndPt.X, ZoomLevel, 100);
ZoomedEndPt.Y := MulDiv(EndPt.Y, ZoomLevel, 100);
Output.MoveTo(ZoomedStartPt.X, ZoomedStartPt.Y);
Output.LineTo(ZoomedEndPt.X, ZoomedEndPt.Y);
end;
StartPt.X := 0;
StartPt.Y := 2 * LineHeight;
EndPt.X := 0;
EndPt.Y := 1000;
LineCount := 0;
while StartPt.X < 1000 do
begin
StartPt.X := StartPt.X + LineHeight;
EndPt.X := EndPt.X + LineHeight;
Inc(LineCount);
if LineCount mod 5 = 0 then
Output.Pen.Color := clRed
else
Output.Pen.Color := clBlack;
ZoomedStartPt.X := MulDiv(StartPt.X, ZoomLevel, 100);
ZoomedStartPt.Y := MulDiv(StartPt.Y, ZoomLevel, 100);
ZoomedEndPt.X := MulDiv(EndPt.X, ZoomLevel, 100);
ZoomedEndPt.Y := MulDiv(EndPt.Y, ZoomLevel, 100);
Output.MoveTo(ZoomedStartPt.X, ZoomedStartPt.Y);
Output.LineTo(ZoomedEndPt.X, ZoomedEndPt.Y);
if Output.Pen.Color = clRed then
begin
HeaderString := IntToStr(LineCount);
OutputBox.Left := StartPt.X - (4 * LineHeight);
OutputBox.Right := StartPt.X + (4 * LineHeight);
OutputBox.Top := 0;
OutputBox.Bottom := OutputBox.Top + (LineHeight * 2);
OutputBox.Left := MulDiv(OutputBox.Left, ZoomLevel, 100);
OutputBox.Right := MulDiv(OutputBox.Right, ZoomLevel, 100);
OutputBox.Top := MulDiv(OutputBox.Top, ZoomLevel, 100);
OutputBox.Bottom := MulDiv(OutputBox.Bottom, ZoomLevel, 100);
DrawText(Output.Handle, PChar(HeaderString), Length(HeaderString),
OutputBox, DT_BOTTOM + DT_SINGLELINE + DT_NOPREFIX + DT_CENTER);
end;
end;
end;
function CountSpaces(S: string): integer;
var
i: integer;
begin
result := 0;
for i := 1 to Length(S) do
begin
if (S[i] = ' ') then
result := result + 1;
end;
end;
procedure OutputText(Canvas: TCanvas; LineNumber: integer; CurrentZoomLevel: integer; FontSize: integer; Text: string;
AdjustChars: boolean = true; AdjustSpaces: boolean = true);
var
DC: HDC;
UnzoomedStringWidth: integer;
UnzoomedFontHeight: integer;
ZoomedLineHeight: integer;
ZoomedStringWidth: integer;
ZoomedFontHeight: integer;
OutputBox: TRect;
ExtraPixels: integer;
StringWidth: integer;
TextOutSize: TSize;
TextLength: integer;
SpacesCount: integer;
PixelsPerChar: Integer;
Report: string;
begin
DC := Canvas.Handle;
// First find the box where the string would be for unzoomed text
UnzoomedFontHeight := -MulDiv(FontSize, GetDeviceCaps(Canvas.Handle, LOGPIXELSY), 72);
Canvas.Font.Height := UnzoomedFontHeight;
UnzoomedStringWidth := Canvas.TextWidth(Text);
// Now figure out the zoomed sizes for the font and the box where
// the string will be drawn
ZoomedLineHeight := MulDiv(LineHeight, CurrentZoomLevel, 96);
ZoomedFontHeight := -MulDiv(-UnzoomedFontHeight, CurrentZoomLevel, 96);
ZoomedStringWidth := MulDiv(UnzoomedStringWidth, CurrentZoomLevel, 96);
OutputBox.Left := ZoomedLineHeight;
OutputBox.Right := OutputBox.Left + ZoomedStringWidth;
OutputBox.Top := (LineNumber * ZoomedLineHeight);
OutputBox.Bottom := OutputBox.Top + ZoomedLineHeight;
Canvas.Font.Height := ZoomedFontHeight;
TextLength := Length(Text);
Windows.GetTextExtentPoint32(Canvas.Handle, PChar(Text), TextLength, TextOutSize);
ExtraPixels := ZoomedStringWidth - TextOutSize.cx;
PixelsPerChar := Round(ExtraPixels / TextLength);
// If we let extra push past two pixels in our out we will end up with either
// letters overlapping or really wide text. A maximum of 1 pixel adjustment
// outside the spaces seem to help keep the text looking normal and
// removes some of the pressure on the spaces adjustment. Also is needed
// for short 1 word labels.
if PixelsPerChar > 1 then
PixelsPerChar := 1;
if PixelsPerChar < -1 then
PixelsPerChar := -1;
if (PixelsPerChar <> 0) and (AdjustChars = true) then
begin
Windows.SetTextCharacterExtra(Canvas.Handle, PixelsPerChar);
ExtraPixels := ExtraPixels - (PixelsPerChar * TextLength);
end;
// What ever is left over do with spaces
if (ExtraPixels <> 0) and (AdjustSpaces = true) then
begin
SpacesCount := CountSpaces(Text);
Windows.SetTextJustification(Canvas.Handle, ExtraPixels, SpacesCount);
end;
Windows.SetTextAlign(Canvas.Handle, TA_LEFT + TA_BASELINE);
Windows.ExtTextOut(Canvas.Handle, OutputBox.Left, OutputBox.Top, 0, @OutputBox, PChar(Text), TextLength, nil);
Windows.GetTextExtentPoint32(Canvas.Handle, PChar(Text), TextLength, TextOutSize);
// Reset these values to 0
Windows.SetTextCharacterExtra(Canvas.Handle, 0);
Windows.SetTextJustification(Canvas.Handle, 0, 0);
Report := 'T=' + IntToStr(ZoomedStringWidth); // Target
Report := Report + ': A=' + IntToStr(TextOutSize.cx); // Actual
Windows.TextOut(Canvas.Handle, OutputBox.Right + 30, OutputBox.Top, PChar(Report), Length(Report));
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoubleBuffered := false;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
procedure TForm1.FormPaint(Sender: TObject);
const
ShortString = 'Short';
MediumString = 'This is a little longer';
LongString = 'Here is something that is really long here is where I see the problem with zooming.';
PhysicalWidth = 700;
var
ZoomLevel: integer;
begin
Canvas.Font.Name := 'Arial';
ZoomLevel := Round((Self.Width / PhysicalWidth) * 100);
DrawGrid(Self.Canvas, ZoomLevel);
OutputText(Self.Canvas, 3, ZoomLevel, 12, ShortString);
OutputText(Self.Canvas, 4, ZoomLevel, 12, MediumString);
OutputText(Self.Canvas, 5, ZoomLevel, 12, LongString);
end;
Есть тестовый код для сравнения различных решений.
Код выводит реальную ширину длинной масштабируемой строки в файл font_cmp.csv.
Ссылка на картинку сравнения
Пример кода:
procedure TForm1.Button1Click(Sender: TObject);
const
LongString = 'Here is something that is really long here is where I see the problem with zooming.';
PhysicalHeight = 500;
PhysicalWidth = 400;
var
bmp : TBitmap;
drawCanvas : TCanvas;
OldMapMode : integer;
OldStretchMode : integer;
outHeight : extended;
originalStrSize : TSize;
scaledStrSize : TSize;
proposedStrSize : TSize;
desiredWidth : integer;
LF : TLogFont;
OldFont : HFONT;
NewFont : HFONT;
cmpList : TStringList;
ratio : extended;
begin
FillChar(LF, SizeOf(TLogFont), 0);
LF.lfOutPrecision := OUT_TT_ONLY_PRECIS;
LF.lfFaceName := 'Arial';
LF.lfHeight := -12;
LF.lfQuality := PROOF_QUALITY;
NewFont := CreateFontIndirect(LF);
try
OldFont := Windows.SelectObject(Self.Canvas.Handle, NewFont);
try
GetTextExtentPoint32(Self.Canvas.Handle, PChar(LongString), Length(LongString), originalStrSize);
finally
Windows.SelectObject(Self.Canvas.Handle, OldFont);
end;
finally
Windows.DeleteObject(NewFont);
end;
cmpList := TStringList.Create;
try
cmpList.Add(
'OriginalLength' + ';' +
'ProperLength' + ';' +
'ScaledLength' + ';' +
'MappedLength' + ';' +
'ScaledLengthDiff' + ';' +
'MappedLengthDiff'
);
for desiredWidth := 1 to 3000 do begin
// compute desired height
ratio := desiredWidth / PhysicalWidth;
outHeight := PhysicalHeight * ratio ;
if(outHeight < 1) then outHeight := 1;
LF.lfHeight := round(12*ratio) * (-1);
NewFont := CreateFontIndirect(LF);
try
OldFont := Windows.SelectObject(Self.Canvas.Handle, NewFont);
try
GetTextExtentPoint32(Canvas.Handle, PChar(LongString), Length(LongString), scaledStrSize);
finally
Windows.SelectObject(Self.Canvas.Handle, OldFont);
end;
finally
Windows.DeleteObject(NewFont);
end;
OldMapMode := SetMapMode(Self.Canvas.Handle, MM_ISOTROPIC);
try
SetWindowExtEx(Self.Canvas.Handle, PhysicalWidth, PhysicalHeight, nil);
SetViewportExtEx(Self.Canvas.Handle, desiredWidth, round(outHeight), nil);
LF.lfHeight := -12;
NewFont := CreateFontIndirect(LF);
try
OldFont := Windows.SelectObject(Self.Canvas.Handle, NewFont);
try
GetTextExtentPoint32(Canvas.Handle, PChar(LongString), Length(LongString), proposedStrSize);
finally
Windows.SelectObject(Self.Canvas.Handle, OldFont);
end;
finally
Windows.DeleteObject(NewFont);
end;
finally
SetMapMode(Self.Canvas.Handle, OldMapMode);
end;
cmpList.Add(
IntToStr(originalStrSize.cx) + ';' +
IntToStr(round(ratio * originalStrSize.cx)) + ';' +
IntToStr(scaledStrSize.cx) + ';' +
IntToStr(proposedStrSize.cx) + ';' +
IntToStr(round(ratio * originalStrSize.cx - scaledStrSize.cx)) + ';' +
IntToStr(round(ratio * originalStrSize.cx - proposedStrSize.cx))
);
end;
cmpList.SaveToFile('font_cmp.csv');
finally
cmpList.Free;
end;
end;
Другой способ решения проблемы масштабирования шрифта - закрасить его в растровое изображение в памяти и затем растянуть с помощью StretchBlt()
до нужного размера.
.
Та же идея, что и в предыдущем ответе, но реализация более ясна.
Базовые шаги:
SetMapMode()
SetWindowExtEx()
и SetViewPortExtEx()
StretchBlt()
in HALFTONE modeДалее следует код, например, сверху страницы.
Во-первых, я создаю одну новую функцию для вывода текста для очистки кода в обработчике OnPaint:
procedure DrawTestText(drawCanvas : TCanvas);
const
ShortString = 'Short';
MediumString = 'This is a little longer';
LongString = 'Here is something that is really long here is where I see the problem with zooming.';
var
LF : TLogFont;
OldFont : HFONT;
NewFont : HFONT;
begin
FillChar(LF, SizeOf(TLogFont), 0);
LF.lfOutPrecision := OUT_TT_ONLY_PRECIS;
LF.lfFaceName := 'Arial';
LF.lfHeight := -12;
LF.lfQuality := PROOF_QUALITY;
NewFont := CreateFontIndirect(LF);
try
OldFont := Windows.SelectObject(drawCanvas.Handle, NewFont);
try
OutputText(drawCanvas, 3, ShortString);
OutputText(drawCanvas, 4, MediumString);
OutputText(drawCanvas, 5, LongString);
finally
Windows.SelectObject(drawCanvas.Handle, OldFont);
end;
finally
Windows.DeleteObject(NewFont);
end;
end;
А дальше - код для события OnPaint:
procedure TForm1.FormPaint(Sender: TObject);
const
PhysicalHeight = 500;
PhysicalWidth = 400;
var
bmp : TBitmap;
bufferBitmap : TBitmap;
drawCanvas : TCanvas;
OldMapMode : integer;
OldStretchMode : integer;
outHeight : extended;
begin
// compute desired height
outHeight := PhysicalHeight * (ClientWidth / PhysicalWidth) ;
// 1. Set MM_ISOTROPIC mapping mode with SetMapMode()
OldMapMode := SetMapMode(Self.Canvas.Handle, MM_ISOTROPIC);
try
// 2. Define coordinate mappings with SetWindowExtEx() and SetViewPortExtEx()
SetWindowExtEx(Self.Canvas.Handle, PhysicalWidth, PhysicalHeight, nil);
SetViewportExtEx(Self.Canvas.Handle, Self.Width, round(outHeight), nil);
SelectClipRgn(Self.Canvas.Handle, CreateRectRgn(0,0, Width, round(outHeight)));
// 3. Draw lines and graphics
DrawGrid(Self.Canvas);
finally
// 4. Restore mapping mode
SetMapMode(Self.Canvas.Handle, OldMapMode);
end;
// 5. Create bitmap with original size
bmp := TBitmap.Create;
try
bmp.Transparent := false;
bmp.Width := PhysicalWidth;
bmp.Height := PhysicalHeight;
drawCanvas := bmp.Canvas;
drawCanvas.Font.Assign(Self.Canvas.Font);
drawCanvas.Brush.Assign(Self.Canvas.Brush);
drawCanvas.Pen.Assign(Self.Canvas.Pen);
drawCanvas.Brush.Style := bsSolid;
drawCanvas.Brush.Color := Color;
drawCanvas.FillRect(Rect(0,0,PhysicalWidth, PhysicalHeight));
// 6. Draw text on bitmap
DrawTestText(drawCanvas);
// 7. Create transparent bitmap with desired size
bufferBitmap := TBitmap.Create;
try
bufferBitmap.PixelFormat := pfDevice;
bufferBitmap.TransparentColor := Color;
bufferBitmap.Transparent := true;
bufferBitmap.Width := ClientWidth;
bufferBitmap.Height := round(outHeight);
bufferBitmap.Canvas.Brush.Style := bsSolid;
bufferBitmap.Canvas.Brush.Color := Color;
bufferBitmap.Canvas.FillRect(Rect(0,0,bufferBitmap.Width, bufferBitmap.Height));
// 8. Copy content of bitmap with text to transparent bitmap with StretchBlt() in HALFTONE mode
OldStretchMode := SetStretchBltMode(bufferBitmap.Canvas.Handle, HALFTONE);
try
SetBrushOrgEx(bufferBitmap.Canvas.Handle, 0, 0, nil);
StretchBlt(
bufferBitmap.Canvas.Handle, 0, 0, bufferBitmap.Width, bufferBitmap.Height,
drawCanvas.Handle, 0, 0, PhysicalWidth, PhysicalHeight,
SRCCOPY
);
finally
SetStretchBltMode(bufferBitmap.Canvas.Handle, oldStretchMode);
end;
// 9. Draw transparent bitmap, which contains text now, on form's canvas
Self.Canvas.Draw(0,0,bufferBitmap);
// 10. Destroy both bitmaps
finally
bufferBitmap.Free;
end;
finally
bmp.Free;
end;
end;