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