1 /*****************************************************************************
2  * dwrite.cpp : DirectWrite font functions
3  *****************************************************************************
4  * Copyright (C) 2016 VLC authors and VideoLAN
5  * $Id: 7b9143432a5dfe2cfe77b3e2d213d6cdb2ca1e15 $
6  *
7  * Authors: Salah-Eddin Shaban <salah@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation, Inc.,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <vlc_common.h>
29 #include <vlc_filter.h>
30 #include <vlc_charset.h>
31 
32 #include <dwrite_2.h>
33 #include <wrl/client.h>
34 #include <vector>
35 #include <stdexcept>
36 #include <regex>
37 
38 #include "../platform_fonts.h"
39 
40 using Microsoft::WRL::ComPtr;
41 using namespace std;
42 
43 typedef HRESULT ( WINAPI *DWriteCreateFactoryProc ) (
44         _In_ DWRITE_FACTORY_TYPE factory_type,
45         _In_ REFIID              iid,
46         _Out_ IUnknown         **pp_factory );
47 
48 struct dw_sys_t
49 {
50     HMODULE                                  p_dw_dll;
51     ComPtr< IDWriteFactory2 >                p_dw_factory;
52     ComPtr< IDWriteFontCollection >          p_dw_system_fonts;
53     ComPtr< IDWriteNumberSubstitution >      p_dw_substitution;
54     ComPtr< IDWriteFontFallback >            p_dw_fallbacks;
55     vector< FT_Stream >                      streams;
56 
dw_sys_tdw_sys_t57     dw_sys_t( HMODULE p_dw_dll ) : p_dw_dll( p_dw_dll )
58     {
59         /* This will fail on versions of Windows prior to 8.1 */
60 #if VLC_WINSTORE_APP
61         if( DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof( IDWriteFactory2 ),
62                 reinterpret_cast<IUnknown **>( p_dw_factory.GetAddressOf() ) ) )
63             throw runtime_error( "failed to create DWrite factory" );
64 #else
65         DWriteCreateFactoryProc pf =
66                 ( DWriteCreateFactoryProc ) GetProcAddress( p_dw_dll, "DWriteCreateFactory" );
67 
68         if( pf == NULL )
69             throw runtime_error( "GetProcAddress() failed" );
70 
71         if( pf( DWRITE_FACTORY_TYPE_SHARED, __uuidof( IDWriteFactory2 ),
72                 reinterpret_cast<IUnknown **>( p_dw_factory.GetAddressOf() ) ) )
73             throw runtime_error( "failed to create DWrite factory" );
74 #endif
75 
76         if( p_dw_factory->GetSystemFontCollection( p_dw_system_fonts.GetAddressOf() ) )
77             throw runtime_error( "GetSystemFontCollection() failed" );
78 
79         if( p_dw_factory->GetSystemFontFallback( p_dw_fallbacks.GetAddressOf() ) )
80             throw runtime_error( "GetSystemFontFallback() failed" );
81 
82         if( p_dw_factory->CreateNumberSubstitution( DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL,
83                 L"en-US", true, p_dw_substitution.GetAddressOf() ) )
84             throw runtime_error( "CreateNumberSubstitution() failed" );
85     }
86 
~dw_sys_tdw_sys_t87     ~dw_sys_t()
88     {
89         for( unsigned int i = 0; i < streams.size(); ++i )
90         {
91             FT_Stream p_stream = streams.at( i );
92             IDWriteFontFileStream *p_dw_stream = ( IDWriteFontFileStream * ) p_stream->descriptor.pointer;
93             p_dw_stream->Release();
94             free( p_stream );
95         }
96     }
97 };
98 
AppendFamily(vlc_family_t ** pp_list,vlc_family_t * p_family)99 static inline void AppendFamily( vlc_family_t **pp_list, vlc_family_t *p_family )
100 {
101     while( *pp_list )
102         pp_list = &( *pp_list )->p_next;
103 
104     *pp_list = p_family;
105 }
106 
InitDWrite(filter_t * p_filter)107 extern "C" int InitDWrite( filter_t *p_filter )
108 {
109     dw_sys_t *p_dw_sys;
110     HMODULE p_dw_dll = NULL;
111 
112     try
113     {
114 #if VLC_WINSTORE_APP
115         p_dw_sys = new dw_sys_t( p_dw_dll );
116 #else
117         p_dw_dll = LoadLibrary( TEXT( "Dwrite.dll" ) );
118         if( p_dw_dll == NULL )
119             return VLC_EGENERIC;
120 
121         p_dw_sys = new dw_sys_t( p_dw_dll );
122 #endif
123     }
124     catch( const exception &e )
125     {
126 #if !VLC_WINSTORE_APP
127         FreeLibrary( p_dw_dll );
128         (void)e;
129 #else
130         msg_Err( p_filter, "InitDWrite(): %s", e.what() );
131 #endif
132 
133         return VLC_EGENERIC;
134     }
135 
136     p_filter->p_sys->p_dw_sys = p_dw_sys;
137     msg_Dbg( p_filter, "Using DWrite backend" );
138     return VLC_SUCCESS;
139 }
140 
ReleaseDWrite(filter_t * p_filter)141 extern "C" int ReleaseDWrite( filter_t *p_filter )
142 {
143     filter_sys_t *p_sys = p_filter->p_sys;
144     dw_sys_t *p_dw_sys = ( dw_sys_t * ) p_sys->p_dw_sys;
145 
146 #if VLC_WINSTORE_APP
147     delete p_dw_sys;
148 #else
149     HMODULE p_dw_dll = NULL;
150     if( p_dw_sys && p_dw_sys->p_dw_dll )
151         p_dw_dll = p_dw_sys->p_dw_dll;
152 
153     delete p_dw_sys;
154     if( p_dw_dll ) FreeLibrary( p_dw_dll );
155 #endif
156 
157     return VLC_SUCCESS;
158 }
159 
DWrite_GetFontStream(filter_t * p_filter,int i_index,FT_Stream * pp_stream)160 extern "C" int DWrite_GetFontStream( filter_t *p_filter, int i_index, FT_Stream *pp_stream )
161 {
162     dw_sys_t *p_dw_sys = ( dw_sys_t * ) p_filter->p_sys->p_dw_sys;
163 
164     if( i_index < 0 || i_index >= ( int ) p_dw_sys->streams.size() )
165         return VLC_ENOITEM;
166 
167     *pp_stream = p_dw_sys->streams.at( i_index );
168 
169     return VLC_SUCCESS;
170 }
171 
ToUTF16(uint32_t i_codepoint,wchar_t * p_out,uint32_t * i_length)172 static inline void ToUTF16( uint32_t i_codepoint, wchar_t *p_out, uint32_t *i_length )
173 {
174     if( i_codepoint < 0x10000 )
175     {
176         p_out[ 0 ] = i_codepoint;
177         *i_length = 1;
178     }
179     else
180     {
181         i_codepoint -= 0x10000;
182         p_out[ 0 ] = ( i_codepoint >> 10 ) + 0xd800;
183         p_out[ 1 ] = ( i_codepoint & 0x3ff ) + 0xdc00;
184         *i_length = 2;
185     }
186 }
187 
DWrite_Read(FT_Stream p_stream,unsigned long l_offset,unsigned char * p_buffer,unsigned long l_count)188 extern "C" unsigned long DWrite_Read( FT_Stream p_stream, unsigned long l_offset,
189                                       unsigned char *p_buffer, unsigned long l_count )
190 {
191     const void  *p_dw_fragment         = NULL;
192     void        *p_dw_fragment_context = NULL;
193 
194     if( l_count == 0 ) return 0;
195 
196     IDWriteFontFileStream *p_dw_stream = ( IDWriteFontFileStream * ) p_stream->descriptor.pointer;
197 
198     if( p_dw_stream->ReadFileFragment( &p_dw_fragment, l_offset, l_count, &p_dw_fragment_context ) )
199         return 0;
200 
201     memcpy( p_buffer, p_dw_fragment, l_count );
202 
203     p_dw_stream->ReleaseFileFragment( p_dw_fragment_context );
204 
205     return l_count;
206 }
207 
DWrite_Close(FT_Stream)208 extern "C" void DWrite_Close( FT_Stream )
209 {
210 }
211 
212 class TextSource : public IDWriteTextAnalysisSource
213 {
214     IDWriteFactory              *mp_factory;
215     IDWriteNumberSubstitution   *mp_substitution;
216     wchar_t                      mpwsz_text[ 3 ];
217     uint32_t                     mi_text_length;
218     ULONG                        ml_ref_count;
219 
220 public:
TextSource(IDWriteFactory * p_factory,IDWriteNumberSubstitution * p_substitution,const wchar_t * pwsz_text,uint32_t i_text_length)221     TextSource( IDWriteFactory *p_factory, IDWriteNumberSubstitution *p_substitution,
222                 const wchar_t *pwsz_text, uint32_t i_text_length )
223               : mp_factory( p_factory ), mp_substitution( p_substitution ), ml_ref_count( 0 )
224     {
225         memset( mpwsz_text, 0, sizeof( mpwsz_text ) );
226         mi_text_length = i_text_length < 2 ? i_text_length : 2;
227         memcpy( mpwsz_text, pwsz_text, mi_text_length * sizeof( *mpwsz_text ) );
228     }
~TextSource()229     virtual ~TextSource() {}
230 
GetLocaleName(UINT32,UINT32 *,const WCHAR ** ppwsz_locale_name)231     virtual HRESULT STDMETHODCALLTYPE GetLocaleName( UINT32, UINT32 *,
232                                         const WCHAR **ppwsz_locale_name ) noexcept
233     {
234         *ppwsz_locale_name = L"en-US";
235         return S_OK;
236     }
237 
GetNumberSubstitution(UINT32,UINT32 *,IDWriteNumberSubstitution ** pp_substitution)238     virtual HRESULT STDMETHODCALLTYPE GetNumberSubstitution( UINT32, UINT32 *,
239                                         IDWriteNumberSubstitution **pp_substitution ) noexcept
240     {
241         mp_substitution->AddRef();
242         *pp_substitution = mp_substitution;
243         return S_OK;
244     }
245 
GetParagraphReadingDirection()246     virtual DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept
247     {
248         return DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
249     }
250 
GetTextAtPosition(UINT32 i_text_position,const WCHAR ** ppwsz_text,UINT32 * pi_text_length)251     virtual HRESULT STDMETHODCALLTYPE GetTextAtPosition( UINT32 i_text_position,
252                                                          const WCHAR **ppwsz_text,
253                                                          UINT32 *pi_text_length ) noexcept
254     {
255         if( i_text_position > mi_text_length )
256             return E_INVALIDARG;
257 
258         *ppwsz_text = mpwsz_text + i_text_position;
259         *pi_text_length = mi_text_length - i_text_position;
260 
261         return S_OK;
262     }
263 
GetTextBeforePosition(UINT32 i_text_position,const WCHAR ** ppwsz_text,UINT32 * pi_text_length)264     virtual HRESULT STDMETHODCALLTYPE GetTextBeforePosition( UINT32 i_text_position,
265                                                              const WCHAR **ppwsz_text,
266                                                              UINT32 *pi_text_length ) noexcept
267     {
268         if( i_text_position > mi_text_length )
269             return E_INVALIDARG;
270 
271         if( i_text_position == 0 )
272         {
273             *ppwsz_text = NULL;
274             *pi_text_length = 0;
275             return S_OK;
276         }
277 
278         *ppwsz_text = mpwsz_text;
279         *pi_text_length = i_text_position;
280         return S_OK;
281     }
282 
QueryInterface(REFIID riid,LPVOID * pp_obj)283     virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, LPVOID *pp_obj )
284     {
285         if( !pp_obj )
286             return E_INVALIDARG;
287 
288         *pp_obj = NULL;
289 
290         if( riid == IID_IUnknown || riid == __uuidof( IDWriteTextAnalysisSource ) )
291         {
292             *pp_obj = ( LPVOID ) this;
293             AddRef();
294             return NOERROR;
295         }
296         return E_NOINTERFACE;
297     }
298 
AddRef()299     virtual ULONG STDMETHODCALLTYPE AddRef()
300     {
301         return InterlockedIncrement( &ml_ref_count );
302     }
303 
Release()304     virtual ULONG STDMETHODCALLTYPE Release()
305     {
306         ULONG l_ret = InterlockedDecrement( &ml_ref_count );
307         if( l_ret == 0 )
308         {
309             delete this;
310         }
311 
312         return l_ret;
313     }
314 };
315 
316 /*
317  * Remove any extra null characters and escape any regex metacharacters
318  */
SanitizeName(const wstring & name)319 static wstring SanitizeName( const wstring &name )
320 {
321     const auto pattern = wregex{ L"[.^$|()\\[\\]{}*+?\\\\]" };
322     const auto replace = wstring{ L"\\\\&" };
323     auto result = regex_replace( wstring{ name.c_str() }, pattern, replace,
324                                  regex_constants::match_default | regex_constants::format_sed );
325     return result;
326 }
327 
328 /*
329  * Check for a partial match e.g. between Roboto and Roboto Thin.
330  * In this case p_unmatched will be set to Thin.
331  * Also used for face names, in which case Thin will match Thin,
332  * Thin Italic, Thin Oblique...
333  */
DWrite_PartialMatch(filter_t * p_filter,const wstring & full_name,const wstring & partial_name,wstring * p_unmatched=nullptr)334 static bool DWrite_PartialMatch( filter_t *p_filter, const wstring &full_name,
335                                  const wstring &partial_name, wstring *p_unmatched = nullptr )
336 {
337     auto pattern = wstring{ L"^" } + SanitizeName( partial_name ) + wstring{ L"\\s*(.*)$" };
338     auto rx = wregex{ pattern, wregex::icase };
339 
340     auto match = wsmatch{};
341 
342     if( !regex_match( full_name, match, rx ) )
343         return false;
344 
345     msg_Dbg( p_filter, "DWrite_PartialMatch(): %S matches %S", full_name.c_str(), partial_name.c_str() );
346 
347     if( p_unmatched )
348         *p_unmatched = match[ 1 ].str();
349 
350     return true;
351 }
352 
353 /*
354  * Check for a partial match between a name and any of 3 localized names of a family or face.
355  * The 3 locales tested are en-US and the user and system default locales. b_partial determines
356  * which parameter has the partial string, p_names or name.
357  */
DWrite_PartialMatch(filter_t * p_filter,ComPtr<IDWriteLocalizedStrings> & p_names,const wstring & name,bool b_partial,wstring * p_unmatched=nullptr)358 static bool DWrite_PartialMatch( filter_t *p_filter, ComPtr< IDWriteLocalizedStrings > &p_names,
359                                  const wstring &name, bool b_partial, wstring *p_unmatched = nullptr )
360 {
361     wchar_t buff_sys[ LOCALE_NAME_MAX_LENGTH ] = {};
362     wchar_t buff_usr[ LOCALE_NAME_MAX_LENGTH ] = {};
363 
364 #if _WIN32_WINNT < _WIN32_WINNT_VISTA
365     HMODULE h_dll = GetModuleHandle(_T("kernel32.dll"));
366 
367     typedef int ( WINAPI *GetUserDefaultLocaleName )( LPWSTR lpLocaleName, int cchLocaleName );
368     GetUserDefaultLocaleName OurGetUserDefaultLocaleName =
369         (GetUserDefaultLocaleName) GetProcAddress( h_dll, "GetUserDefaultLocaleName" );
370 
371     typedef int ( WINAPI *GetSystemDefaultLocaleName )( LPWSTR lpLocaleName, int cchLocaleName );
372     GetSystemDefaultLocaleName OurGetSystemDefaultLocaleName =
373         (GetSystemDefaultLocaleName) GetProcAddress( h_dll, "GetSystemDefaultLocaleName" );
374 
375     if( OurGetSystemDefaultLocaleName )
376         OurGetSystemDefaultLocaleName( buff_sys, LOCALE_NAME_MAX_LENGTH );
377     if( OurGetUserDefaultLocaleName )
378         OurGetUserDefaultLocaleName( buff_usr, LOCALE_NAME_MAX_LENGTH );
379 #else
380     GetSystemDefaultLocaleName( buff_sys, LOCALE_NAME_MAX_LENGTH );
381     GetUserDefaultLocaleName( buff_usr, LOCALE_NAME_MAX_LENGTH );
382 #endif
383 
384     const wchar_t *pp_locales[] = { L"en-US", buff_sys, buff_usr };
385 
386     for( int i = 0; i < 3; ++i )
387     {
388         HRESULT  hr;
389         UINT32   i_index;
390         UINT32   i_length;
391         BOOL     b_exists;
392         wstring  locale_name;
393 
394         try
395         {
396             hr = p_names->FindLocaleName( pp_locales[ i ], &i_index, &b_exists );
397 
398             if( SUCCEEDED( hr ) && b_exists )
399             {
400                 hr = p_names->GetStringLength( i_index, &i_length );
401 
402                 if( SUCCEEDED( hr ) )
403                 {
404                     locale_name.resize( i_length + 1 );
405                     hr = p_names->GetString( i_index, &locale_name[ 0 ], ( UINT32 ) locale_name.size() );
406 
407                     if( SUCCEEDED( hr ) )
408                     {
409                         bool b_result;
410                         if( b_partial )
411                             b_result = DWrite_PartialMatch( p_filter, locale_name, name, p_unmatched );
412                         else
413                             b_result = DWrite_PartialMatch( p_filter, name, locale_name, p_unmatched );
414 
415                         if( b_result )
416                             return true;
417                     }
418                 }
419             }
420         }
421         catch( ... )
422         {
423         }
424     }
425 
426     return false;
427 }
428 
DWrite_GetFonts(filter_t * p_filter,IDWriteFontFamily * p_dw_family,const wstring & face_name)429 static vector< ComPtr< IDWriteFont > > DWrite_GetFonts( filter_t *p_filter, IDWriteFontFamily *p_dw_family,
430                                                         const wstring &face_name )
431 {
432     vector< ComPtr< IDWriteFont > > result;
433 
434     if( !face_name.empty() )
435     {
436         UINT32 i_count = p_dw_family->GetFontCount();
437         for( UINT32 i = 0; i < i_count; ++i )
438         {
439             ComPtr< IDWriteFont > p_dw_font;
440             ComPtr< IDWriteLocalizedStrings > p_dw_names;
441 
442             if( FAILED( p_dw_family->GetFont( i, p_dw_font.GetAddressOf() ) ) )
443                 throw runtime_error( "GetFont() failed" );
444 
445             if( FAILED( p_dw_font->GetFaceNames( p_dw_names.GetAddressOf() ) ) )
446                 throw runtime_error( "GetFaceNames() failed" );
447 
448             if( DWrite_PartialMatch( p_filter, p_dw_names, face_name, true ) )
449                 result.push_back( p_dw_font );
450         }
451     }
452     else
453     {
454         for( int i = 0; i < 4; ++i )
455         {
456             ComPtr< IDWriteFont > p_dw_font;
457             DWRITE_FONT_STYLE style;
458             DWRITE_FONT_WEIGHT weight;
459 
460             switch( i )
461             {
462             case 0:
463                 weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_NORMAL;
464                 break;
465             case 1:
466                 weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_NORMAL;
467                 break;
468             case 2:
469                 weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_ITALIC;
470                 break;
471             default:
472                 weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_ITALIC;
473                 break;
474             }
475 
476             if( FAILED( p_dw_family->GetFirstMatchingFont( weight, DWRITE_FONT_STRETCH_NORMAL, style, p_dw_font.GetAddressOf() ) ) )
477                 throw runtime_error( "GetFirstMatchingFont() failed" );
478 
479             result.push_back( p_dw_font );
480         }
481     }
482 
483     return result;
484 }
485 
DWrite_ParseFamily(filter_t * p_filter,IDWriteFontFamily * p_dw_family,const wstring & face_name,vlc_family_t * p_family,vector<FT_Stream> & streams)486 static void DWrite_ParseFamily( filter_t *p_filter, IDWriteFontFamily *p_dw_family, const wstring &face_name,
487                                 vlc_family_t *p_family, vector< FT_Stream > &streams )
488 {
489     vector< ComPtr< IDWriteFont > > fonts = DWrite_GetFonts( p_filter, p_dw_family, face_name );
490 
491     /*
492      * We select at most 4 fonts to add to p_family, one for each style. So in case of
493      * e.g multiple fonts with weights >= 700 we select the one closest to 700 as the
494      * bold font.
495      */
496     IDWriteFont *p_filtered[ 4 ] = {};
497 
498     for( size_t i = 0; i < fonts.size(); ++i )
499     {
500         ComPtr< IDWriteFont > p_dw_font = fonts[ i ];
501 
502         /* Skip oblique. It's handled by FreeType */
503         if( p_dw_font->GetStyle() == DWRITE_FONT_STYLE_OBLIQUE )
504             continue;
505 
506         bool b_bold = p_dw_font->GetWeight() >= 700;
507         bool b_italic = p_dw_font->GetStyle() == DWRITE_FONT_STYLE_ITALIC;
508 
509         size_t i_index = 0 | ( b_bold ? 1 : 0 ) | ( b_italic ? 2 : 0 );
510         IDWriteFont **pp_font = &p_filtered[ i_index ];
511 
512         if( *pp_font )
513         {
514             int i_weight = b_bold ? 700 : 400;
515             if( abs( ( *pp_font )->GetWeight() - i_weight ) > abs( p_dw_font->GetWeight() - i_weight ) )
516             {
517                 msg_Dbg( p_filter, "DWrite_ParseFamily(): using font at index %u with weight %u for bold: %d, italic: %d",
518                          ( unsigned ) i, p_dw_font->GetWeight(), b_bold, b_italic );
519                 *pp_font = p_dw_font.Get();
520             }
521         }
522         else
523         {
524             msg_Dbg( p_filter, "DWrite_ParseFamily(): using font at index %u with weight %u for bold: %d, italic: %d",
525                      ( unsigned ) i, p_dw_font->GetWeight(), b_bold, b_italic );
526             *pp_font = p_dw_font.Get();
527         }
528     }
529 
530     for( size_t i = 0; i < 4; ++i )
531     {
532         IDWriteFont                      *p_dw_font;
533         ComPtr< IDWriteFontFace >         p_dw_face;
534         ComPtr< IDWriteFontFileLoader >   p_dw_loader;
535         ComPtr< IDWriteFontFile >         p_dw_file;
536 
537         p_dw_font = p_filtered[ i ];
538         if( !p_dw_font )
539             continue;
540 
541         bool b_bold = i & 1;
542         bool b_italic = i & 2;
543 
544         if( p_dw_font->CreateFontFace( p_dw_face.GetAddressOf() ) )
545             throw runtime_error( "CreateFontFace() failed" );
546 
547         UINT32 i_num_files = 0;
548         if( p_dw_face->GetFiles( &i_num_files, NULL ) || i_num_files == 0 )
549             throw runtime_error( "GetFiles() failed" );
550 
551         i_num_files = 1;
552         if( p_dw_face->GetFiles( &i_num_files, p_dw_file.GetAddressOf() ) )
553             throw runtime_error( "GetFiles() failed" );
554 
555         UINT32 i_font_index = p_dw_face->GetIndex();
556 
557         if( p_dw_file->GetLoader( p_dw_loader.GetAddressOf() ) )
558             throw runtime_error( "GetLoader() failed" );
559 
560         const void  *key;
561         UINT32       keySize;
562         if( p_dw_file->GetReferenceKey( &key, &keySize ) )
563             throw runtime_error( "GetReferenceKey() failed" );
564 
565         ComPtr< IDWriteFontFileStream > p_dw_stream;
566         if( p_dw_loader->CreateStreamFromKey( key, keySize, p_dw_stream.GetAddressOf() ) )
567             throw runtime_error( "CreateStreamFromKey() failed" );
568 
569         UINT64 i_stream_size;
570         if( p_dw_stream->GetFileSize( &i_stream_size ) )
571             throw runtime_error( "GetFileSize() failed" );
572 
573         FT_Stream p_stream = ( FT_Stream ) calloc( 1, sizeof( *p_stream ) );
574         if( p_stream == NULL ) throw bad_alloc();
575 
576         p_stream->descriptor.pointer = p_dw_stream.Get();
577         p_stream->read = DWrite_Read;
578         p_stream->close = DWrite_Close;
579         p_stream->size = i_stream_size;
580 
581         try { streams.push_back( p_stream ); }
582         catch( ... ) { free( p_stream ); throw; }
583         p_dw_stream.Detach();
584 
585         char *psz_font_path = NULL;
586         if( asprintf( &psz_font_path, ":dw/%d", ( int ) streams.size() - 1 ) < 0 )
587             throw bad_alloc();
588 
589         NewFont( psz_font_path, i_font_index, b_bold, b_italic, p_family );
590     }
591 }
592 
DWrite_GetFamily(filter_t * p_filter,const char * psz_family)593 extern "C" const vlc_family_t *DWrite_GetFamily( filter_t *p_filter, const char *psz_family )
594 {
595     filter_sys_t                 *p_sys        = p_filter->p_sys;
596     dw_sys_t                     *p_dw_sys     = ( dw_sys_t * ) p_sys->p_dw_sys;
597     ComPtr< IDWriteFontFamily >   p_dw_family;
598 
599     UINT32 i_index;
600     BOOL b_exists = false;
601 
602     char *psz_lc = ToLower( psz_family );
603     if( unlikely( !psz_lc ) )
604         return NULL;
605 
606     vlc_family_t *p_family =
607         ( vlc_family_t * ) vlc_dictionary_value_for_key( &p_sys->family_map, psz_lc );
608 
609     free( psz_lc );
610 
611     if( p_family )
612         return p_family;
613 
614     p_family = NewFamily( p_filter, psz_family, &p_sys->p_families,
615                           &p_sys->family_map, psz_family );
616 
617     if( unlikely( !p_family ) )
618         return NULL;
619 
620     msg_Dbg( p_filter, "DWrite_GetFamily(): family name: %s", psz_family );
621 
622     wchar_t *pwsz_family = ToWide( psz_family );
623     if( unlikely( !pwsz_family ) )
624         goto done;
625 
626     /* Try to find an exact match first */
627     if( SUCCEEDED( p_dw_sys->p_dw_system_fonts->FindFamilyName( pwsz_family, &i_index, &b_exists ) ) && b_exists )
628     {
629         if( FAILED( p_dw_sys->p_dw_system_fonts->GetFontFamily( i_index, p_dw_family.GetAddressOf() ) ) )
630         {
631             msg_Err( p_filter, "DWrite_GetFamily: GetFontFamily() failed" );
632             goto done;
633         }
634 
635         try
636         {
637             DWrite_ParseFamily( p_filter, p_dw_family.Get(), wstring{}, p_family, p_dw_sys->streams );
638         }
639         catch( const exception &e )
640         {
641             msg_Err( p_filter, "DWrite_GetFamily(): %s", e.what() );
642             goto done;
643         }
644     }
645 
646     /*
647      * DirectWrite does not recognize Roboto Thin and similar as family names.
648      * When enumerating names via DirectWrite we get only Roboto. Thin, Black etc
649      * are face names within the Roboto family.
650      * So we try partial name matching if an exact match cannot be found. In this
651      * case Roboto Thin will match the Roboto family, and the unmatched part of
652      * the name (i.e Thin) will be used to find faces within that family (Thin,
653      * Thin Italic, Thin Oblique...)
654      */
655     if( !p_dw_family )
656     {
657         wstring face_name;
658         ComPtr< IDWriteLocalizedStrings > p_names;
659 
660         UINT32 i_count = p_dw_sys->p_dw_system_fonts->GetFontFamilyCount();
661 
662         for( i_index = 0; i_index < i_count; ++i_index )
663         {
664             ComPtr< IDWriteFontFamily > p_cur_family;
665             if( FAILED( p_dw_sys->p_dw_system_fonts->GetFontFamily( i_index, p_cur_family.GetAddressOf() ) ) )
666             {
667                 msg_Err( p_filter, "DWrite_GetFamily: GetFontFamily() failed" );
668                 continue;
669             }
670 
671             if( FAILED( p_cur_family->GetFamilyNames( p_names.GetAddressOf() ) ) )
672             {
673                 msg_Err( p_filter, "DWrite_GetFamily: GetFamilyNames() failed" );
674                 continue;
675             }
676 
677             if( !DWrite_PartialMatch( p_filter, p_names, wstring{ pwsz_family }, false, &face_name ) )
678                 continue;
679 
680             try
681             {
682                 DWrite_ParseFamily( p_filter, p_cur_family.Get(), face_name, p_family, p_dw_sys->streams );
683             }
684             catch( const exception &e )
685             {
686                 msg_Err( p_filter, "DWrite_GetFamily(): %s", e.what() );
687             }
688 
689             /*
690              * If the requested family is e.g. Microsoft JhengHei UI Light, DWrite_PartialMatch will return
691              * true for both Microsoft JhengHei and Microsoft JhengHei UI. When the former is used with
692              * DWrite_ParseFamily no faces will be matched (because face_name will be UI Light) and
693              * p_family->p_fonts will be NULL. With the latter face_name will be Light and the correct face
694              * will be added to p_family, therefore breaking this loop.
695              */
696             if( p_family->p_fonts )
697                 break;
698         }
699     }
700 
701 done:
702     free( pwsz_family );
703     return p_family;
704 }
705 
DWrite_Fallback(filter_t * p_filter,const char * psz_family,uni_char_t codepoint)706 static char *DWrite_Fallback( filter_t *p_filter, const char *psz_family,
707                               uni_char_t codepoint )
708 {
709     filter_sys_t                     *p_sys             = p_filter->p_sys;
710     dw_sys_t                         *p_dw_sys          = ( dw_sys_t * ) p_sys->p_dw_sys;
711     wchar_t                          *pwsz_buffer       = NULL;
712     char                             *psz_result        = NULL;
713     UINT32                            i_string_length   = 0;
714     UINT32                            i_mapped_length   = 0;
715     float                             f_scale;
716     ComPtr< IDWriteFont >             p_dw_font;
717     ComPtr< IDWriteFontFamily >       p_dw_family;
718     ComPtr< IDWriteLocalizedStrings > p_names;
719 
720     msg_Dbg( p_filter, "DWrite_Fallback(): family: %s, codepoint: 0x%x", psz_family, codepoint );
721 
722     wchar_t p_text[2];
723     UINT32  i_text_length;
724     ToUTF16( codepoint, p_text, &i_text_length );
725 
726     wchar_t *pwsz_family = ToWide( psz_family );
727     if( unlikely( !pwsz_family ) ) return NULL;
728 
729     ComPtr< TextSource > p_ts;
730     p_ts = new(std::nothrow) TextSource( p_dw_sys->p_dw_factory.Get(), p_dw_sys->p_dw_substitution.Get(), p_text, i_text_length );
731     if( unlikely( p_ts == NULL ) ) { goto done; }
732 
733     if( p_dw_sys->p_dw_fallbacks->MapCharacters( p_ts.Get(), 0, i_text_length, p_dw_sys->p_dw_system_fonts.Get(), pwsz_family,
734                                   DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
735                                   &i_mapped_length, p_dw_font.GetAddressOf(), &f_scale )
736      || !p_dw_font )
737     {
738         msg_Warn( p_filter, "DWrite_Fallback(): MapCharacters() failed" );
739         goto done;
740     }
741 
742     if( p_dw_font->GetFontFamily( p_dw_family.GetAddressOf() ) )
743     {
744         msg_Err( p_filter, "DWrite_Fallback(): GetFontFamily() failed" );
745         goto done;
746     }
747 
748     if( p_dw_family->GetFamilyNames( p_names.GetAddressOf() ) )
749     {
750         msg_Err( p_filter, "DWrite_Fallback(): GetFamilyNames() failed" );
751         goto done;
752     }
753 
754     if( p_names->GetStringLength( 0, &i_string_length ) )
755     {
756         msg_Err( p_filter, "DWrite_Fallback(): GetStringLength() failed" );
757         goto done;
758     }
759 
760     pwsz_buffer = ( wchar_t * ) vlc_alloc( ( i_string_length + 1 ), sizeof( *pwsz_buffer ) );
761     if( unlikely( !pwsz_buffer ) )
762         goto done;
763 
764     if( p_names->GetString( 0, pwsz_buffer, i_string_length + 1 ) )
765     {
766         msg_Err( p_filter, "DWrite_Fallback(): GetString() failed" );
767         goto done;
768     }
769 
770     psz_result = FromWide( pwsz_buffer );
771     msg_Dbg( p_filter, "DWrite_Fallback(): returning %s", psz_result );
772 
773 done:
774     free( pwsz_buffer );
775     free( pwsz_family );
776     return psz_result;
777 }
778 
DWrite_GetFallbacks(filter_t * p_filter,const char * psz_family,uni_char_t codepoint)779 extern "C" vlc_family_t *DWrite_GetFallbacks( filter_t *p_filter, const char *psz_family,
780                                               uni_char_t codepoint )
781 {
782     filter_sys_t  *p_sys         = p_filter->p_sys;
783     vlc_family_t  *p_family      = NULL;
784     vlc_family_t  *p_fallbacks   = NULL;
785     char          *psz_fallback  = NULL;
786 
787 
788     char *psz_lc = ToLower( psz_family );
789 
790     if( unlikely( !psz_lc ) )
791         return NULL;
792 
793     p_fallbacks = ( vlc_family_t * ) vlc_dictionary_value_for_key( &p_sys->fallback_map, psz_lc );
794 
795     if( p_fallbacks )
796         p_family = SearchFallbacks( p_filter, p_fallbacks, codepoint );
797 
798     /*
799      * If the fallback list of psz_family has no family which contains the requested
800      * codepoint, try DWrite_Fallback(). If it returns a valid family which does
801      * contain that codepoint, add the new family to the fallback list to speed up
802      * later searches.
803      */
804     if( !p_family )
805     {
806         psz_fallback = DWrite_Fallback( p_filter, psz_lc, codepoint );
807 
808         if( !psz_fallback )
809             goto done;
810 
811         const vlc_family_t *p_fallback = DWrite_GetFamily( p_filter, psz_fallback );
812         if( !p_fallback || !p_fallback->p_fonts )
813             goto done;
814 
815         FT_Face p_face = GetFace( p_filter, p_fallback->p_fonts );
816 
817         if( !p_face || !FT_Get_Char_Index( p_face, codepoint ) )
818             goto done;
819 
820         p_family = NewFamily( p_filter, psz_fallback, NULL, NULL, NULL );
821 
822         if( unlikely( !p_family ) )
823             goto done;
824 
825         p_family->p_fonts = p_fallback->p_fonts;
826 
827         if( p_fallbacks )
828             AppendFamily( &p_fallbacks, p_family );
829         else
830             vlc_dictionary_insert( &p_sys->fallback_map,
831                                    psz_lc, p_family );
832     }
833 
834 done:
835     free( psz_lc );
836     free( psz_fallback );
837     return p_family;
838 }
839