Working with Paragraphs

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.