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