1 /* 2 * fontview 3 * 4 * fontview.c 5 * 6 * Copyright (C) 2007 Timo Kreuzer <timo <dot> kreuzer <at> reactos <dot> org> 7 * Copyright (C) 2016-2017 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 */ 23 24 #include "precomp.h" 25 26 #include <winnls.h> 27 #include <shellapi.h> 28 #include <windowsx.h> 29 #include <winreg.h> 30 31 #include "fontview.h" 32 #include "resource.h" 33 34 HINSTANCE g_hInstance; 35 INT g_FontIndex = 0; 36 INT g_NumFonts = 0; 37 LOGFONTW g_LogFonts[64]; 38 LPCWSTR g_fileName = L""; 39 WCHAR g_FontTitle[1024] = L""; 40 BOOL g_FontPrint = FALSE; 41 BOOL g_DisableInstall = FALSE; 42 43 static const WCHAR g_szFontViewClassName[] = L"FontViewWClass"; 44 45 /* GetFontResourceInfoW is undocumented */ 46 BOOL WINAPI GetFontResourceInfoW(LPCWSTR lpFileName, DWORD *pdwBufSize, void* lpBuffer, DWORD dwType); 47 48 DWORD 49 FormatString( 50 DWORD dwFlags, 51 HINSTANCE hInstance, 52 DWORD dwStringId, 53 DWORD dwLanguageId, 54 LPWSTR lpBuffer, 55 DWORD nSize, 56 va_list* Arguments 57 ) 58 { 59 DWORD dwRet; 60 int len; 61 WCHAR Buffer[1000]; 62 63 len = LoadStringW(hInstance, dwStringId, (LPWSTR)Buffer, 1000); 64 65 if (len) 66 { 67 dwFlags |= FORMAT_MESSAGE_FROM_STRING; 68 dwFlags &= ~(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM); 69 dwRet = FormatMessageW(dwFlags, Buffer, 0, dwLanguageId, lpBuffer, nSize, Arguments); 70 return dwRet; 71 } 72 return 0; 73 } 74 75 static void 76 ErrorMsgBox(HWND hParent, DWORD dwMessageId, ...) 77 { 78 HLOCAL hMemCaption = NULL; 79 HLOCAL hMemText = NULL; 80 va_list args; 81 82 va_start(args, dwMessageId); 83 FormatString(FORMAT_MESSAGE_ALLOCATE_BUFFER, 84 NULL, dwMessageId, 0, (LPWSTR)&hMemText, 0, &args); 85 va_end(args); 86 87 FormatString(FORMAT_MESSAGE_ALLOCATE_BUFFER, 88 NULL, IDS_ERROR, 0, (LPWSTR)&hMemCaption, 0, NULL); 89 90 MessageBoxW(hParent, hMemText, hMemCaption, MB_ICONERROR); 91 92 LocalFree(hMemCaption); 93 LocalFree(hMemText); 94 } 95 96 int WINAPI 97 wWinMain(HINSTANCE hThisInstance, 98 HINSTANCE hPrevInstance, 99 LPWSTR lpCmdLine, 100 int nCmdShow) 101 { 102 int argc; 103 INT i; 104 WCHAR** argv; 105 DWORD dwSize; 106 HWND hMainWnd; 107 MSG msg; 108 WNDCLASSEXW wincl; 109 LPCWSTR fileName; 110 111 switch (GetUserDefaultUILanguage()) 112 { 113 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT): 114 SetProcessDefaultLayout(LAYOUT_RTL); 115 break; 116 117 default: 118 break; 119 } 120 121 g_hInstance = hThisInstance; 122 123 /* Get unicode command line */ 124 argv = CommandLineToArgvW(GetCommandLineW(), &argc); 125 if (argc < 2) 126 { 127 #if 0 128 WCHAR szFileName[MAX_PATH] = L""; 129 OPENFILENAMEW fontOpen; 130 WCHAR filter[MAX_PATH*2] = {0}, dialogTitle[MAX_PATH]; 131 132 LoadStringW(NULL, IDS_OPEN, dialogTitle, ARRAYSIZE(dialogTitle)); 133 LoadStringW(NULL, IDS_FILTER_LIST, filter, ARRAYSIZE(filter) - 1); 134 135 /* Clears out any values of fontOpen before we use it */ 136 ZeroMemory(&fontOpen, sizeof(fontOpen)); 137 138 /* Sets up the open dialog box */ 139 fontOpen.lStructSize = sizeof(fontOpen); 140 fontOpen.hwndOwner = NULL; 141 fontOpen.lpstrFilter = filter; 142 fontOpen.lpstrFile = szFileName; 143 fontOpen.lpstrTitle = dialogTitle; 144 fontOpen.nMaxFile = MAX_PATH; 145 fontOpen.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; 146 fontOpen.lpstrDefExt = L"ttf"; 147 148 /* Opens up the Open File dialog box in order to chose a font file. */ 149 if(GetOpenFileNameW(&fontOpen)) 150 { 151 fileName = fontOpen.lpstrFile; 152 g_fileName = fileName; 153 } else { 154 /* If the user decides to close out of the open dialog effectively 155 exiting the program altogether */ 156 return 0; 157 } 158 #endif 159 } 160 else 161 { 162 /* Try to add the font resource from command line */ 163 for (i = 1; i < argc; ++i) 164 { 165 // Treat the last argument as filename 166 if (i + 1 == argc) 167 { 168 fileName = argv[i]; 169 } 170 else if (argv[i][0] == '/' || argv[i][0] == '-') 171 { 172 switch (argv[i][1]) 173 { 174 case 'p': 175 case 'P': 176 g_FontPrint = TRUE; 177 break; 178 case 'd': 179 case 'D': 180 g_DisableInstall = TRUE; 181 break; 182 default: 183 fileName = argv[i]; 184 break; 185 } 186 } 187 else 188 { 189 fileName = argv[i]; 190 } 191 } 192 g_fileName = fileName; 193 } 194 195 if (!AddFontResourceW(g_fileName)) 196 { 197 ErrorMsgBox(0, IDS_ERROR_NOFONT, g_fileName); 198 return -1; 199 } 200 201 /* Get the font name */ 202 dwSize = sizeof(g_LogFonts); 203 ZeroMemory(g_LogFonts, sizeof(g_LogFonts)); 204 if (!GetFontResourceInfoW(fileName, &dwSize, g_LogFonts, 2)) 205 { 206 ErrorMsgBox(0, IDS_ERROR_NOFONT, fileName); 207 return -1; 208 } 209 g_NumFonts = 0; 210 for (i = 0; i < ARRAYSIZE(g_LogFonts); ++i) 211 { 212 if (g_LogFonts[i].lfFaceName[0] == 0) 213 break; 214 215 ++g_NumFonts; 216 } 217 if (g_NumFonts == 0) 218 { 219 ErrorMsgBox(0, IDS_ERROR_NOFONT, fileName); 220 return -1; 221 } 222 223 /* get font title */ 224 dwSize = sizeof(g_FontTitle); 225 ZeroMemory(g_FontTitle, sizeof(g_FontTitle)); 226 GetFontResourceInfoW(fileName, &dwSize, g_FontTitle, 1); 227 228 if (!Display_InitClass(hThisInstance)) 229 { 230 ErrorMsgBox(0, IDS_ERROR_NOCLASS); 231 return -1; 232 } 233 234 /* The main window class */ 235 wincl.cbSize = sizeof (WNDCLASSEXW); 236 wincl.style = CS_DBLCLKS; 237 wincl.lpfnWndProc = MainWndProc; 238 wincl.cbClsExtra = 0; 239 wincl.cbWndExtra = 0; 240 wincl.hInstance = hThisInstance; 241 wincl.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TT)); 242 wincl.hCursor = LoadCursor (NULL, IDC_ARROW); 243 wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND; 244 wincl.lpszMenuName = NULL; 245 wincl.lpszClassName = g_szFontViewClassName; 246 wincl.hIconSm = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TT)); 247 248 /* Register the window class, and if it fails quit the program */ 249 if (!RegisterClassExW (&wincl)) 250 { 251 ErrorMsgBox(0, IDS_ERROR_NOCLASS); 252 return 0; 253 } 254 255 /* The class is registered, let's create the main window */ 256 hMainWnd = CreateWindowExW( 257 0, /* Extended possibilities for variation */ 258 g_szFontViewClassName, /* Classname */ 259 g_FontTitle, /* Title Text */ 260 WS_OVERLAPPEDWINDOW, /* default window */ 261 CW_USEDEFAULT, /* Windows decides the position */ 262 CW_USEDEFAULT, /* where the window ends up on the screen */ 263 544, /* The programs width */ 264 375, /* and height in pixels */ 265 HWND_DESKTOP, /* The window is a child-window to desktop */ 266 NULL, /* No menu */ 267 hThisInstance, /* Program Instance handler */ 268 NULL /* No Window Creation data */ 269 ); 270 ShowWindow(hMainWnd, nCmdShow); 271 272 /* Main message loop */ 273 while (GetMessage (&msg, NULL, 0, 0)) 274 { 275 if (IsDialogMessage(hMainWnd, &msg)) 276 continue; 277 TranslateMessage(&msg); 278 DispatchMessage(&msg); 279 } 280 281 RemoveFontResourceW(argv[1]); 282 283 return (int)msg.wParam; 284 } 285 286 static LRESULT 287 MainWnd_OnCreate(HWND hwnd) 288 { 289 WCHAR szQuit[MAX_BUTTONNAME]; 290 WCHAR szPrint[MAX_BUTTONNAME]; 291 WCHAR szString[MAX_STRING]; 292 WCHAR szPrevious[MAX_STRING]; 293 WCHAR szNext[MAX_STRING]; 294 HWND hDisplay, hButtonInstall, hButtonPrint, hButtonPrev, hButtonNext; 295 296 /* create the display window */ 297 hDisplay = CreateWindowExW( 298 0, /* Extended style */ 299 g_szFontDisplayClassName, /* Classname */ 300 L"", /* Title text */ 301 WS_CHILD | WS_VSCROLL, /* Window style */ 302 0, /* X-pos */ 303 HEADER_SIZE, /* Y-Pos */ 304 550, /* Width */ 305 370-HEADER_SIZE, /* Height */ 306 hwnd, /* Parent */ 307 (HMENU)IDC_DISPLAY, /* Identifier */ 308 g_hInstance, /* Program Instance handler */ 309 NULL /* Window Creation data */ 310 ); 311 312 LoadStringW(g_hInstance, IDS_STRING, szString, MAX_STRING); 313 SendMessage(hDisplay, FVM_SETSTRING, 0, (LPARAM)szString); 314 315 /* Create the install button */ 316 LoadStringW(g_hInstance, IDS_INSTALL, szQuit, MAX_BUTTONNAME); 317 hButtonInstall = CreateWindowExW( 318 0, /* Extended style */ 319 L"button", /* Classname */ 320 szQuit, /* Title text */ 321 WS_CHILD | WS_VISIBLE, /* Window style */ 322 BUTTON_POS_X, /* X-pos */ 323 BUTTON_POS_Y, /* Y-Pos */ 324 BUTTON_WIDTH, /* Width */ 325 BUTTON_HEIGHT, /* Height */ 326 hwnd, /* Parent */ 327 (HMENU)IDC_INSTALL, /* Identifier */ 328 g_hInstance, /* Program Instance handler */ 329 NULL /* Window Creation data */ 330 ); 331 SendMessage(hButtonInstall, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)TRUE); 332 EnableWindow(hButtonInstall, !g_DisableInstall); 333 334 /* Create the print button */ 335 LoadStringW(g_hInstance, IDS_PRINT, szPrint, MAX_BUTTONNAME); 336 hButtonPrint = CreateWindowExW( 337 0, /* Extended style */ 338 L"button", /* Classname */ 339 szPrint, /* Title text */ 340 WS_CHILD | WS_VISIBLE, /* Window style */ 341 450, /* X-pos */ 342 BUTTON_POS_Y, /* Y-Pos */ 343 BUTTON_WIDTH, /* Width */ 344 BUTTON_HEIGHT, /* Height */ 345 hwnd, /* Parent */ 346 (HMENU)IDC_PRINT, /* Identifier */ 347 g_hInstance, /* Program Instance handler */ 348 NULL /* Window Creation data */ 349 ); 350 SendMessage(hButtonPrint, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)TRUE); 351 352 /* Create the previous button */ 353 LoadStringW(g_hInstance, IDS_PREVIOUS, szPrevious, MAX_BUTTONNAME); 354 hButtonPrev = CreateWindowExW( 355 0, /* Extended style */ 356 L"button", /* Classname */ 357 szPrevious, /* Title text */ 358 WS_CHILD | WS_VISIBLE, /* Window style */ 359 450, /* X-pos */ 360 BUTTON_POS_Y, /* Y-Pos */ 361 BUTTON_WIDTH, /* Width */ 362 BUTTON_HEIGHT, /* Height */ 363 hwnd, /* Parent */ 364 (HMENU)IDC_PREV, /* Identifier */ 365 g_hInstance, /* Program Instance handler */ 366 NULL /* Window Creation data */ 367 ); 368 SendMessage(hButtonPrev, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)TRUE); 369 370 /* Create the next button */ 371 LoadStringW(g_hInstance, IDS_NEXT, szNext, MAX_BUTTONNAME); 372 hButtonNext = CreateWindowExW( 373 0, /* Extended style */ 374 L"button", /* Classname */ 375 szNext, /* Title text */ 376 WS_CHILD | WS_VISIBLE, /* Window style */ 377 450, /* X-pos */ 378 BUTTON_POS_Y, /* Y-Pos */ 379 BUTTON_WIDTH, /* Width */ 380 BUTTON_HEIGHT, /* Height */ 381 hwnd, /* Parent */ 382 (HMENU)IDC_NEXT, /* Identifier */ 383 g_hInstance, /* Program Instance handler */ 384 NULL /* Window Creation data */ 385 ); 386 SendMessage(hButtonNext, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)TRUE); 387 388 EnableWindow(hButtonPrev, FALSE); 389 if (g_NumFonts <= 1) 390 EnableWindow(hButtonNext, FALSE); 391 392 /* Init the display window with the font name */ 393 g_FontIndex = 0; 394 SendMessage(hDisplay, FVM_SETTYPEFACE, 0, (LPARAM)&g_LogFonts[g_FontIndex]); 395 ShowWindow(hDisplay, SW_SHOWNORMAL); 396 397 if (g_FontPrint) 398 PostMessage(hwnd, WM_COMMAND, IDC_PRINT, 0); 399 400 return 0; 401 } 402 403 static LRESULT 404 MainWnd_OnSize(HWND hwnd) 405 { 406 RECT rc; 407 HWND hInstall, hPrint, hPrev, hNext, hDisplay; 408 HDWP hDWP; 409 410 GetClientRect(hwnd, &rc); 411 412 hDWP = BeginDeferWindowPos(5); 413 414 hInstall = GetDlgItem(hwnd, IDC_INSTALL); 415 if (hDWP) 416 hDWP = DeferWindowPos(hDWP, hInstall, NULL, BUTTON_POS_X, BUTTON_POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, SWP_NOZORDER); 417 418 hPrint = GetDlgItem(hwnd, IDC_PRINT); 419 if (hDWP) 420 hDWP = DeferWindowPos(hDWP, hPrint, NULL, BUTTON_POS_X + BUTTON_WIDTH + BUTTON_PADDING, BUTTON_POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, SWP_NOZORDER); 421 422 hPrev = GetDlgItem(hwnd, IDC_PREV); 423 if (hDWP) 424 hDWP = DeferWindowPos(hDWP, hPrev, NULL, rc.right - (BUTTON_WIDTH * 2 + BUTTON_PADDING + BUTTON_POS_X), BUTTON_POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, SWP_NOZORDER); 425 426 hNext = GetDlgItem(hwnd, IDC_NEXT); 427 if (hDWP) 428 hDWP = DeferWindowPos(hDWP, hNext, NULL, rc.right - (BUTTON_WIDTH + BUTTON_POS_X), BUTTON_POS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, SWP_NOZORDER); 429 430 hDisplay = GetDlgItem(hwnd, IDC_DISPLAY); 431 if (hDWP) 432 hDWP = DeferWindowPos(hDWP, hDisplay, NULL, 0, HEADER_SIZE, rc.right, rc.bottom - HEADER_SIZE, SWP_NOZORDER); 433 434 EndDeferWindowPos(hDWP); 435 436 InvalidateRect(hwnd, NULL, TRUE); 437 438 return 0; 439 } 440 441 static LRESULT 442 MainWnd_OnPaint(HWND hwnd) 443 { 444 HDC hDC; 445 PAINTSTRUCT ps; 446 RECT rc; 447 448 hDC = BeginPaint(hwnd, &ps); 449 GetClientRect(hwnd, &rc); 450 rc.top = HEADER_SIZE - 2; 451 rc.bottom = HEADER_SIZE; 452 FillRect(hDC, &rc, GetStockObject(GRAY_BRUSH)); 453 EndPaint(hwnd, &ps); 454 return 0; 455 } 456 457 static LRESULT 458 MainWnd_OnInstall(HWND hwnd) 459 { 460 WCHAR szFullName[64]; 461 462 WCHAR szSrcPath[MAX_PATH]; 463 WCHAR szDestPath[MAX_PATH]; 464 PWSTR pszFileName; 465 LONG res; 466 HKEY hKey; 467 468 SendDlgItemMessage(hwnd, IDC_DISPLAY, FVM_GETFULLNAME, 64, (LPARAM)szFullName); 469 // MessageBoxW(hwnd, szFullName, L"Debug", MB_OK); 470 471 /* First, we have to find out if the font still exists */ 472 if (GetFileAttributes(g_fileName) == INVALID_FILE_ATTRIBUTES) 473 { 474 /* Fail, if the source file does not exist */ 475 ErrorMsgBox(0, IDS_ERROR_NOFONT, g_fileName); 476 return -1; 477 } 478 479 /* Build the full destination file name */ 480 GetFullPathNameW(g_fileName, MAX_PATH, szSrcPath, &pszFileName); 481 482 GetWindowsDirectoryW(szDestPath, MAX_PATH); 483 wcscat(szDestPath, L"\\Fonts\\"); 484 wcscat(szDestPath, pszFileName); 485 486 /* Debug Message */ 487 // MessageBoxW(hwnd, szDestPath, L"szDestPath", MB_OK); 488 // MessageBoxW(hwnd, pszFileName, L"pszFileExt", MB_OK); 489 490 /* Check if the file already exists */ 491 if (GetFileAttributesW(szDestPath) != INVALID_FILE_ATTRIBUTES) 492 { 493 MessageBoxW(hwnd, L"This font is already installed!", L"Already Installed", MB_OK); 494 return 0; 495 } 496 497 /* Copy the font file */ 498 if (!CopyFileW(g_fileName, szDestPath, TRUE)) 499 { 500 MessageBoxW(hwnd,L"Failed to copy the font file!", L"File Error", MB_OK); 501 return -1; 502 } 503 504 /* Open the fonts key */ 505 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, 506 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", 507 0, 508 KEY_ALL_ACCESS, 509 &hKey); 510 if (res != ERROR_SUCCESS) 511 { 512 MessageBoxW(hwnd, L"Failed top open the fonts key!", L"Debug1", MB_OK); 513 return -1; 514 } 515 516 /* Register the font */ 517 res = RegSetValueExW(hKey, 518 szFullName, 519 0, 520 REG_SZ, 521 (LPBYTE)pszFileName, 522 (wcslen(pszFileName) + 1) * sizeof(WCHAR)); 523 if (res != ERROR_SUCCESS) 524 { 525 MessageBoxW(hwnd, L"Failed to register the new font!", L"Debug2", MB_OK); 526 RegCloseKey(hKey); 527 return -1; 528 } 529 530 /* Close the fonts key */ 531 RegCloseKey(hKey); 532 533 /* Broadcast WM_FONTCHANGE message */ 534 SendMessageW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 535 536 /* if all of this goes correctly, message the user about success */ 537 MessageBoxW(hwnd, L"Font Installation Completed.", L"Success", MB_OK); 538 539 return 0; 540 } 541 542 static LRESULT 543 MainWnd_OnPrev(HWND hwnd) 544 { 545 HWND hDisplay; 546 if (g_FontIndex > 0) 547 { 548 --g_FontIndex; 549 EnableWindow(GetDlgItem(hwnd, IDC_NEXT), TRUE); 550 if (g_FontIndex == 0) 551 EnableWindow(GetDlgItem(hwnd, IDC_PREV), FALSE); 552 553 hDisplay = GetDlgItem(hwnd, IDC_DISPLAY); 554 SendMessage(hDisplay, FVM_SETTYPEFACE, 0, (LPARAM)&g_LogFonts[g_FontIndex]); 555 InvalidateRect(hDisplay, NULL, TRUE); 556 } 557 return 0; 558 } 559 560 static LRESULT 561 MainWnd_OnNext(HWND hwnd) 562 { 563 HWND hDisplay; 564 if (g_FontIndex + 1 < g_NumFonts) 565 { 566 ++g_FontIndex; 567 EnableWindow(GetDlgItem(hwnd, IDC_PREV), TRUE); 568 if (g_FontIndex == g_NumFonts - 1) 569 EnableWindow(GetDlgItem(hwnd, IDC_NEXT), FALSE); 570 571 hDisplay = GetDlgItem(hwnd, IDC_DISPLAY); 572 SendMessage(hDisplay, FVM_SETTYPEFACE, 0, (LPARAM)&g_LogFonts[g_FontIndex]); 573 InvalidateRect(hDisplay, NULL, TRUE); 574 } 575 return 0; 576 } 577 578 LRESULT CALLBACK 579 MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 580 { 581 switch (message) 582 { 583 case WM_CREATE: 584 return MainWnd_OnCreate(hwnd); 585 586 case WM_PAINT: 587 return MainWnd_OnPaint(hwnd); 588 589 case WM_SIZE: 590 return MainWnd_OnSize(hwnd); 591 592 case WM_COMMAND: 593 switch(LOWORD(wParam)) 594 { 595 case IDC_INSTALL: 596 return MainWnd_OnInstall(hwnd); 597 598 case IDC_PRINT: 599 return Display_OnPrint(hwnd); 600 601 case IDC_PREV: 602 return MainWnd_OnPrev(hwnd); 603 604 case IDC_NEXT: 605 return MainWnd_OnNext(hwnd); 606 } 607 break; 608 609 case WM_DESTROY: 610 PostQuitMessage (0); /* send a WM_QUIT to the message queue */ 611 break; 612 613 default: /* for messages that we don't deal with */ 614 return DefWindowProcW(hwnd, message, wParam, lParam); 615 } 616 617 return 0; 618 } 619 620 /* EOF */ 621