1 /*
2  * PROJECT:     ReactOS VGA Font Editor
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Implements the window showing the character boxes for a font
5  * COPYRIGHT:   Copyright 2008 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 static const WCHAR szFontBoxesWndClass[] = L"VGAFontEditFontBoxesWndClass";
11 
12 static VOID
13 DrawCharacterPixel(IN PAINTSTRUCT *ps, IN UINT uCharacter, IN UCHAR uRow, IN UCHAR uColumn, IN UCHAR uBit, IN COLORREF clBackground)
14 {
15     INT x;
16     INT y;
17 
18     x = (uCharacter % 16) * (CHARACTER_BOX_WIDTH + CHARACTER_BOX_PADDING) + 24 + uColumn;
19     y = (uCharacter / 16) * (CHARACTER_BOX_HEIGHT + CHARACTER_BOX_PADDING)+ 1 + CHARACTER_INFO_BOX_HEIGHT + 2 + uRow;
20 
21     SetPixel( ps->hdc, x, y, (uBit ? 0 : clBackground) );
22 }
23 
24 VOID
25 GetCharacterRect(IN UINT uFontRow, IN UINT uFontColumn, OUT LPRECT CharacterRect)
26 {
27     CharacterRect->left = uFontColumn * (CHARACTER_BOX_WIDTH + CHARACTER_BOX_PADDING);
28     CharacterRect->top = uFontRow * (CHARACTER_BOX_HEIGHT + CHARACTER_BOX_PADDING);
29     CharacterRect->right = CharacterRect->left + CHARACTER_BOX_WIDTH;
30     CharacterRect->bottom = CharacterRect->top + CHARACTER_BOX_HEIGHT;
31 }
32 
33 static INT
34 FontBoxesHitTest(IN UINT xPos, IN UINT yPos, OUT LPRECT CharacterRect)
35 {
36     UINT uFontColumn;
37     UINT uFontRow;
38 
39     uFontColumn = xPos / (CHARACTER_BOX_WIDTH + CHARACTER_BOX_PADDING);
40     uFontRow = yPos / (CHARACTER_BOX_HEIGHT + CHARACTER_BOX_PADDING);
41     GetCharacterRect(uFontRow, uFontColumn, CharacterRect);
42 
43     if(xPos > (UINT)CharacterRect->right || yPos > (UINT)CharacterRect->bottom)
44         // The user clicked on separator space, so return HITTEST_SEPARATOR
45         return HITTEST_SEPARATOR;
46     else
47         // Return the character number
48         return (uFontRow * 16 + uFontColumn);
49 }
50 
51 static VOID
52 SetSelectedCharacter(IN PFONT_WND_INFO Info, IN UINT uNewCharacter, OPTIONAL IN LPRECT NewCharacterRect)
53 {
54     LPRECT pCharacterRect;
55     RECT OldCharacterRect;
56     UINT uFontColumn;
57     UINT uFontRow;
58 
59     // Remove the selection of the old character
60     GetCharacterPosition(Info->uSelectedCharacter, &uFontRow, &uFontColumn);
61     GetCharacterRect(uFontRow, uFontColumn, &OldCharacterRect);
62     InvalidateRect(Info->hFontBoxesWnd, &OldCharacterRect, FALSE);
63 
64     // You may pass the RECT of the new character, otherwise we'll allocate memory for one and get it ourselves
65     if(NewCharacterRect)
66         pCharacterRect = NewCharacterRect;
67     else
68     {
69         GetCharacterPosition(uNewCharacter, &uFontRow, &uFontColumn);
70         pCharacterRect = (LPRECT) HeapAlloc( hProcessHeap, 0, sizeof(RECT) );
71         GetCharacterRect(uFontRow, uFontColumn, pCharacterRect);
72     }
73 
74     // Select the new character
75     Info->uSelectedCharacter = uNewCharacter;
76     InvalidateRect(Info->hFontBoxesWnd, pCharacterRect, FALSE);
77 
78     if(!NewCharacterRect)
79         HeapFree(hProcessHeap, 0, pCharacterRect);
80 }
81 
82 static VOID
83 DrawProc(IN PFONT_WND_INFO Info, IN PAINTSTRUCT* ps)
84 {
85     COLORREF clBackground;
86     HBRUSH hBrush = 0;
87     HBRUSH hOldBrush = 0;
88     HDC hBoxDC;
89     HFONT hFont;
90     HFONT hOldFont;
91     RECT CharacterRect;
92     UINT uFontColumn;
93     UINT uStartColumn;
94     UINT uEndColumn;
95     UINT uFontRow;
96     UINT uStartRow;
97     UINT uEndRow;
98     UINT uCharacter;
99     UCHAR uCharacterColumn;
100     UCHAR uCharacterRow;
101     UCHAR uBit;
102     WCHAR szInfoText[9];
103     HBITMAP hBitmapOld;
104 
105     // Preparations
106     hBoxDC = CreateCompatibleDC(NULL);
107     hBitmapOld = SelectObject(hBoxDC, Info->MainWndInfo->hBoxBmp);
108 
109     hFont = CreateFontW(13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Tahoma");
110     hOldFont = SelectObject(ps->hdc, hFont);
111 
112     SetBkMode( ps->hdc, TRANSPARENT );
113 
114     // What ranges do we have to draw?
115     uStartRow = ps->rcPaint.top / (CHARACTER_BOX_HEIGHT + CHARACTER_BOX_PADDING);
116     uEndRow = ps->rcPaint.bottom / (CHARACTER_BOX_HEIGHT + CHARACTER_BOX_PADDING);
117     uStartColumn = ps->rcPaint.left / (CHARACTER_BOX_WIDTH + CHARACTER_BOX_PADDING);
118     uEndColumn = ps->rcPaint.right / (CHARACTER_BOX_WIDTH + CHARACTER_BOX_PADDING);
119 
120     for(uFontRow = uStartRow; uFontRow <= uEndRow; uFontRow++)
121     {
122         for(uFontColumn = uStartColumn; uFontColumn <= uEndColumn; uFontColumn++)
123         {
124             GetCharacterRect(uFontRow, uFontColumn, &CharacterRect);
125             uCharacter = uFontRow * 16 + uFontColumn;
126 
127             // Draw the Character Info Box (header)
128             BitBlt(ps->hdc,
129                    CharacterRect.left,
130                    CharacterRect.top,
131                    CHARACTER_BOX_WIDTH,
132                    CHARACTER_INFO_BOX_HEIGHT,
133                    hBoxDC,
134                    0,
135                    0,
136                    SRCCOPY);
137 
138             // Draw the header text
139             wsprintfW(szInfoText, L"%02u = %02X", uCharacter, uCharacter);
140             DrawTextW( ps->hdc, szInfoText, -1, &CharacterRect, DT_CENTER );
141 
142             // Draw the Character Bitmap Box (rectangle with the actual character)
143             if(Info->uSelectedCharacter == uCharacter)
144             {
145                 clBackground = RGB(255, 255, 0);
146                 hBrush = CreateSolidBrush(clBackground);
147                 hOldBrush = SelectObject(ps->hdc, hBrush);
148             }
149             else
150             {
151                 clBackground = RGB(255, 255, 255);
152                 SelectObject( ps->hdc, GetStockObject(WHITE_BRUSH) );
153             }
154 
155             Rectangle(ps->hdc,
156                       CharacterRect.left,
157                       CharacterRect.top + CHARACTER_INFO_BOX_HEIGHT,
158                       CharacterRect.right,
159                       CharacterRect.bottom);
160 
161             // Draw the actual character into the box
162             for(uCharacterRow = 0; uCharacterRow < 8; uCharacterRow++)
163             {
164                 for(uCharacterColumn = 0; uCharacterColumn < 8; uCharacterColumn++)
165                 {
166                     uBit = Info->Font->Bits[uCharacter * 8 + uCharacterRow] << uCharacterColumn & 0x80;
167                     DrawCharacterPixel(ps, uCharacter, uCharacterRow, uCharacterColumn, uBit, clBackground);
168                 }
169             }
170         }
171     }
172 
173     SelectObject(hBoxDC, hBitmapOld);
174     SelectObject(ps->hdc, hOldFont);
175     DeleteObject(hFont);
176     SelectObject(ps->hdc, hOldBrush);
177     DeleteObject(hBrush);
178     DeleteDC(hBoxDC);
179 }
180 
181 VOID
182 EditCurrentGlyph(PFONT_WND_INFO FontWndInfo)
183 {
184     PEDIT_GLYPH_INFO EditGlyphInfo;
185 
186     // Has the window for this character already been opened?
187     EditGlyphInfo = FontWndInfo->FirstEditGlyphWnd;
188 
189     while(EditGlyphInfo)
190     {
191         if(EditGlyphInfo->uCharacter == FontWndInfo->uSelectedCharacter)
192         {
193             // Yes, it has. Bring it to the front.
194             SetFocus(EditGlyphInfo->hSelf);
195             return;
196         }
197 
198         EditGlyphInfo = EditGlyphInfo->NextEditGlyphWnd;
199     }
200 
201     // No. Then create a new one
202     EditGlyphInfo = (PEDIT_GLYPH_INFO) HeapAlloc( hProcessHeap, 0, sizeof(EDIT_GLYPH_INFO) );
203     EditGlyphInfo->FontWndInfo = FontWndInfo;
204     EditGlyphInfo->uCharacter = FontWndInfo->uSelectedCharacter;
205     RtlCopyMemory( EditGlyphInfo->CharacterBits, FontWndInfo->Font->Bits + FontWndInfo->uSelectedCharacter * 8, sizeof(EditGlyphInfo->CharacterBits) );
206 
207     // Add the new window to the linked list
208     EditGlyphInfo->PrevEditGlyphWnd = FontWndInfo->LastEditGlyphWnd;
209     EditGlyphInfo->NextEditGlyphWnd = NULL;
210 
211     if(FontWndInfo->LastEditGlyphWnd)
212         FontWndInfo->LastEditGlyphWnd->NextEditGlyphWnd = EditGlyphInfo;
213     else
214         FontWndInfo->FirstEditGlyphWnd = EditGlyphInfo;
215 
216     FontWndInfo->LastEditGlyphWnd = EditGlyphInfo;
217 
218     // Open the window as a modeless dialog, so people can edit several characters at the same time.
219     EditGlyphInfo->hSelf = CreateDialogParamW(hInstance, MAKEINTRESOURCEW(IDD_EDITGLYPH), FontWndInfo->hSelf, EditGlyphDlgProc, (LPARAM)EditGlyphInfo);
220     ShowWindow(EditGlyphInfo->hSelf, SW_SHOW);
221 }
222 
223 VOID
224 CreateFontBoxesWindow(IN PFONT_WND_INFO FontWndInfo)
225 {
226     FontWndInfo->hFontBoxesWnd = CreateWindowExW(0,
227                                                  szFontBoxesWndClass,
228                                                  0,
229                                                  WS_CHILD | WS_VISIBLE,
230                                                  0,
231                                                  0,
232                                                  0,
233                                                  0,
234                                                  FontWndInfo->hSelf,
235                                                  NULL,
236                                                  hInstance,
237                                                  FontWndInfo);
238 }
239 
240 static LRESULT CALLBACK
241 FontBoxesWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
242 {
243     PFONT_WND_INFO Info;
244 
245     Info = (PFONT_WND_INFO) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
246 
247     if(Info || uMsg == WM_CREATE)
248     {
249         switch(uMsg)
250         {
251             case WM_CREATE:
252                 Info = (PFONT_WND_INFO)( ( (LPCREATESTRUCT)lParam )->lpCreateParams );
253                 SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)Info);
254 
255                 // Set a fixed window size
256                 SetWindowPos(hwnd, NULL, 0, 0, FONT_BOXES_WND_WIDTH, FONT_BOXES_WND_HEIGHT, SWP_NOZORDER | SWP_NOMOVE);
257 
258                 return 0;
259 
260             case WM_DESTROY:
261                 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
262                 return 0;
263 
264             case WM_KEYDOWN:
265                 switch(wParam)
266                 {
267                     case VK_DOWN:
268                         if(Info->uSelectedCharacter < 239)
269                             SetSelectedCharacter(Info, Info->uSelectedCharacter + 16, NULL);
270                         return 0;
271 
272                     case VK_LEFT:
273                         if(Info->uSelectedCharacter)
274                             SetSelectedCharacter(Info, Info->uSelectedCharacter - 1, NULL);
275                         return 0;
276 
277                     case VK_RETURN:
278                         EditCurrentGlyph(Info);
279                         return 0;
280 
281                     case VK_RIGHT:
282                         if(Info->uSelectedCharacter < 255)
283                             SetSelectedCharacter(Info, Info->uSelectedCharacter + 1, NULL);
284                         return 0;
285 
286                     case VK_UP:
287                         if(Info->uSelectedCharacter > 15)
288                             SetSelectedCharacter(Info, Info->uSelectedCharacter - 16, NULL);
289                         return 0;
290                 }
291 
292                 break;
293 
294             case WM_LBUTTONDBLCLK:
295             {
296                 EditCurrentGlyph(Info);
297                 return 0;
298             }
299 
300             case WM_LBUTTONDOWN:
301             {
302                 RECT CharacterRect;
303                 INT iRet;
304 
305                 iRet = FontBoxesHitTest( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &CharacterRect );
306 
307                 if(iRet >= 0)
308                     SetSelectedCharacter( Info, (UINT)iRet, &CharacterRect );
309 
310                 return 0;
311             }
312 
313             case WM_PAINT:
314             {
315                 PAINTSTRUCT ps;
316 
317                 BeginPaint(hwnd, &ps);
318                 DrawProc(Info, &ps);
319                 EndPaint(hwnd, &ps);
320 
321                 return 0;
322             }
323         }
324     }
325 
326     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
327 }
328 
329 BOOL
330 InitFontBoxesWndClass(VOID)
331 {
332     WNDCLASSW wc = {0,};
333 
334     wc.lpfnWndProc    = FontBoxesWndProc;
335     wc.hInstance      = hInstance;
336     wc.hCursor        = LoadCursor( NULL, IDC_ARROW );
337     wc.hbrBackground  = (HBRUSH)( COLOR_BTNFACE + 1 );
338     wc.lpszClassName  = szFontBoxesWndClass;
339     wc.style          = CS_DBLCLKS;
340 
341     return RegisterClassW(&wc) != 0;
342 }
343 
344 VOID
345 UnInitFontBoxesWndClass(VOID)
346 {
347     UnregisterClassW(szFontBoxesWndClass, hInstance);
348 }
349