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