1 /*****************************************************************************
2  * freetype.c : Put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2002 - 2015 VLC authors and VideoLAN
5  * $Id: 73d4939f07eb5022f1eb2f313f98a88a8527de34 $
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *          Bernie Purcell <bitmap@videolan.org>
10  *          Jean-Baptiste Kempf <jb@videolan.org>
11  *          Felix Paul Kühne <fkuehne@videolan.org>
12  *          Salah-Eddin Shaban <salshaaban@gmail.com>
13  *
14  * This program is free software; you can redistribute it and/or modify it
15  * under the terms of the GNU Lesser General Public License as published by
16  * the Free Software Foundation; either version 2.1 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with this program; if not, write to the Free Software Foundation, Inc.,
26  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
28 
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32 
33 /** \ingroup freetype_fonts
34  * @{
35  * \file
36  * Win32 font management
37  */
38 
39 #ifdef HAVE_CONFIG_H
40 # include "config.h"
41 #endif
42 
43 #include <vlc_common.h>
44 #include <vlc_filter.h>
45 
46 #include <ft2build.h>
47 #include FT_SFNT_NAMES_H
48 
49 /* Win32 GDI */
50 #ifdef _WIN32
51 # include <windows.h>
52 # include <shlobj.h>
53 # include <usp10.h>
54 # include <vlc_charset.h>                                     /* FromT */
55 # undef HAVE_FONTCONFIG
56 #endif
57 
58 #include "../platform_fonts.h"
59 
60 #if !VLC_WINSTORE_APP
61 #define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
62 
AppendFamily(vlc_family_t ** pp_list,vlc_family_t * p_family)63 static inline void AppendFamily( vlc_family_t **pp_list, vlc_family_t *p_family )
64 {
65     while( *pp_list )
66         pp_list = &( *pp_list )->p_next;
67 
68     *pp_list = p_family;
69 }
70 
71 
Trim(char * psz_text)72 static char *Trim( char *psz_text )
73 {
74     int i_first_char = -1;
75     int i_last_char = -1;
76     int i_len = strlen( psz_text );
77 
78     for( int i = 0; i < i_len; ++i )
79     {
80         if( psz_text[i] != ' ')
81         {
82             if( i_first_char == -1 )
83                 i_first_char = i;
84             i_last_char = i;
85         }
86     }
87 
88     psz_text[ i_last_char + 1 ] = 0;
89     if( i_first_char != -1 ) psz_text = psz_text + i_first_char;
90 
91     return psz_text;
92 }
93 
ConcatenatedIndex(char * psz_haystack,const char * psz_needle)94 static int ConcatenatedIndex( char *psz_haystack, const char *psz_needle )
95 {
96     char *psz_family = psz_haystack;
97     char *psz_terminator = psz_haystack + strlen( psz_haystack );
98     int i_index = 0;
99 
100     while( psz_family < psz_terminator )
101     {
102         char *psz_amp = strchr( psz_family, '&' );
103 
104         if( !psz_amp ) psz_amp = psz_terminator;
105 
106         *psz_amp = 0;
107 
108         psz_family = Trim( psz_family );
109         if( !strcasecmp( psz_family, psz_needle ) )
110             return i_index;
111 
112         psz_family = psz_amp + 1;
113         ++i_index;
114     }
115 
116     return -1;
117 }
118 
GetFileFontByName(LPCTSTR font_name,char ** psz_filename,int * i_index)119 static int GetFileFontByName( LPCTSTR font_name, char **psz_filename, int *i_index )
120 {
121     HKEY hKey;
122     TCHAR vbuffer[MAX_PATH];
123     TCHAR dbuffer[256];
124 
125     if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
126             != ERROR_SUCCESS )
127         return 1;
128 
129     char *font_name_temp = FromT( font_name );
130 
131     for( int index = 0;; index++ )
132     {
133         DWORD vbuflen = MAX_PATH - 1;
134         DWORD dbuflen = 255;
135 
136         LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
137                                       NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
138         if( i_result != ERROR_SUCCESS )
139         {
140             free( font_name_temp );
141             RegCloseKey( hKey );
142             return i_result;
143         }
144 
145         char *psz_value = FromT( vbuffer );
146 
147         char *s = strchr( psz_value,'(' );
148         if( s != NULL && s != psz_value ) s[-1] = '\0';
149 
150         int i_concat_idx = 0;
151         if( ( i_concat_idx = ConcatenatedIndex( psz_value, font_name_temp ) ) != -1 )
152         {
153             *i_index = i_concat_idx;
154             *psz_filename = FromT( dbuffer );
155             free( psz_value );
156             break;
157         }
158 
159         free( psz_value );
160     }
161 
162     free( font_name_temp );
163     RegCloseKey( hKey );
164     return 0;
165 }
166 
GetWindowsFontPath()167 static char* GetWindowsFontPath()
168 {
169     wchar_t wdir[MAX_PATH];
170     if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
171     {
172         GetWindowsDirectoryW( wdir, MAX_PATH );
173         wcscat( wdir, L"\\fonts" );
174     }
175     return FromWide( wdir );
176 }
177 
178 /**
179  * Get an SFNT name entry from an SFNT name table.
180  *
181  * \param p_name_data the start position of the name entry within the table [IN]
182  * \param p_storage_start the start position of the string storage area in the table [IN]
183  * \param p_table_end the position after the last byte of the table [IN]
184  * \param p_sfnt_name pointer to a \ref FT_SfntName to fill [OUT]
185  *
186  * \return \ref VLC_SUCCESS or \ref VLC_EGENERIC
187  */
GetSfntNameEntry(FT_Byte * p_name_data,FT_Byte * p_storage_start,FT_Byte * p_table_end,FT_SfntName * p_sfnt_name)188 static int GetSfntNameEntry( FT_Byte *p_name_data, FT_Byte *p_storage_start,
189                              FT_Byte *p_table_end, FT_SfntName *p_sfnt_name )
190 {
191     uint16_t i_string_len      = U16_AT( p_name_data + 8 );
192     uint16_t i_string_offset   = U16_AT( p_name_data + 10 );
193     if( i_string_len == 0
194      || p_storage_start + i_string_offset + i_string_len > p_table_end )
195         return VLC_EGENERIC;
196 
197     p_sfnt_name->platform_id = U16_AT( p_name_data + 0 );
198     p_sfnt_name->encoding_id = U16_AT( p_name_data + 2 );
199     p_sfnt_name->language_id = U16_AT( p_name_data + 4 );
200     p_sfnt_name->name_id     = U16_AT( p_name_data + 6 );
201     p_sfnt_name->string_len  = i_string_len;
202     p_sfnt_name->string      = p_storage_start + i_string_offset;
203 
204     return VLC_SUCCESS;
205 }
206 
207 /**
208  * Get the SFNT name string matching the specified platform, encoding, name, and language IDs.
209  *
210  *\param p_table the SFNT table [IN]
211  *\param i_size table size in bytes [IN]
212  *\param i_platform_id platform ID as specified in the TrueType spec [IN]
213  *\param i_encoding_id encoding ID as specified in the TrueType spec [IN]
214  *\param i_name_id name ID as specified in the TrueType spec [IN]
215  *\param i_language_id language ID as specified in the TrueType spec [IN]
216  *\param pp_name the requested name.
217  *       This is not null terminated. And can have different encodings
218  *       based on the specified platform/encoding IDs [OUT]
219  *\param i_name_length the length in bytes of the returned name [OUT]
220  *
221  *\return \ref VLC_SUCCESS or \ref VLC_EGENERIC
222  */
GetSfntNameString(FT_Byte * p_table,FT_UInt i_size,FT_UShort i_platform_id,FT_UShort i_encoding_id,FT_UShort i_name_id,FT_UShort i_language_id,FT_Byte ** pp_name,FT_UInt * i_name_length)223 static int GetSfntNameString( FT_Byte *p_table, FT_UInt i_size, FT_UShort i_platform_id,
224                               FT_UShort i_encoding_id, FT_UShort i_name_id, FT_UShort i_language_id,
225                               FT_Byte **pp_name, FT_UInt *i_name_length )
226 {
227     uint16_t i_name_count     = U16_AT( p_table + 2 );
228     uint16_t i_storage_offset = U16_AT( p_table + 4 );
229     FT_Byte *p_storage        = p_table + i_storage_offset;
230     FT_Byte *p_names          = p_table + 6;
231 
232     const int i_entry_size = 12;
233 
234     for(int i = 0; i < i_name_count; ++i)
235     {
236         FT_SfntName sfnt_name;
237 
238         if( GetSfntNameEntry( p_names + i * i_entry_size, p_storage, p_table + i_size, &sfnt_name ) )
239             return VLC_EGENERIC;
240 
241         if( sfnt_name.platform_id == i_platform_id && sfnt_name.encoding_id == i_encoding_id
242          && sfnt_name.name_id == i_name_id && sfnt_name.language_id == i_language_id )
243         {
244             *i_name_length = sfnt_name.string_len;
245             *pp_name = sfnt_name.string;
246 
247             return VLC_SUCCESS;
248         }
249     }
250 
251     return VLC_EGENERIC;
252 }
253 
254 /**
255  * Get the font's full English name.
256  *
257  * If the font has a family name or style name that matches the
258  * system's locale, EnumFontCallback() will be called with a \ref ENUMLOGFONTEX
259  * that has the localized name.
260  *
261  * We have to get the English name because that's what the Windows registry uses
262  * for name to file mapping.
263  */
GetFullEnglishName(const ENUMLOGFONTEX * lpelfe)264 static TCHAR *GetFullEnglishName( const ENUMLOGFONTEX *lpelfe )
265 {
266 
267     HFONT    hFont      = NULL;
268     HDC      hDc        = NULL;
269     FT_Byte *p_table    = NULL;
270     TCHAR   *psz_result = NULL;
271 
272     hFont = CreateFontIndirect( &lpelfe->elfLogFont );
273 
274     if( !hFont )
275         return NULL;
276 
277     hDc = CreateCompatibleDC( NULL );
278 
279     if( !hDc )
280     {
281         DeleteObject( hFont );
282         return NULL;
283     }
284 
285     HFONT hOriginalFont = ( HFONT ) SelectObject( hDc, hFont );
286 
287     const uint32_t i_name_tag = ntoh32( ( uint32_t ) 'n' << 24
288                                       | ( uint32_t ) 'a' << 16
289                                       | ( uint32_t ) 'm' << 8
290                                       | ( uint32_t ) 'e' << 0 );
291 
292     int i_size = GetFontData( hDc, i_name_tag, 0, 0, 0 );
293 
294     if( i_size <= 0 )
295         goto done;
296 
297     p_table = malloc( i_size );
298 
299     if( !p_table )
300         goto done;
301 
302     if( GetFontData( hDc, i_name_tag, 0, p_table, i_size ) <= 0 )
303         goto done;
304 
305     FT_Byte *p_name = NULL;
306     FT_UInt  i_name_length = 0;
307 
308     /* FIXME: Try other combinations of platform/encoding/language IDs if necessary */
309     if( GetSfntNameString( p_table, i_size, 3, 1, 4, 0x409, &p_name, &i_name_length) )
310         goto done;
311 
312     int i_length_in_wchars = i_name_length / 2;
313     wchar_t *psz_name = vlc_alloc( i_length_in_wchars + 1, sizeof( *psz_name ) );
314 
315     if( !psz_name )
316         goto done;
317 
318     for( int i = 0; i < i_length_in_wchars; ++i )
319         psz_name[ i ] = U16_AT( p_name + i * 2 );
320     psz_name[ i_length_in_wchars ] = 0;
321 
322 #ifdef UNICODE
323     psz_result = psz_name;
324 #else
325     psz_result = FromWide( psz_name );
326     free( psz_name );
327 #endif
328 
329 done:
330     free( p_table );
331     SelectObject( hDc, hOriginalFont );
332     DeleteObject( hFont );
333     DeleteDC( hDc );
334 
335     return psz_result;
336 }
337 
EnumFontCallback(const ENUMLOGFONTEX * lpelfe,const NEWTEXTMETRICEX * metric,DWORD type,LPARAM lParam)338 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
339                                      DWORD type, LPARAM lParam)
340 {
341     VLC_UNUSED( metric );
342     if( (type & RASTER_FONTTYPE) ) return 1;
343 
344     vlc_family_t *p_family = ( vlc_family_t * ) lParam;
345 
346     bool b_bold = ( lpelfe->elfLogFont.lfWeight == FW_BOLD );
347     bool b_italic = ( lpelfe->elfLogFont.lfItalic != 0 );
348 
349     /*
350      * This function will be called by Windows as many times for each font
351      * of the family as the number of scripts the font supports.
352      * Check to avoid duplicates.
353      */
354     for( vlc_font_t *p_font = p_family->p_fonts; p_font; p_font = p_font->p_next )
355         if( !!p_font->b_bold == !!b_bold && !!p_font->b_italic == !!b_italic )
356             return 1;
357 
358     char *psz_filename = NULL;
359     char *psz_fontfile = NULL;
360     int   i_index      = 0;
361 
362     if( GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, &psz_filename, &i_index ) )
363     {
364         TCHAR *psz_english_name = GetFullEnglishName( lpelfe );
365 
366         if( !psz_english_name )
367             return 1;
368 
369         if( GetFileFontByName( psz_english_name, &psz_filename, &i_index ) )
370         {
371             free( psz_english_name );
372             return 1;
373         }
374 
375         free( psz_english_name );
376     }
377 
378     if( strchr( psz_filename, DIR_SEP_CHAR ) )
379         psz_fontfile = psz_filename;
380     else
381     {
382         /* Get Windows Font folder */
383         char *psz_win_fonts_path = GetWindowsFontPath();
384         if( asprintf( &psz_fontfile, "%s\\%s", psz_win_fonts_path, psz_filename ) == -1 )
385         {
386             free( psz_filename );
387             free( psz_win_fonts_path );
388             return 1;
389         }
390         free( psz_filename );
391         free( psz_win_fonts_path );
392     }
393 
394     NewFont( psz_fontfile, i_index, b_bold, b_italic, p_family );
395 
396     return 1;
397 }
398 
Win32_GetFamily(filter_t * p_filter,const char * psz_family)399 const vlc_family_t *Win32_GetFamily( filter_t *p_filter, const char *psz_family )
400 {
401     filter_sys_t *p_sys = p_filter->p_sys;
402     char *psz_lc = ToLower( psz_family );
403 
404     if( unlikely( !psz_lc ) )
405         return NULL;
406 
407     vlc_family_t *p_family =
408         vlc_dictionary_value_for_key( &p_sys->family_map, psz_lc );
409 
410     free( psz_lc );
411 
412     if( p_family )
413         return p_family;
414 
415     p_family = NewFamily( p_filter, psz_family, &p_sys->p_families,
416                           &p_sys->family_map, psz_family );
417 
418     if( unlikely( !p_family ) )
419         return NULL;
420 
421     LOGFONT lf;
422     lf.lfCharSet = DEFAULT_CHARSET;
423 
424     LPTSTR psz_fbuffer = ToT( psz_family );
425     _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
426     free( psz_fbuffer );
427 
428     /* */
429     HDC hDC = GetDC( NULL );
430     EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)p_family, 0);
431     ReleaseDC(NULL, hDC);
432 
433     return p_family;
434 }
435 
MetaFileEnumProc(HDC hdc,HANDLETABLE * table,CONST ENHMETARECORD * record,int table_entries,LPARAM log_font)436 static int CALLBACK MetaFileEnumProc( HDC hdc, HANDLETABLE* table,
437                                       CONST ENHMETARECORD* record,
438                                       int table_entries, LPARAM log_font )
439 {
440     VLC_UNUSED( hdc );
441     VLC_UNUSED( table );
442     VLC_UNUSED( table_entries );
443 
444     if( record->iType == EMR_EXTCREATEFONTINDIRECTW )
445     {
446         const EMREXTCREATEFONTINDIRECTW* create_font_record =
447                 ( const EMREXTCREATEFONTINDIRECTW * ) record;
448 
449         *( ( LOGFONT * ) log_font ) = create_font_record->elfw.elfLogFont;
450     }
451     return 1;
452 }
453 
454 /**
455  * This is a hack used by Chrome and WebKit to expose the fallback font used
456  * by Uniscribe for some given text for use with custom shapers / font engines.
457  */
UniscribeFallback(const char * psz_family,uni_char_t codepoint)458 static char *UniscribeFallback( const char *psz_family, uni_char_t codepoint )
459 {
460     HDC          hdc          = NULL;
461     HDC          meta_file_dc = NULL;
462     HENHMETAFILE meta_file    = NULL;
463     LPTSTR       psz_fbuffer  = NULL;
464     char        *psz_result   = NULL;
465 
466     hdc = CreateCompatibleDC( NULL );
467     if( !hdc )
468         return NULL;
469 
470     meta_file_dc = CreateEnhMetaFile( hdc, NULL, NULL, NULL );
471     if( !meta_file_dc )
472         goto error;
473 
474     LOGFONT lf;
475     memset( &lf, 0, sizeof( lf ) );
476 
477     psz_fbuffer = ToT( psz_family );
478     if( !psz_fbuffer )
479         goto error;
480     _tcsncpy( ( LPTSTR ) &lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
481     free( psz_fbuffer );
482 
483     lf.lfCharSet = DEFAULT_CHARSET;
484     HFONT hFont = CreateFontIndirect( &lf );
485     if( !hFont )
486         goto error;
487 
488     HFONT hOriginalFont = SelectObject( meta_file_dc, hFont );
489 
490     TCHAR text = codepoint;
491 
492     SCRIPT_STRING_ANALYSIS script_analysis;
493     HRESULT hresult = ScriptStringAnalyse( meta_file_dc, &text, 1, 0, -1,
494                             SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
495                             0, NULL, NULL, NULL, NULL, NULL, &script_analysis );
496 
497     if( SUCCEEDED( hresult ) )
498     {
499         hresult = ScriptStringOut( script_analysis, 0, 0, 0, NULL, 0, 0, FALSE );
500         ScriptStringFree( &script_analysis );
501     }
502 
503     SelectObject( meta_file_dc, hOriginalFont );
504     DeleteObject( hFont );
505     meta_file = CloseEnhMetaFile( meta_file_dc );
506 
507     if( SUCCEEDED( hresult ) )
508     {
509         LOGFONT log_font;
510         log_font.lfFaceName[ 0 ] = 0;
511         EnumEnhMetaFile( 0, meta_file, MetaFileEnumProc, &log_font, NULL );
512         if( log_font.lfFaceName[ 0 ] )
513             psz_result = FromT( log_font.lfFaceName );
514     }
515 
516     DeleteEnhMetaFile(meta_file);
517     DeleteDC( hdc );
518     return psz_result;
519 
520 error:
521     if( meta_file_dc ) DeleteEnhMetaFile( CloseEnhMetaFile( meta_file_dc ) );
522     if( hdc ) DeleteDC( hdc );
523     return NULL;
524 }
525 
Win32_GetFallbacks(filter_t * p_filter,const char * psz_family,uni_char_t codepoint)526 vlc_family_t *Win32_GetFallbacks( filter_t *p_filter, const char *psz_family,
527                                   uni_char_t codepoint )
528 {
529     vlc_family_t  *p_family      = NULL;
530     vlc_family_t  *p_fallbacks   = NULL;
531     filter_sys_t  *p_sys         = p_filter->p_sys;
532     char          *psz_uniscribe = NULL;
533 
534 
535     char *psz_lc = ToLower( psz_family );
536 
537     if( unlikely( !psz_lc ) )
538         return NULL;
539 
540     p_fallbacks = vlc_dictionary_value_for_key( &p_sys->fallback_map, psz_lc );
541 
542     if( p_fallbacks )
543         p_family = SearchFallbacks( p_filter, p_fallbacks, codepoint );
544 
545     /*
546      * If the fallback list of psz_family has no family which contains the requested
547      * codepoint, try UniscribeFallback(). If it returns a valid family which does
548      * contain that codepoint, add the new family to the fallback list to speed up
549      * later searches.
550      */
551     if( !p_family )
552     {
553         psz_uniscribe = UniscribeFallback( psz_lc, codepoint );
554 
555         if( !psz_uniscribe )
556             goto done;
557 
558         const vlc_family_t *p_uniscribe = Win32_GetFamily( p_filter, psz_uniscribe );
559         if( !p_uniscribe || !p_uniscribe->p_fonts )
560             goto done;
561 
562         FT_Face p_face = GetFace( p_filter, p_uniscribe->p_fonts );
563 
564         if( !p_face || !FT_Get_Char_Index( p_face, codepoint ) )
565             goto done;
566 
567         p_family = NewFamily( p_filter, psz_uniscribe, NULL, NULL, NULL );
568 
569         if( unlikely( !p_family ) )
570             goto done;
571 
572         p_family->p_fonts = p_uniscribe->p_fonts;
573 
574         if( p_fallbacks )
575             AppendFamily( &p_fallbacks, p_family );
576         else
577             vlc_dictionary_insert( &p_sys->fallback_map,
578                                    psz_lc, p_family );
579     }
580 
581 done:
582     free( psz_lc );
583     free( psz_uniscribe );
584     return p_family;
585 }
586 
Dummy_Select(filter_t * p_filter,const char * psz_font,bool b_bold,bool b_italic,int * i_idx,uni_char_t codepoint)587 char* Dummy_Select( filter_t *p_filter, const char* psz_font,
588                     bool b_bold, bool b_italic,
589                     int *i_idx, uni_char_t codepoint )
590 {
591     VLC_UNUSED(p_filter);
592     VLC_UNUSED(b_bold);
593     VLC_UNUSED(b_italic);
594     VLC_UNUSED(codepoint);
595     VLC_UNUSED(i_idx);
596 
597     char *psz_fontname;
598     /* Get Windows Font folder */
599     char *psz_win_fonts_path = GetWindowsFontPath();
600     if( asprintf( &psz_fontname, "%s\\%s", psz_win_fonts_path, psz_font ) == -1 )
601         psz_fontname = NULL;
602     free(psz_win_fonts_path);
603 
604     return psz_fontname;
605 }
606 
607 #endif
608 
609