If you're working with a whole paragraph, you have to start at the beginning and scan through the string looking for blanks. Every time you encounter a blank, you call GetTextExtent to determine if the text still fits between the left and right margins. When the text exceeds the space allowed for it, then you backtrack to the previous blank. Now you have determined the character string for the line. If you want to justify the line, call SetTextJustification and TextOut, clear out the error term, and proceed to the next line.
The JUSTIFY program, shown in Figure 14-9, does this job for the first paragraph of Herman Melville's Moby Dick. You choose a screen font, and the Alignment menu lets you align the text on the left or right, center it, or justify it. Figure 14-10 on page 713 shows a typical JUSTIFY screen.
JUSTIFY.MAK
#-----------------------
# JUSTIFY.MAK make file
#-----------------------
justify.exe : justify.obj justify.def justify.res
link justify, /align:16, NUL, /nod slibcew libw, justify
rc justify.res
justify.obj : justify.c justify.h
cl -c -Gsw -Ow -W2 -Zp justify.c
justify.res : justify.rc justify.asc justify.h
rc -r justify.rc
JUSTIFY.C
/*----------------------------------------
JUSTIFY.C -- Justified Type Program
(c) Charles Petzold, 1990
----------------------------------------*/
#include <windows.h>
#include <string.h>
#include "justify.h"
typedef struct
{
short nNumFaces ;
char szFaceNames [MAX_FACES] [LF_FACESIZE] ;
}
ENUMFACE ;
typedef struct
{
short nNumSizes ;
short xLogPixPerInch ;
short yLogPixPerInch ;
LOGFONT lf [MAX_SIZES] ;
TEXTMETRIC tm [MAX_SIZES] ;
}
ENUMSIZE ;
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int FAR PASCAL EnumAllFaces (LPLOGFONT, LPTEXTMETRIC, short, ENUMFACE FAR *) ;
int FAR PASCAL EnumAllSizes (LPLOGFONT, LPTEXTMETRIC, short, ENUMSIZE FAR *) ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName[] = "Justify" ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
}
hwnd = CreateWindow (szAppName, "Justified Type",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
int FAR PASCAL EnumAllFaces (LPLOGFONT lplf, LPTEXTMETRIC lptm,
short nFontType, ENUMFACE FAR *lpef)
{
if (nFontType & RASTER_FONTTYPE)
{
lstrcpy (lpef->szFaceNames[lpef->nNumFaces], lplf->lfFaceName) ;
if (++lpef->nNumFaces == MAX_FACES)
return 0 ;
}
return 1 ;
}
int FAR PASCAL EnumAllSizes (LPLOGFONT lplf, LPTEXTMETRIC lptm,
short nFontType, ENUMSIZE FAR *lpes)
{
if (lpes->xLogPixPerInch == lptm->tmDigitizedAspectX &&
lpes->yLogPixPerInch == lptm->tmDigitizedAspectY)
{
lpes->lf [lpes->nNumSizes] = *lplf ;
lpes->tm [lpes->nNumSizes] = *lptm ;
if (++lpes->nNumSizes == MAX_SIZES)
return 0 ;
}
return 1 ;
}
short MakeSizeMenu (HWND hwnd, FARPROC lpfnEnumAllSizes,
ENUMSIZE *pes, char *szFaceName)
{
static LOGFONT lfBlank ;
char szBuffer[20] ;
HDC hdc ;
HMENU hPopup ;
short i ;
hdc = GetDC (hwnd) ;
hPopup = GetSubMenu (GetMenu (hwnd), SIZE_MENU) ;
pes->nNumSizes = 0 ;
EnumFonts (hdc, szFaceName, lpfnEnumAllSizes, (LPSTR) pes) ;
ReleaseDC (hwnd, hdc) ;
while (GetMenuItemCount (hPopup) > 0)
DeleteMenu (hPopup, 0, MF_BYPOSITION) ;
if (pes->nNumSizes)
for (i = 0 ; i < pes->nNumSizes ; i++)
{
wsprintf (szBuffer, "%i %2d / %2d", i + 1,
(pes->tm[i].tmHeight - pes->tm[i].tmInternalLeading + 10)
/ 20,
(pes->tm[i].tmHeight + 10) / 20) ;
AppendMenu (hPopup, 0, IDM_ISIZE + i, szBuffer) ;
}
else /* no fonts found that match aspect ratio of display */
{
pes->lf[0] = lfBlank ;
strcpy (pes->lf[0].lfFaceName, szFaceName) ;
AppendMenu (hPopup, 0, IDM_ISIZE, "Default") ;
}
CheckMenuItem (hPopup, IDM_ISIZE, MF_CHECKED) ;
return 0 ;
}
void DrawRuler (HDC hdc, POINT ptClient)
{
static short nRuleSize [16] = { 360, 72, 144, 72, 216, 72, 144, 72,
288, 72, 144, 72, 216, 72, 144, 72 } ;
short i, j ;
MoveTo (hdc, 0, -360) ;
LineTo (hdc, ptClient.x, -360) ;
MoveTo (hdc, -360, 0) ;
LineTo (hdc, -360, ptClient.y) ;
for (i = 0, j = 0 ; i <= ptClient.x ; i += 1440 / 16, j++)
{
MoveTo (hdc, i, -360) ;
LineTo (hdc, i, -360 - nRuleSize [j % 16]) ;
}
for (i = 0, j = 0 ; i <= ptClient.y ; i += 1440 / 16, j++)
{
MoveTo (hdc, -360, i) ;
LineTo (hdc, -360 - nRuleSize [j % 16], i) ;
}
}
void Justify (HDC hdc, HANDLE hResource, POINT ptClient, short nCurAlign)
{
DWORD dwExtent ;
LPSTR lpText, lpBegin, lpEnd ;
short i, xStart, yStart, nBreakCount ;
lpText = LockResource (hResource) ;
yStart = 0 ;
do // for each text line
{
nBreakCount = 0 ;
while (*lpText == ' ') // skip over leading blanks
lpText++ ;
lpBegin = lpText ;
do // until the line is known
{
lpEnd = lpText ;
while (*lpText != '\0' && *lpText++ != ' ') ;
if (*lpText == '\0')
break ;
// for each space, calculate extents
nBreakCount++ ;
SetTextJustification (hdc, 0, 0) ;
dwExtent = GetTextExtent (hdc, lpBegin, lpText - lpBegin - 1) ;
}
while (LOWORD (dwExtent) < ptClient.x) ;
nBreakCount-- ;
while (*(lpEnd - 1) == ' ') // eliminate trailing blanks
{
lpEnd-- ;
nBreakCount-- ;
}
if (*lpText == '\0' || nBreakCount <= 0)
lpEnd = lpText ;
SetTextJustification (hdc, 0, 0) ;
dwExtent = GetTextExtent (hdc, lpBegin, lpEnd - lpBegin) ;
if (nCurAlign == IDM_LEFT) // use alignment for xStart
xStart = 0 ;
else if (nCurAlign == IDM_RIGHT)
xStart = ptClient.x - LOWORD (dwExtent) ;
else if (nCurAlign == IDM_CENTER)
xStart = (ptClient.x - LOWORD (dwExtent)) / 2 ;
else
{
if (*lpText != '\0' && nBreakCount > 0)
SetTextJustification (hdc, ptClient.x - LOWORD (dwExtent),
nBreakCount) ;
xStart = 0 ;
}
TextOut (hdc, xStart, yStart, lpBegin, lpEnd - lpBegin) ;
yStart += HIWORD (dwExtent) ;
lpText = lpEnd ;
}
while (*lpText && yStart < ptClient.y) ;
GlobalUnlock (hResource) ;
}
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static ENUMFACE ef ;
static ENUMSIZE es ;
static FARPROC lpfnEnumAllFaces, lpfnEnumAllSizes ;
static HANDLE hResource ;
static POINT ptClient ;
static short nCurSize, nCurFace, nCurAttr, nCurAlign = IDM_LEFT ;
HANDLE hInstance ;
HDC hdc ;
HFONT hFont ;
HMENU hMenu, hPopup ;
PAINTSTRUCT ps ;
short i ;
switch (message)
{
case WM_CREATE :
hdc = GetDC (hwnd) ;
es.xLogPixPerInch = GetDeviceCaps (hdc, LOGPIXELSX) ;
es.yLogPixPerInch = GetDeviceCaps (hdc, LOGPIXELSY) ;
// Set Map Mode
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1440, 1440) ;
SetViewportExt (hdc, es.xLogPixPerInch, es.yLogPixPerInch) ;
SetWindowOrg (hdc, -720, -720) ;
// MakeProcInstance for 2 routines
hInstance = ((LPCREATESTRUCT) lParam)-> hInstance ;
lpfnEnumAllFaces = MakeProcInstance (EnumAllFaces, hInstance) ;
lpfnEnumAllSizes = MakeProcInstance (EnumAllSizes, hInstance) ;
// Enumerate the Font Faces
EnumFonts (hdc, NULL, lpfnEnumAllFaces, (LPSTR) &ef) ;
ReleaseDC (hwnd, hdc) ;
// Initialize the Menus
hMenu = GetMenu (hwnd) ;
hPopup = CreateMenu () ;
for (i = 0 ; i < ef.nNumFaces ; i++)
AppendMenu (hPopup, 0, IDM_IFACE + i, ef.szFaceNames[i]) ;
ModifyMenu (hMenu, IDM_FACE, MF_POPUP, hPopup, "&FaceName") ;
CheckMenuItem (hMenu, IDM_IFACE, MF_CHECKED) ;
nCurSize = MakeSizeMenu (hwnd, lpfnEnumAllSizes, &es,
ef.szFaceNames [nCurFace]) ;
// Load the Text Resource
hResource = LoadResource (hInstance,
FindResource (hInstance, "Ishmael", "TEXT")) ;
return 0 ;
case WM_SIZE :
hdc = GetDC (hwnd) ;
ptClient = MAKEPOINT (lParam) ;
DPtoLP (hdc, &ptClient, 1) ;
ptClient.x -= 360 ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_COMMAND :
hMenu = GetMenu (hwnd) ;
if (wParam >= IDM_IFACE && wParam < IDM_IFACE + MAX_FACES)
{
CheckMenuItem (hMenu, nCurFace + IDM_IFACE, MF_UNCHECKED) ;
CheckMenuItem (hMenu, wParam, MF_CHECKED) ;
nCurFace = wParam - IDM_IFACE ;
nCurSize = MakeSizeMenu (hwnd, lpfnEnumAllSizes, &es,
ef.szFaceNames [nCurFace]) ;
}
else if (wParam >= IDM_ISIZE && wParam < IDM_ISIZE + MAX_SIZES)
{
CheckMenuItem (hMenu, nCurSize + IDM_ISIZE, MF_UNCHECKED) ;
CheckMenuItem (hMenu, wParam, MF_CHECKED) ;
nCurSize = wParam - IDM_ISIZE ;
}
else switch (wParam)
{
case IDM_BOLD :
case IDM_ITALIC :
case IDM_STRIKE :
case IDM_UNDER :
CheckMenuItem (hMenu, wParam, MF_CHECKED &
GetMenuState (hMenu, wParam, MF_BYCOMMAND) ?
MF_UNCHECKED : MF_CHECKED) ;
nCurAttr ^= wParam ;
break ;
case IDM_NORM :
nCurAttr = 0 ;
CheckMenuItem (hMenu, IDM_BOLD, MF_UNCHECKED) ;
CheckMenuItem (hMenu, IDM_ITALIC, MF_UNCHECKED) ;
CheckMenuItem (hMenu, IDM_STRIKE, MF_UNCHECKED) ;
CheckMenuItem (hMenu, IDM_UNDER, MF_UNCHECKED) ;
break ;
case IDM_LEFT :
case IDM_RIGHT :
case IDM_CENTER :
case IDM_JUST :
CheckMenuItem (hMenu, nCurAlign, MF_UNCHECKED) ;
nCurAlign = wParam ;
CheckMenuItem (hMenu, nCurAlign, MF_CHECKED) ;
break ;
}
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
es.lf[nCurSize].lfWeight = nCurAttr & IDM_BOLD ? 700 : 400 ;
es.lf[nCurSize].lfItalic = (BYTE) (nCurAttr & IDM_ITALIC) ;
es.lf[nCurSize].lfUnderline = (BYTE) (nCurAttr & IDM_UNDER) ;
es.lf[nCurSize].lfStrikeOut = (BYTE) (nCurAttr & IDM_STRIKE) ;
hFont = CreateFontIndirect (&es.lf[nCurSize]) ;
hFont = SelectObject (hdc, hFont) ;
DrawRuler (hdc, ptClient) ;
Justify (hdc, hResource, ptClient, nCurAlign) ;
DeleteObject (SelectObject (hdc, hFont)) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
FreeResource (hResource) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
JUSTIFY.RC
/*----------------------------
JUSTIFY.RC resource script
----------------------------*/
#include "justify.h"
Ishmael TEXT justify.asc
Justify MENU
{
MENUITEM "&FaceName", IDM_FACE
POPUP "&PointSize"
{
MENUITEM "temp", IDM_SIZE
}
POPUP "&Attributes"
{
MENUITEM "&Bold", IDM_BOLD
MENUITEM "&Italic", IDM_ITALIC
MENUITEM "&StrikeOut", IDM_STRIKE
MENUITEM "&Underline", IDM_UNDER
MENUITEM SEPARATOR
MENUITEM "&Normal", IDM_NORM
}
POPUP "A&lignment"
{
MENUITEM "&Left", IDM_LEFT, CHECKED
MENUITEM "&Right", IDM_RIGHT
MENUITEM "&Centered", IDM_CENTER
MENUITEM "&Justified", IDM_JUST
}
}
JUSTIFY.ASC
Call me Ishmael. Some years ago -- never mind how long precisely -
- having little or no money in my purse, and nothing particular to
interest me on shore, I thought I would sail about a little and see
the watery part of the world. It is a way I have of driving off the
spleen, and regulating the circulation. Whenever I find myself
growing grim about the mouth; whenever it is a damp, drizzly November
in my soul; whenever I find myself involuntarily pausing before coffin
warehouses, and bringing up the rear of every funeral I meet; and
especially whenever my hypos get such an upper hand of me, that it
requires a strong moral principle to prevent me from deliberately
stepping into the street, and methodically knocking people's hats
off -- then, I account it high time to get to sea as soon as I can.
This is my substitute for pistol and ball. With a philosophical
flourish Cato throws himself upon his sword; I quietly take to the
ship. There is nothing surprising in this. If they but knew it,
almost all men in their degree, some time or other, cherish very
nearly the same feelings towards the ocean with me.
JUSTIFY.H
/*-----------------------
JUSTIFY.H header file
-----------------------*/
#define SIZE_MENU 1
#define MAX_FACES 16
#define MAX_SIZES 16
#define IDM_BOLD 1
#define IDM_ITALIC 2
#define IDM_STRIKE 4
#define IDM_UNDER 8
#define IDM_FACE 0x10
#define IDM_SIZE 0x11
#define IDM_NORM 0x12
#define IDM_LEFT 0x13
#define IDM_RIGHT 0x14
#define IDM_CENTER 0x15
#define IDM_JUST 0x16
#define IDM_IFACE 0x20
#define IDM_ISIZE (IDM_IFACE + MAX_FACES)
JUSTIFY.DEF
;------------------------------------
; JUSTIFY.DEF module definition file
;------------------------------------
NAME JUSTIFY
DESCRIPTION 'Demonstration of Justified Text (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
EnumAllFaces
EnumAllSizes
Like the FONTLIST program, JUSTIFY uses the EnumFonts function to obtain the fonts available for the screen. The two call-back functions are called EnumAllFaces and EnumAllSizes. The program can store up to 16 typeface names and up to 16 font sizes for a particular typeface. During the WM_CREATE message, JUSTIFY calls EnumFonts with a NULL second parameter and puts the typeface names into the FaceName menu.
When you select a typeface from the FaceName menu, JUSTIFY calls EnumFonts again to get the available sizes of that typeface. JUSTIFY must then reconstruct the PointSize menu, which displays the font sizes in this format:
24 / 28
which means a 24-point font on a 28-point line spacing. The Attributes menu offers the options Bold, Italic, StrikeOut, and Underline, and the aforementioned Alignment menu has the options Left, Right, Centered, and Justified.
JUSTIFY uses the ”Logical Twips“ mapping mode to facilitate the translation into point sizes of the information available from the TEXTMETRIC structure. To avoid setting this mapping mode every time it obtains a device context, JUSTIFY uses the CS_OWNDC class style.
JUSTIFY displays a ruler (in logical inches, of course) across the top and down the left side of the client area. The DrawRuler function draws the ruler. The window origin is adjusted to begin 1/2 inch from the left and top of the client area. JUSTIFY also leaves a 1/4-inch margin at the right of the client area.
The text is a user-defined resource. It is in a one-line-per-paragraph format and is NULL-terminated. The bulk of the work involved with formatting this text is in the Justify function. JUSTIFY starts searching for blanks at the beginning of the text and uses GetTextExtent to measure each line. When the length of the line exceeds the width of the display area, JUSTIFY returns to the previous space and uses the line up to that point. Depending on the Alignment choice, the line is left aligned, right aligned, centered, or justified.
JUSTIFY isn't perfect. In particular, the justification logic falls apart when there is only one word in each line. Even if we solve this problem (which isn't a difficult one), the program still won't work properly when a single word is too long to fit within the left and right margins. Of course, matters can become even more complex when you start working with programs that can use multiple fonts on the same line (as Windows WRITE can). But nobody ever claimed this stuff was easy. It's just easier than if you were doing all the work yourself.