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