1 // This file is part of VSTGUI. It is subject to the license terms
2 // in the LICENSE file found in the top-level directory of this
3 // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
4
5 #include "d2dfont.h"
6
7 #if WINDOWS
8
9 #include "../win32support.h"
10 #include "../winstring.h"
11 #include "../comptr.h"
12 #include "d2ddrawcontext.h"
13 #include <dwrite.h>
14 #include <d2d1.h>
15
16 #ifndef NTDDI_WIN10_RS2
17 #define NTDDI_WIN10_RS2 0x0A000003 /* ABRACADABRA_WIN10_RS2 */
18 #endif
19 #if !defined(VSTGUI_WIN32_CUSTOMFONT_SUPPORT) && WDK_NTDDI_VERSION >= NTDDI_WIN10_RS2
20 #include <dwrite_3.h>
21 #define VSTGUI_WIN32_CUSTOMFONT_SUPPORT 1
22 #else
23 #define VSTGUI_WIN32_CUSTOMFONT_SUPPORT 0
24 #pragma message( \
25 "Warning: VSTGUI Custom Font support is only available when building with the Windows 10 Creator Update SDK or newer")
26 #endif
27
28 namespace VSTGUI {
29
30 //-----------------------------------------------------------------------------
31 struct CustomFonts
32 {
33 #if VSTGUI_WIN32_CUSTOMFONT_SUPPORT
getFontCollectionVSTGUI::CustomFonts34 static IDWriteFontCollection1* getFontCollection ()
35 {
36 return instance ().fontCollection.get ();
37 }
containsVSTGUI::CustomFonts38 static bool contains (const WCHAR* name, DWRITE_FONT_WEIGHT fontWeight,
39 DWRITE_FONT_STRETCH fontStretch, DWRITE_FONT_STYLE fontStyle)
40 {
41 if (auto fontSet = instance ().fontSet.get ())
42 {
43 COM::Ptr<IDWriteFontSet> matchingFonts;
44 if (SUCCEEDED (fontSet->GetMatchingFonts (name, fontWeight, fontStretch, fontStyle,
45 matchingFonts.adoptPtr ())))
46 return matchingFonts->GetFontCount () > 0;
47 }
48 return false;
49 }
50
51 private:
52 COM::Ptr<IDWriteFontSet> fontSet;
53 COM::Ptr<IDWriteFontCollection1> fontCollection;
54
instanceVSTGUI::CustomFonts55 static CustomFonts& instance ()
56 {
57 static CustomFonts gInstance;
58 return gInstance;
59 }
CustomFontsVSTGUI::CustomFonts60 CustomFonts ()
61 {
62 auto basePath = WinResourceInputStream::getBasePath ();
63 if (!basePath)
64 return;
65 *basePath += "Fonts\\*";
66 auto files = getDirectoryContents (*basePath);
67 if (files.empty ())
68 return;
69 auto factory = getDWriteFactory ();
70 COM::Ptr<IDWriteFactory5> factory5;
71 if (!factory || FAILED (factory->QueryInterface<IDWriteFactory5> (factory5.adoptPtr ())))
72 return;
73 COM::Ptr<IDWriteFontSetBuilder1> fontSetBuilder;
74 if (FAILED (factory5->CreateFontSetBuilder (fontSetBuilder.adoptPtr ())))
75 return;
76 for (const auto& file : files)
77 {
78 COM::Ptr<IDWriteFontFile> fontFile;
79 if (FAILED (factory5->CreateFontFileReference (file.data (), nullptr,
80 fontFile.adoptPtr ())))
81 continue;
82 fontSetBuilder->AddFontFile (fontFile.get ());
83 }
84 if (FAILED (fontSetBuilder->CreateFontSet (fontSet.adoptPtr ())))
85 return;
86 factory5->CreateFontCollectionFromFontSet (fontSet.get (), fontCollection.adoptPtr ());
87 }
88
getDirectoryContentsVSTGUI::CustomFonts89 std::vector<std::wstring> getDirectoryContents (const UTF8String& path) const
90 {
91 std::vector<std::wstring> result;
92 UTF8StringHelper fontsDir (path);
93 std::wstring basePath (fontsDir.getWideString ());
94 WIN32_FIND_DATA findData {};
95 auto find = FindFirstFile (basePath.data (), &findData);
96 if (find == INVALID_HANDLE_VALUE)
97 return result;
98 basePath.erase (basePath.size () - 1);
99 do
100 {
101 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
102 {
103 result.emplace_back (basePath + findData.cFileName);
104 }
105 } while (FindNextFile (find, &findData) != 0);
106 FindClose (find);
107 return result;
108 }
109 #else
110 static IDWriteFontCollection* getFontCollection () { return nullptr; }
111 static bool contains (const WCHAR*, DWRITE_FONT_WEIGHT, DWRITE_FONT_STRETCH, DWRITE_FONT_STYLE)
112 {
113 return false;
114 }
115 #endif
116 };
117
118 //-----------------------------------------------------------------------------
gatherFonts(std::list<std::string> & fontFamilyNames,IDWriteFontCollection * collection)119 static void gatherFonts (std::list<std::string>& fontFamilyNames, IDWriteFontCollection* collection)
120 {
121 UINT32 numFonts = collection->GetFontFamilyCount ();
122 for (UINT32 i = 0; i < numFonts; ++i)
123 {
124 IDWriteFontFamily* fontFamily = nullptr;
125 if (!SUCCEEDED (collection->GetFontFamily (i, &fontFamily)))
126 continue;
127 IDWriteLocalizedStrings* names = nullptr;
128 if (!SUCCEEDED (fontFamily->GetFamilyNames (&names)))
129 continue;
130 UINT32 nameLength = 0;
131 if (!SUCCEEDED (names->GetStringLength (0, &nameLength)) || nameLength < 1)
132 continue;
133 nameLength++;
134 WCHAR* name = new WCHAR[nameLength];
135 if (SUCCEEDED (names->GetString (0, name, nameLength)))
136 {
137 UTF8StringHelper str (name);
138 fontFamilyNames.emplace_back (str.getUTF8String ());
139 }
140 delete [] name;
141 }
142 }
143
144 //-----------------------------------------------------------------------------
getAllPlatformFontFamilies(std::list<std::string> & fontFamilyNames)145 bool D2DFont::getAllPlatformFontFamilies (std::list<std::string>& fontFamilyNames)
146 {
147 IDWriteFontCollection* collection = nullptr;
148 if (SUCCEEDED (getDWriteFactory ()->GetSystemFontCollection (&collection, true)))
149 gatherFonts (fontFamilyNames, collection);
150 if (auto customFontCollection = CustomFonts::getFontCollection ())
151 gatherFonts (fontFamilyNames, customFontCollection);
152 return true;
153 }
154
155 //-----------------------------------------------------------------------------
getFont(IDWriteTextFormat * format,int32_t style)156 static COM::Ptr<IDWriteFont> getFont (IDWriteTextFormat* format, int32_t style)
157 {
158 DWRITE_FONT_STYLE fontStyle = (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
159 DWRITE_FONT_WEIGHT fontWeight = (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;
160 IDWriteFontCollection* fontCollection = nullptr;
161 format->GetFontCollection (&fontCollection);
162 if (!fontCollection)
163 return {};
164 auto nameLength = format->GetFontFamilyNameLength () + 1;
165 auto familyName = std::unique_ptr<WCHAR[]> (new WCHAR [nameLength]);
166 if (FAILED (format->GetFontFamilyName (familyName.get (), nameLength)))
167 return {};
168 UINT32 index = 0;
169 BOOL exists = FALSE;
170 if (FAILED (fontCollection->FindFamilyName (familyName.get (), &index, &exists)))
171 return {};
172 COM::Ptr<IDWriteFontFamily> fontFamily;
173 fontCollection->GetFontFamily (index, fontFamily.adoptPtr ());
174 if (fontFamily)
175 {
176 COM::Ptr<IDWriteFont> font;
177 fontFamily->GetFirstMatchingFont (fontWeight, DWRITE_FONT_STRETCH_NORMAL, fontStyle, font.adoptPtr ());
178 return font;
179 }
180 return {};
181 }
182
183 //-----------------------------------------------------------------------------
D2DFont(const UTF8String & name,const CCoord & size,const int32_t & style)184 D2DFont::D2DFont (const UTF8String& name, const CCoord& size, const int32_t& style)
185 : textFormat (nullptr)
186 , ascent (-1)
187 , descent (-1)
188 , leading (-1)
189 , capHeight (-1)
190 , style (style)
191 {
192 DWRITE_FONT_STYLE fontStyle = (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
193 DWRITE_FONT_WEIGHT fontWeight = (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL;
194 UTF8StringHelper nameStr (name.data ());
195
196 IDWriteFontCollection* fontCollection = nullptr;
197 if (CustomFonts::contains (nameStr.getWideString (), fontWeight, DWRITE_FONT_STRETCH_NORMAL,
198 fontStyle))
199 fontCollection = CustomFonts::getFontCollection ();
200
201 getDWriteFactory ()->CreateTextFormat (nameStr, fontCollection, fontWeight, fontStyle,
202 DWRITE_FONT_STRETCH_NORMAL, (FLOAT)size, L"en-us",
203 &textFormat);
204 if (textFormat)
205 {
206 if (auto font = getFont (textFormat, style))
207 {
208 DWRITE_FONT_METRICS fontMetrics;
209 font->GetMetrics (&fontMetrics);
210 ascent = fontMetrics.ascent * (size / fontMetrics.designUnitsPerEm);
211 descent = fontMetrics.descent * (size / fontMetrics.designUnitsPerEm);
212 leading = fontMetrics.lineGap * (size / fontMetrics.designUnitsPerEm);
213 capHeight = fontMetrics.capHeight * (size / fontMetrics.designUnitsPerEm);
214 }
215 }
216 }
217
218 //-----------------------------------------------------------------------------
~D2DFont()219 D2DFont::~D2DFont ()
220 {
221 if (textFormat)
222 textFormat->Release ();
223 }
224
225 //-----------------------------------------------------------------------------
asLogFont(LOGFONTW & logfont) const226 bool D2DFont::asLogFont (LOGFONTW& logfont) const
227 {
228 if (!textFormat)
229 return false;
230 COM::Ptr<IDWriteGdiInterop> interOp;
231 if (FAILED (getDWriteFactory ()->GetGdiInterop (interOp.adoptPtr ())))
232 return false;
233 if (auto font = getFont (textFormat, style))
234 {
235 BOOL isSystemFont;
236 if (SUCCEEDED (interOp->ConvertFontToLOGFONT (font.get (), &logfont, &isSystemFont)))
237 {
238 logfont.lfHeight = -static_cast<LONG> (std::round (textFormat->GetFontSize ()));
239 return true;
240 }
241 }
242 return false;
243 }
244
245 //-----------------------------------------------------------------------------
createTextLayout(IPlatformString * string) const246 IDWriteTextLayout* D2DFont::createTextLayout (IPlatformString* string) const
247 {
248 const WinString* winString = dynamic_cast<const WinString*> (string);
249 IDWriteTextLayout* textLayout = nullptr;
250 if (winString)
251 getDWriteFactory ()->CreateTextLayout (winString->getWideString (), (UINT32)wcslen (winString->getWideString ()), textFormat, 10000, 1000, &textLayout);
252 return textLayout;
253 }
254
255 //-----------------------------------------------------------------------------
drawString(CDrawContext * context,IPlatformString * string,const CPoint & p,bool antialias) const256 void D2DFont::drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, bool antialias) const
257 {
258 D2DDrawContext* d2dContext = dynamic_cast<D2DDrawContext*> (context);
259 if (d2dContext && textFormat)
260 {
261 D2DDrawContext::D2DApplyClip ac (d2dContext);
262 if (ac.isEmpty ())
263 return;
264 ID2D1RenderTarget* renderTarget = d2dContext->getRenderTarget ();
265 if (renderTarget)
266 {
267 IDWriteTextLayout* textLayout = createTextLayout (string);
268 if (textLayout)
269 {
270 if (style & kUnderlineFace)
271 {
272 DWRITE_TEXT_RANGE range = { 0, UINT_MAX };
273 textLayout->SetUnderline (true, range);
274 }
275 if (style & kStrikethroughFace)
276 {
277 DWRITE_TEXT_RANGE range = { 0, UINT_MAX };
278 textLayout->SetStrikethrough (true, range);
279 }
280 renderTarget->SetTextAntialiasMode (antialias ? D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
281 CPoint pos (p);
282 pos.y -= textFormat->GetFontSize ();
283 if (context->getDrawMode ().integralMode ())
284 pos.makeIntegral ();
285 pos.y += 0.5;
286
287 D2D1_POINT_2F origin = {(FLOAT)(p.x), (FLOAT)(pos.y)};
288 d2dContext->getRenderTarget ()->DrawTextLayout (origin, textLayout, d2dContext->getFontBrush ());
289 textLayout->Release ();
290 }
291 }
292 }
293 }
294
295 //-----------------------------------------------------------------------------
getStringWidth(CDrawContext * context,IPlatformString * string,bool antialias) const296 CCoord D2DFont::getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias) const
297 {
298 CCoord result = 0;
299 if (textFormat)
300 {
301 IDWriteTextLayout* textLayout = createTextLayout (string);
302 if (textLayout)
303 {
304 DWRITE_TEXT_METRICS textMetrics;
305 if (SUCCEEDED (textLayout->GetMetrics (&textMetrics)))
306 result = (CCoord)textMetrics.widthIncludingTrailingWhitespace;
307 textLayout->Release ();
308 }
309 }
310 return result;
311 }
312
313 } // VSTGUI
314
315 #endif // WINDOWS
316