I am using the following code, adapted from here, to fit text in a rectangle in an MFC application, which works but I'd like it to use the vertical space as well.
It seems to be to do with using ExtTextOut, but I can't get it to fit with other functions. Any ideas?
void CCube::FontInRect(CDC *pDC, CString sText, CRect &rFont, DWORD dSettings) {
int i, nStringLength;
BOOL bResult;
int *pDx;
int nX = rFont.left;
int nY = rFont.top;
int Width = 0;
// How long is the string - you need this later in this code.
nStringLength = lstrlen(sText);
// Allocate enough memory for the intercharacter spacing array.
pDx = (int *)new int[sizeof(int)* nStringLength];
// Initialize the array with the standard values.
for (i = 0; i < nStringLength; i++) {
ABC abc;
if (!GetCharABCWidths(*pDC, sText[i], sText[i], &abc)) {
delete[] pDx;
return;
}
pDx[i] = abc.abcA + abc.abcB + abc.abcC;
// You need the width.
Width += pDx[i];
// Also, account for the Black extent of the string.
if (i == 0) {
// Adjustment before the first character for underhang
nX -= abc.abcA;
Width -= abc.abcA;
}
if (i == nStringLength - 1) {
// Adjustment for overhang
Width -= abc.abcC;
}
}
int deltaCX = rFont.right - rFont.left - Width;
int deltaCh = deltaCX / nStringLength;
int remainder = deltaCX % nStringLength;
int error = 0;
// Distribute the adjustment through the intercharacter spacing.
// For a more typographically correct approach, distribute the
// adjustment in the "white space."
for (i = 0; i < nStringLength; i++) {
pDx[i] += deltaCh;
error += remainder;
if (abs(error) >= nStringLength) // adjustment?
{
int adjustment = abs(error) / error;
pDx[i] += adjustment;
error -= nStringLength*adjustment;
}
}
// ExtTextOut() draws our text with our ICS array.
bResult = ExtTextOut(*pDC, nX, nY, 0, &rFont, sText, nStringLength, pDx);
// Clean up.
delete[] pDx;
}
(Please Ignore the size of the rectangles for now.) Current:
Desired:
You can use DrawText
with DT_CALCRECT
flag. You have to know the width of target rectangle, then DrawText will calculate the height of the rectangle which it needs. It let's you draw any paragraph.
CString str = L"Test Test Test Test Test Test Test Test\nLine 2";
CRect rtext(20, 20, 200, 0);
DWORD textformat = DT_TOP | DT_LEFT | DT_WORDBREAK;
dc.DrawText(str, &rtext, textformat| DT_CALCRECT);
CRect rc = rtext;
rc.InflateRect(2, 2);
CPen pen;
pen.CreatePen(PS_SOLID, 1, RGB(0,0,0));
dc.SelectObject(&pen);
dc.Rectangle(rc);
dc.DrawText(str, &rtext, textformat);
To force the text to fit in a fixed rectangle, simply use dc.DrawText(str, &target_rect, textformat);
The last lines will be clipped if necessary.
Also it's possible to fit text in a fixed rectangle by means adjusting the font size. This method is not very common, but here it is for the sake of example:
CString str = L"Test Test Test Test Test Test Test\nLine 2\nLine 3\nLine 4\nLine 5";
DWORD textformat = DT_TOP | DT_LEFT | DT_WORDBREAK;
CRect rctarget(20, 20, 200, 80);
CRect r = rctarget;
int gap = 1;
r.InflateRect(-gap, -gap);
CFont font;
LOGFONT logfont{ 0 };
wcscpy_s(logfont.lfFaceName, L"Segoe UI");
CRect rctext;
//Try different font heights. If large height won't
//fit then go down to the lowest possible height.
for (int fontheight = 24; fontheight > 10; fontheight--)
{
logfont.lfHeight = -fontheight;
font.CreateFontIndirect(&logfont);
CFont *oldfont = dc.SelectObject(&font);
rctext = r;
dc.DrawText(str, &rctext, textformat | DT_CALCRECT);
if (rctext.Height() < r.Height())
break;//it fits, stops here
dc.SelectObject(oldfont);
font.DeleteObject();
}
//uncomment this line to show all of the text without clipping
//rctarget.bottom = rctext.bottom + 2 * gap;
dc.Rectangle(rctarget);
rctarget.InflateRect(-gap, -gap);
dc.DrawText(str, &rctarget, textformat);