freetype: Prevent creating font with NULL path
[vlc.git] / modules / text_renderer / freetype / fonts / darwin.c
1 /*****************************************************************************
2  * darwin.c : Put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2015 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul K├╝hne <fkuehne@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *          Salah-Eddin Shaban <salshaaban@gmail.com>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation, Inc.,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include <vlc_filter.h>                                      /* filter_sys_t */
36
37 #include <CoreFoundation/CoreFoundation.h>
38 #include <CoreText/CoreText.h>
39
40 #include "../platform_fonts.h"
41
42 char* getPathForFontDescription(CTFontDescriptorRef fontDescriptor);
43 void addNewFontToFamily(filter_t *p_filter, CTFontDescriptorRef iter, char *path, vlc_family_t *family);
44
45 static char* getCStringCopyForCFStringRef(CFStringRef cfstring, CFStringEncoding encoding)
46 {
47     // Try to get pointer directly
48     const char *cptr = CFStringGetCStringPtr(cfstring, encoding);
49     if (cptr) {
50         return strdup(cptr);
51     }
52
53     // If it fails, use CFStringGetCString
54     CFIndex len = CFStringGetLength(cfstring);
55     CFIndex size = CFStringGetMaximumSizeForEncoding(len, encoding);
56     char *buffer = calloc(len + 1, sizeof(char));
57
58     if (CFStringGetCString(cfstring, buffer, size, encoding)) {
59         return buffer;
60     } else {
61         free(buffer);
62         return NULL;
63     }
64 }
65
66 char* getPathForFontDescription(CTFontDescriptorRef fontDescriptor)
67 {
68     CFURLRef url = CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
69     CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
70     char *retPath = getCStringCopyForCFStringRef(path, kCFStringEncodingUTF8);
71     CFRelease(path);
72     CFRelease(url);
73     return retPath;
74 }
75
76 void addNewFontToFamily(filter_t *p_filter, CTFontDescriptorRef iter, char *path, vlc_family_t *p_family)
77 {
78     bool b_bold = false;
79     bool b_italic = false;
80     CFDictionaryRef fontTraits = CTFontDescriptorCopyAttribute(iter, kCTFontTraitsAttribute);
81     CFNumberRef trait = CFDictionaryGetValue(fontTraits, kCTFontWeightTrait);
82     float traitValue = 0.;
83     CFNumberGetValue(trait, kCFNumberFloatType, &traitValue);
84     b_bold = traitValue > 0.23;
85     trait = CFDictionaryGetValue(fontTraits, kCTFontSlantTrait);
86     traitValue = 0.;
87     CFNumberGetValue(trait, kCFNumberFloatType, &traitValue);
88     b_italic = traitValue > 0.03;
89
90 #ifndef NDEBUG
91     msg_Dbg(p_filter, "New font: bold %i italic %i path '%s'", b_bold, b_italic, path);
92 #else
93     VLC_UNUSED(p_filter);
94 #endif
95     NewFont(path, 0, b_bold, b_italic, p_family);
96
97     CFRelease(fontTraits);
98 }
99
100 const vlc_family_t *CoreText_GetFamily(filter_t *p_filter, const char *psz_family)
101 {
102     filter_sys_t *p_sys = p_filter->p_sys;
103
104     if (unlikely(psz_family == NULL)) {
105         return NULL;
106     }
107
108     char *psz_lc = ToLower(psz_family);
109     if (unlikely(!psz_lc)) {
110         return NULL;
111     }
112
113     /* let's double check if we have parsed this family already */
114     vlc_family_t *p_family = vlc_dictionary_value_for_key(&p_sys->family_map, psz_lc);
115     if (p_family) {
116         free(psz_lc);
117         return p_family;
118     }
119
120     CTFontCollectionRef coreTextFontCollection = NULL;
121     CFArrayRef matchedFontDescriptions = NULL;
122
123     /* we search for family name, display name and name to find them all */
124     const size_t numberOfAttributes = 3;
125     CTFontDescriptorRef coreTextFontDescriptors[numberOfAttributes];
126     CFMutableDictionaryRef coreTextAttributes[numberOfAttributes];
127     CFStringRef attributeNames[numberOfAttributes] = {
128         kCTFontFamilyNameAttribute,
129         kCTFontDisplayNameAttribute,
130         kCTFontNameAttribute,
131     };
132
133 #ifndef NDEBUG
134     msg_Dbg(p_filter, "Creating new family for '%s'", psz_family);
135 #endif
136
137     CFStringRef familyName = CFStringCreateWithCString(kCFAllocatorDefault,
138                                                        psz_family,
139                                                        kCFStringEncodingUTF8);
140     for (size_t x = 0; x < numberOfAttributes; x++) {
141         coreTextAttributes[x] = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
142         CFDictionaryAddValue(coreTextAttributes[x], attributeNames[x], familyName);
143         coreTextFontDescriptors[x] = CTFontDescriptorCreateWithAttributes(coreTextAttributes[x]);
144     }
145
146     CFArrayRef coreTextFontDescriptorsArray = CFArrayCreate(kCFAllocatorDefault,
147                                                             (const void **)&coreTextFontDescriptors,
148                                                             numberOfAttributes, NULL);
149
150     coreTextFontCollection = CTFontCollectionCreateWithFontDescriptors(coreTextFontDescriptorsArray, 0);
151     if (coreTextFontCollection == NULL) {
152         goto end;
153     }
154
155     matchedFontDescriptions = CTFontCollectionCreateMatchingFontDescriptors(coreTextFontCollection);
156     if (matchedFontDescriptions == NULL) {
157         goto end;
158     }
159
160     CFIndex numberOfFoundFontDescriptions = CFArrayGetCount(matchedFontDescriptions);
161
162     char *path = NULL;
163
164     /* create a new family object */
165     p_family = NewFamily(p_filter, psz_lc, &p_sys->p_families, &p_sys->family_map, psz_lc);
166     if (unlikely(!p_family)) {
167         goto end;
168     }
169
170     for (CFIndex i = 0; i < numberOfFoundFontDescriptions; i++) {
171         CTFontDescriptorRef iter = CFArrayGetValueAtIndex(matchedFontDescriptions, i);
172         path = getPathForFontDescription(iter);
173
174         /* check if the path is empty, which can happen in rare circumstances */
175         if (path != NULL) {
176             if (strcmp("", path) == 0) {
177                 FREENULL(path);
178                 continue;
179             }
180         } else {
181             continue;
182         }
183
184         addNewFontToFamily(p_filter, iter, path, p_family);
185     }
186
187 end:
188     if (matchedFontDescriptions != NULL) {
189         CFRelease(matchedFontDescriptions);
190     }
191     if (coreTextFontCollection != NULL) {
192         CFRelease(coreTextFontCollection);
193     }
194
195     for (size_t x = 0; x < numberOfAttributes; x++) {
196         CFRelease(coreTextAttributes[x]);
197         CFRelease(coreTextFontDescriptors[x]);
198     }
199
200     CFRelease(coreTextFontDescriptorsArray);
201     CFRelease(familyName);
202
203     return p_family;
204 }
205
206 vlc_family_t *CoreText_GetFallbacks(filter_t *p_filter, const char *psz_family, uni_char_t codepoint)
207 {
208     filter_sys_t *p_sys = p_filter->p_sys;
209     if (unlikely(psz_family == NULL)) {
210         return NULL;
211     }
212
213     vlc_family_t *p_family = NULL;
214     CFStringRef postScriptFallbackFontname = NULL;
215     CTFontDescriptorRef fallbackFontDescriptor = NULL;
216     char *psz_lc_fallback = NULL;
217     char *psz_fontPath = NULL;
218
219     CFStringRef familyName = CFStringCreateWithCString(kCFAllocatorDefault,
220                                                        psz_family,
221                                                        kCFStringEncodingUTF8);
222     CTFontRef font = CTFontCreateWithName(familyName, 0, NULL);
223     uint32_t littleEndianCodePoint = OSSwapHostToLittleInt32(codepoint);
224     CFStringRef codepointString = CFStringCreateWithBytes(kCFAllocatorDefault,
225                                                           (const UInt8 *)&littleEndianCodePoint,
226                                                           sizeof(littleEndianCodePoint),
227                                                           kCFStringEncodingUTF32LE,
228                                                           false);
229     CTFontRef fallbackFont = CTFontCreateForString(font, codepointString, CFRangeMake(0,1));
230     CFStringRef fallbackFontFamilyName = CTFontCopyFamilyName(fallbackFont);
231
232     /* create a new family object */
233     const char *psz_fallbackFamilyName = CFStringGetCStringPtr(fallbackFontFamilyName, kCFStringEncodingUTF8);
234     if (psz_fallbackFamilyName == NULL) {
235         goto done;
236     }
237 #ifndef NDEBUG
238     msg_Dbg(p_filter, "Will deploy fallback font '%s'", psz_fallbackFamilyName);
239 #endif
240
241     psz_lc_fallback = ToLower(strdup(psz_fallbackFamilyName));
242
243     p_family = vlc_dictionary_value_for_key(&p_sys->family_map, psz_lc_fallback);
244     if (p_family) {
245         goto done;
246     }
247
248     p_family = NewFamily(p_filter, psz_lc_fallback, &p_sys->p_families, &p_sys->family_map, psz_lc_fallback);
249     if (unlikely(!p_family)) {
250         goto done;
251     }
252
253     postScriptFallbackFontname = CTFontCopyPostScriptName(fallbackFont);
254     fallbackFontDescriptor = CTFontDescriptorCreateWithNameAndSize(postScriptFallbackFontname, 0.);
255     psz_fontPath = getPathForFontDescription(fallbackFontDescriptor);
256
257     /* check if the path is empty, which can happen in rare circumstances */
258     if (psz_fontPath != NULL) {
259         if (strcmp("", psz_fontPath) == 0) {
260             goto done;
261         }
262     } else {
263         goto done;
264     }
265
266     addNewFontToFamily(p_filter, fallbackFontDescriptor, strdup(psz_fontPath), p_family);
267
268 done:
269     CFRelease(familyName);
270     CFRelease(font);
271     CFRelease(codepointString);
272     CFRelease(fallbackFont);
273     CFRelease(fallbackFontFamilyName);
274     free(psz_lc_fallback);
275     free(psz_fontPath);
276     if (postScriptFallbackFontname != NULL)
277         CFRelease(postScriptFallbackFontname);
278     if (fallbackFontDescriptor != NULL)
279         CFRelease(fallbackFontDescriptor);
280     return p_family;
281 }