1 /* 2 * PROJECT: ReactOS Font Shell Extension 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: CFontExt implementation 5 * COPYRIGHT: Copyright 2019,2020 Mark Jansen (mark.jansen@reactos.org) 6 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 7 */ 8 9 #include "precomp.h" 10 #include "undocgdi.h" // for GetFontResourceInfoW 11 12 WINE_DEFAULT_DEBUG_CHANNEL(fontext); 13 14 15 struct FolderViewColumns 16 { 17 int iResource; 18 DWORD dwDefaultState; 19 int cxChar; 20 int fmt; 21 }; 22 23 static FolderViewColumns g_ColumnDefs[] = 24 { 25 { IDS_COL_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 25, LVCFMT_LEFT }, 26 { IDS_COL_FILENAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 20, LVCFMT_LEFT }, 27 { IDS_COL_SIZE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_RIGHT }, 28 { IDS_COL_MODIFIED, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, 15, LVCFMT_LEFT }, 29 { IDS_COL_ATTR, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 12, LVCFMT_RIGHT }, 30 }; 31 32 33 34 // Should fix our headers.. 35 EXTERN_C HRESULT WINAPI SHCreateFileExtractIconW(LPCWSTR pszPath, DWORD dwFileAttributes, REFIID riid, void **ppv); 36 37 38 // Helper functions to translate a guid to a readable name 39 bool GetInterfaceName(const WCHAR* InterfaceString, WCHAR* buf, size_t size) 40 { 41 WCHAR LocalBuf[100]; 42 DWORD dwType = 0, dwDataSize = size * sizeof(WCHAR); 43 44 if (!SUCCEEDED(StringCchPrintfW(LocalBuf, _countof(LocalBuf), L"Interface\\%s", InterfaceString))) 45 return false; 46 47 return RegGetValueW(HKEY_CLASSES_ROOT, LocalBuf, NULL, RRF_RT_REG_SZ, &dwType, buf, &dwDataSize) == ERROR_SUCCESS; 48 } 49 50 WCHAR* g2s(REFCLSID iid) 51 { 52 static WCHAR buf[2][300]; 53 static int idx = 0; 54 55 idx ^= 1; 56 57 LPOLESTR tmp; 58 HRESULT hr = ProgIDFromCLSID(iid, &tmp); 59 if (SUCCEEDED(hr)) 60 { 61 wcscpy(buf[idx], tmp); 62 CoTaskMemFree(tmp); 63 return buf[idx]; 64 } 65 StringFromGUID2(iid, buf[idx], _countof(buf[idx])); 66 if (GetInterfaceName(buf[idx], buf[idx], _countof(buf[idx]))) 67 { 68 return buf[idx]; 69 } 70 StringFromGUID2(iid, buf[idx], _countof(buf[idx])); 71 72 return buf[idx]; 73 } 74 75 76 CFontExt::CFontExt() 77 { 78 InterlockedIncrement(&g_ModuleRefCnt); 79 } 80 81 CFontExt::~CFontExt() 82 { 83 InterlockedDecrement(&g_ModuleRefCnt); 84 } 85 86 // *** IShellFolder2 methods *** 87 STDMETHODIMP CFontExt::GetDefaultSearchGUID(GUID *lpguid) 88 { 89 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 90 return E_NOTIMPL; 91 } 92 93 STDMETHODIMP CFontExt::EnumSearches(IEnumExtraSearch **ppenum) 94 { 95 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 96 return E_NOTIMPL; 97 } 98 99 STDMETHODIMP CFontExt::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay) 100 { 101 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 102 return E_NOTIMPL; 103 } 104 105 STDMETHODIMP CFontExt::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags) 106 { 107 if (!pcsFlags || iColumn >= _countof(g_ColumnDefs)) 108 return E_INVALIDARG; 109 110 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState; 111 return S_OK; 112 } 113 114 STDMETHODIMP CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) 115 { 116 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 117 return E_NOTIMPL; 118 } 119 120 STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd) 121 { 122 if (iColumn >= _countof(g_ColumnDefs)) 123 return E_FAIL; 124 125 psd->cxChar = g_ColumnDefs[iColumn].cxChar; 126 psd->fmt = g_ColumnDefs[iColumn].fmt; 127 128 // No item requested, so return the column name 129 if (pidl == NULL) 130 { 131 return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource); 132 } 133 134 // Validate that this pidl is the last one 135 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl); 136 if (curpidl->mkid.cb != 0) 137 { 138 ERR("ERROR, unhandled PIDL!\n"); 139 return E_FAIL; 140 } 141 142 // Name, ReactOS specific? 143 if (iColumn == 0) 144 return GetDisplayNameOf(pidl, 0, &psd->str); 145 146 const FontPidlEntry* fontEntry = _FontFromIL(pidl); 147 if (!fontEntry) 148 { 149 ERR("ERROR, not a font PIDL!\n"); 150 return E_FAIL; 151 } 152 153 // If we got here, we are in details view! 154 // Let's see if we got info about this file that we can re-use 155 if (m_LastDetailsFontName != fontEntry->Name) 156 { 157 CStringW File = g_FontCache->Filename(fontEntry, true); 158 HANDLE hFile = FindFirstFileW(File, &m_LastDetailsFileData); 159 if (hFile == INVALID_HANDLE_VALUE) 160 { 161 m_LastDetailsFontName.Empty(); 162 ERR("Unable to query info about %S\n", File.GetString()); 163 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 164 } 165 FindClose(hFile); 166 m_LastDetailsFontName = fontEntry->Name; 167 } 168 169 // Most code borrowed from CFSFolder::GetDetailsOf 170 FILETIME lft; 171 SYSTEMTIME time; 172 int ret; 173 LARGE_INTEGER FileSize; 174 CStringA AttrLetters; 175 switch (iColumn) 176 { 177 case 1: // Filename 178 return SHSetStrRet(&psd->str, m_LastDetailsFileData.cFileName); 179 case 2: // Size 180 psd->str.uType = STRRET_CSTR; 181 FileSize.HighPart = m_LastDetailsFileData.nFileSizeHigh; 182 FileSize.LowPart = m_LastDetailsFileData.nFileSizeLow; 183 StrFormatKBSizeA(FileSize.QuadPart, psd->str.cStr, MAX_PATH); 184 return S_OK; 185 case 3: // Modified 186 FileTimeToLocalFileTime(&m_LastDetailsFileData.ftLastWriteTime, &lft); 187 FileTimeToSystemTime (&lft, &time); 188 psd->str.uType = STRRET_CSTR; 189 ret = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, psd->str.cStr, MAX_PATH); 190 if (ret < 1) 191 { 192 ERR("GetDateFormatA failed\n"); 193 return E_FAIL; 194 } 195 psd->str.cStr[ret-1] = ' '; 196 GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &psd->str.cStr[ret], MAX_PATH - ret); 197 return S_OK; 198 case 4: // Attributes 199 AttrLetters.LoadString(IDS_COL_ATTR_LETTERS); 200 if (AttrLetters.GetLength() != 5) 201 { 202 ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n"); 203 return E_FAIL; 204 } 205 psd->str.uType = STRRET_CSTR; 206 ret = 0; 207 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) 208 psd->str.cStr[ret++] = AttrLetters[0]; 209 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) 210 psd->str.cStr[ret++] = AttrLetters[1]; 211 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) 212 psd->str.cStr[ret++] = AttrLetters[2]; 213 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) 214 psd->str.cStr[ret++] = AttrLetters[3]; 215 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) 216 psd->str.cStr[ret++] = AttrLetters[4]; 217 psd->str.cStr[ret] = '\0'; 218 return S_OK; 219 default: 220 break; 221 } 222 223 UNIMPLEMENTED; 224 return E_NOTIMPL; 225 } 226 227 STDMETHODIMP CFontExt::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) 228 { 229 //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 230 return E_NOTIMPL; 231 } 232 233 // *** IShellFolder2 methods *** 234 STDMETHODIMP CFontExt::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, DWORD *pchEaten, PIDLIST_RELATIVE *ppidl, DWORD *pdwAttributes) 235 { 236 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 237 return E_NOTIMPL; 238 } 239 240 STDMETHODIMP CFontExt::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) 241 { 242 return _CEnumFonts_CreateInstance(this, dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList)); 243 } 244 245 STDMETHODIMP CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 246 { 247 ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid)); 248 return E_NOTIMPL; 249 } 250 251 STDMETHODIMP CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 252 { 253 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 254 return E_NOTIMPL; 255 } 256 257 STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) 258 { 259 const FontPidlEntry* fontEntry1 = _FontFromIL(pidl1); 260 const FontPidlEntry* fontEntry2 = _FontFromIL(pidl2); 261 262 if (!fontEntry1 || !fontEntry2) 263 return E_INVALIDARG; 264 265 int result = (int)fontEntry1->Index - (int)fontEntry2->Index; 266 267 return MAKE_COMPARE_HRESULT(result); 268 } 269 270 STDMETHODIMP CFontExt::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut) 271 { 272 HRESULT hr = E_NOINTERFACE; 273 274 *ppvOut = NULL; 275 276 if (IsEqualIID(riid, IID_IDropTarget)) 277 { 278 ERR("IDropTarget not implemented\n"); 279 *ppvOut = static_cast<IDropTarget *>(this); 280 AddRef(); 281 hr = S_OK; 282 } 283 else if (IsEqualIID(riid, IID_IContextMenu)) 284 { 285 ERR("IContextMenu not implemented\n"); 286 hr = E_NOTIMPL; 287 } 288 else if (IsEqualIID(riid, IID_IShellView)) 289 { 290 // Just create a default shell folder view, and register ourself as folder 291 SFV_CREATE sfv = { sizeof(SFV_CREATE) }; 292 sfv.pshf = this; 293 hr = SHCreateShellFolderView(&sfv, (IShellView**)ppvOut); 294 } 295 296 return hr; 297 } 298 299 STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut) 300 { 301 if (!rgfInOut || !cidl || !apidl) 302 return E_INVALIDARG; 303 304 DWORD rgf = 0; 305 while (cidl > 0 && *apidl) 306 { 307 const FontPidlEntry* fontEntry = _FontFromIL(*apidl); 308 if (fontEntry) 309 { 310 // We don't support delete yet 311 rgf |= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_FILESYSTEM); 312 } 313 else 314 { 315 rgf = 0; 316 break; 317 } 318 319 apidl++; 320 cidl--; 321 } 322 323 *rgfInOut = rgf; 324 return S_OK; 325 } 326 327 328 STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut) 329 { 330 if (riid == IID_IContextMenu || 331 riid == IID_IContextMenu2 || 332 riid == IID_IContextMenu3) 333 { 334 return _CFontMenu_CreateInstance(hwndOwner, cidl, apidl, this, riid, ppvOut); 335 } 336 else if (riid == IID_IExtractIconA || riid == IID_IExtractIconW) 337 { 338 if (cidl == 1) 339 { 340 const FontPidlEntry* fontEntry = _FontFromIL(*apidl); 341 if (fontEntry) 342 { 343 DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL; 344 CStringW File = g_FontCache->Filename(fontEntry); 345 // Just create a default icon extractor based on the filename 346 // We might want to create a preview with the font to get really fancy one day. 347 return SHCreateFileExtractIconW(File, dwAttributes, riid, ppvOut); 348 } 349 } 350 else 351 { 352 ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n"); 353 } 354 } 355 else if (riid == IID_IDataObject) 356 { 357 if (cidl >= 1) 358 { 359 return _CDataObject_CreateInstance(m_Folder, cidl, apidl, riid, ppvOut); 360 } 361 else 362 { 363 ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n"); 364 } 365 } 366 367 //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid)); 368 return E_NOTIMPL; 369 } 370 371 STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet) 372 { 373 if (!pidl) 374 return E_NOTIMPL; 375 376 // Validate that this pidl is the last one 377 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl); 378 if (curpidl->mkid.cb != 0) 379 { 380 ERR("ERROR, unhandled PIDL!\n"); 381 return E_FAIL; 382 } 383 384 const FontPidlEntry* fontEntry = _FontFromIL(pidl); 385 if (!fontEntry) 386 return E_FAIL; 387 388 return SHSetStrRet(strRet, fontEntry->Name); 389 } 390 391 STDMETHODIMP CFontExt::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut) 392 { 393 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); 394 return E_NOTIMPL; 395 } 396 397 // *** IPersistFolder2 methods *** 398 STDMETHODIMP CFontExt::GetCurFolder(LPITEMIDLIST *ppidl) 399 { 400 if (ppidl && m_Folder) 401 { 402 *ppidl = ILClone(m_Folder); 403 return S_OK; 404 } 405 406 return E_POINTER; 407 } 408 409 410 // *** IPersistFolder methods *** 411 STDMETHODIMP CFontExt::Initialize(LPCITEMIDLIST pidl) 412 { 413 WCHAR PidlPath[MAX_PATH + 1] = {0}, FontsDir[MAX_PATH + 1]; 414 if (!SHGetPathFromIDListW(pidl, PidlPath)) 415 { 416 ERR("Unable to extract path from pidl\n"); 417 return E_FAIL; 418 } 419 420 HRESULT hr = SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, FontsDir); 421 if (FAILED_UNEXPECTEDLY(hr)) 422 { 423 ERR("Unable to get fonts path (0x%x)\n", hr); 424 return hr; 425 } 426 427 if (_wcsicmp(PidlPath, FontsDir)) 428 { 429 ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath); 430 return E_FAIL; 431 } 432 433 m_Folder.Attach(ILClone(pidl)); 434 StringCchCatW(FontsDir, _countof(FontsDir), L"\\"); 435 g_FontCache->SetFontDir(FontsDir); 436 437 return S_OK; 438 } 439 440 441 // *** IPersist methods *** 442 STDMETHODIMP CFontExt::GetClassID(CLSID *lpClassId) 443 { 444 *lpClassId = CLSID_CFontExt; 445 return S_OK; 446 } 447 448 // *** IDropTarget methods *** 449 STDMETHODIMP CFontExt::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) 450 { 451 *pdwEffect = DROPEFFECT_NONE; 452 453 CComHeapPtr<CIDA> cida; 454 HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida); 455 if (FAILED_UNEXPECTEDLY(hr)) 456 return hr; 457 458 *pdwEffect = DROPEFFECT_COPY; 459 return S_OK; 460 } 461 462 STDMETHODIMP CFontExt::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) 463 { 464 return S_OK; 465 } 466 467 STDMETHODIMP CFontExt::DragLeave() 468 { 469 return S_OK; 470 } 471 472 STDMETHODIMP CFontExt::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) 473 { 474 *pdwEffect = DROPEFFECT_NONE; 475 476 CComHeapPtr<CIDA> cida; 477 HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida); 478 if (FAILED_UNEXPECTEDLY(hr)) 479 return hr; 480 481 PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(cida); 482 if (!pidlParent) 483 { 484 ERR("pidlParent is NULL\n"); 485 return E_FAIL; 486 } 487 488 BOOL bOK = TRUE; 489 CAtlArray<CStringW> FontPaths; 490 for (UINT n = 0; n < cida->cidl; ++n) 491 { 492 PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(cida, n); 493 if (!pidlRelative) 494 continue; 495 496 PIDLIST_ABSOLUTE pidl = ILCombine(pidlParent, pidlRelative); 497 if (!pidl) 498 { 499 ERR("ILCombine failed\n"); 500 bOK = FALSE; 501 break; 502 } 503 504 WCHAR szPath[MAX_PATH]; 505 BOOL ret = SHGetPathFromIDListW(pidl, szPath); 506 ILFree(pidl); 507 508 if (!ret) 509 { 510 ERR("SHGetPathFromIDListW failed\n"); 511 bOK = FALSE; 512 break; 513 } 514 515 if (PathIsDirectoryW(szPath)) 516 { 517 ERR("PathIsDirectory\n"); 518 bOK = FALSE; 519 break; 520 } 521 522 LPCWSTR pchDotExt = PathFindExtensionW(szPath); 523 if (!IsFontDotExt(pchDotExt)) 524 { 525 ERR("'%S' is not supported\n", pchDotExt); 526 bOK = FALSE; 527 break; 528 } 529 530 FontPaths.Add(szPath); 531 } 532 533 if (!bOK) 534 return E_FAIL; 535 536 CRegKey keyFonts; 537 if (keyFonts.Open(FONT_HIVE, FONT_KEY, KEY_WRITE) != ERROR_SUCCESS) 538 { 539 ERR("keyFonts.Open failed\n"); 540 return E_FAIL; 541 } 542 543 for (size_t iItem = 0; iItem < FontPaths.GetCount(); ++iItem) 544 { 545 HRESULT hr = DoInstallFontFile(FontPaths[iItem], g_FontCache->FontPath(), keyFonts.m_hKey); 546 if (FAILED_UNEXPECTEDLY(hr)) 547 { 548 bOK = FALSE; 549 break; 550 } 551 } 552 553 // TODO: update g_FontCache 554 555 SendMessageW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 556 557 // TODO: Show message 558 559 return bOK ? S_OK : E_FAIL; 560 } 561 562 HRESULT CFontExt::DoInstallFontFile(LPCWSTR pszFontPath, LPCWSTR pszFontsDir, HKEY hkeyFonts) 563 { 564 WCHAR szDestFile[MAX_PATH]; 565 LPCWSTR pszFileTitle = PathFindFileName(pszFontPath); 566 567 CStringW strFontName; 568 if (!DoGetFontTitle(pszFontPath, strFontName)) 569 return E_FAIL; 570 571 RemoveFontResourceW(pszFileTitle); 572 573 StringCchCopyW(szDestFile, sizeof(szDestFile), pszFontsDir); 574 PathAppendW(szDestFile, pszFileTitle); 575 if (!CopyFileW(pszFontPath, szDestFile, FALSE)) 576 { 577 ERR("CopyFileW('%S', '%S') failed\n", pszFontPath, szDestFile); 578 return E_FAIL; 579 } 580 581 if (!AddFontResourceW(szDestFile)) 582 { 583 ERR("AddFontResourceW('%S') failed\n", pszFileTitle); 584 DeleteFileW(szDestFile); 585 return E_FAIL; 586 } 587 588 DWORD cbData = (wcslen(pszFileTitle) + 1) * sizeof(WCHAR); 589 LONG nError = RegSetValueExW(hkeyFonts, strFontName, 0, REG_SZ, 590 (const BYTE *)pszFileTitle, cbData); 591 if (nError) 592 { 593 ERR("RegSetValueExW failed with %ld\n", nError); 594 RemoveFontResourceW(pszFileTitle); 595 DeleteFileW(szDestFile); 596 return E_FAIL; 597 } 598 599 return S_OK; 600 } 601 602 HRESULT 603 CFontExt::DoGetFontTitle(IN LPCWSTR pszFontPath, OUT CStringW& strFontName) 604 { 605 DWORD cbInfo = 0; 606 BOOL ret = GetFontResourceInfoW(pszFontPath, &cbInfo, NULL, 1); 607 if (!ret || !cbInfo) 608 { 609 ERR("GetFontResourceInfoW failed\n"); 610 return E_FAIL; 611 } 612 613 LPWSTR pszBuffer = strFontName.GetBuffer(cbInfo / sizeof(WCHAR)); 614 ret = GetFontResourceInfoW(pszFontPath, &cbInfo, pszBuffer, 1); 615 strFontName.ReleaseBuffer(); 616 if (ret) 617 { 618 TRACE("pszFontName: %S\n", (LPCWSTR)strFontName); 619 return S_OK; 620 } 621 622 ERR("GetFontResourceInfoW failed\n"); 623 return E_FAIL; 624 } 625