1 /*****************************************************************************
2  * darwin.c : Put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2015 VLC authors and VideoLAN
5  * $Id: bf9f8268384c584988d88bd72494862da141709b $
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 /* Obtains a copy of the contents of a CFString in specified encoding.
46  * Returns char* (must be freed by caller) or NULL on failure.
47  */
CFStringCopyCString(CFStringRef cfString,CFStringEncoding cfStringEncoding)48 static char* CFStringCopyCString(CFStringRef cfString, CFStringEncoding cfStringEncoding)
49 {
50     // Try the quick way to obtain the buffer
51     const char *tmpBuffer = CFStringGetCStringPtr(cfString, cfStringEncoding);
52 
53     if (tmpBuffer != NULL) {
54        return strdup(tmpBuffer);
55     }
56 
57     // The quick way did not work, try the long way
58     CFIndex length = CFStringGetLength(cfString);
59     CFIndex maxSize =
60         CFStringGetMaximumSizeForEncoding(length, cfStringEncoding);
61 
62     // If result would exceed LONG_MAX, kCFNotFound is returned
63     if (unlikely(maxSize == kCFNotFound)) {
64         return NULL;
65     }
66 
67     // Account for the null terminator
68     maxSize++;
69 
70     char *buffer = (char *)malloc(maxSize);
71 
72     if (unlikely(buffer == NULL)) {
73         return NULL;
74     }
75 
76     // Copy CFString in requested encoding to buffer
77     Boolean success = CFStringGetCString(cfString, buffer, maxSize, cfStringEncoding);
78 
79     if (!success)
80         FREENULL(buffer);
81     return buffer;
82 }
83 
getPathForFontDescription(CTFontDescriptorRef fontDescriptor)84 char* getPathForFontDescription(CTFontDescriptorRef fontDescriptor)
85 {
86     CFURLRef url = CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
87     if (url == NULL)
88         return NULL;
89     CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
90     if (path == NULL) {
91         CFRelease(url);
92         return NULL;
93     }
94     char *retPath = CFStringCopyCString(path, kCFStringEncodingUTF8);
95     CFRelease(path);
96     CFRelease(url);
97     return retPath;
98 }
99 
addNewFontToFamily(filter_t * p_filter,CTFontDescriptorRef iter,char * path,vlc_family_t * p_family)100 void addNewFontToFamily(filter_t *p_filter, CTFontDescriptorRef iter, char *path, vlc_family_t *p_family)
101 {
102     bool b_bold = false;
103     bool b_italic = false;
104     CFDictionaryRef fontTraits = CTFontDescriptorCopyAttribute(iter, kCTFontTraitsAttribute);
105     CFNumberRef trait = CFDictionaryGetValue(fontTraits, kCTFontWeightTrait);
106     float traitValue = 0.;
107     CFNumberGetValue(trait, kCFNumberFloatType, &traitValue);
108     b_bold = traitValue > 0.23;
109     trait = CFDictionaryGetValue(fontTraits, kCTFontSlantTrait);
110     traitValue = 0.;
111     CFNumberGetValue(trait, kCFNumberFloatType, &traitValue);
112     b_italic = traitValue > 0.03;
113 
114 #ifndef NDEBUG
115     msg_Dbg(p_filter, "New font: bold %i italic %i path '%s'", b_bold, b_italic, path);
116 #else
117     VLC_UNUSED(p_filter);
118 #endif
119     NewFont(path, 0, b_bold, b_italic, p_family);
120 
121     CFRelease(fontTraits);
122 }
123 
CoreText_GetFamily(filter_t * p_filter,const char * psz_family)124 const vlc_family_t *CoreText_GetFamily(filter_t *p_filter, const char *psz_family)
125 {
126     filter_sys_t *p_sys = p_filter->p_sys;
127 
128     if (unlikely(psz_family == NULL)) {
129         return NULL;
130     }
131 
132     char *psz_lc = ToLower(psz_family);
133     if (unlikely(!psz_lc)) {
134         return NULL;
135     }
136 
137     /* let's double check if we have parsed this family already */
138     vlc_family_t *p_family = vlc_dictionary_value_for_key(&p_sys->family_map, psz_lc);
139     if (p_family) {
140         free(psz_lc);
141         return p_family;
142     }
143 
144     CTFontCollectionRef coreTextFontCollection = NULL;
145     CFArrayRef matchedFontDescriptions = NULL;
146 
147     /* we search for family name, display name and name to find them all */
148     const size_t numberOfAttributes = 3;
149     CTFontDescriptorRef coreTextFontDescriptors[numberOfAttributes];
150     CFMutableDictionaryRef coreTextAttributes[numberOfAttributes];
151     CFStringRef attributeNames[numberOfAttributes] = {
152         kCTFontFamilyNameAttribute,
153         kCTFontDisplayNameAttribute,
154         kCTFontNameAttribute,
155     };
156 
157 #ifndef NDEBUG
158     msg_Dbg(p_filter, "Creating new family for '%s'", psz_family);
159 #endif
160 
161     CFStringRef familyName = CFStringCreateWithCString(kCFAllocatorDefault,
162                                                        psz_family,
163                                                        kCFStringEncodingUTF8);
164     for (size_t x = 0; x < numberOfAttributes; x++) {
165         coreTextAttributes[x] = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
166         CFDictionaryAddValue(coreTextAttributes[x], attributeNames[x], familyName);
167         coreTextFontDescriptors[x] = CTFontDescriptorCreateWithAttributes(coreTextAttributes[x]);
168     }
169 
170     CFArrayRef coreTextFontDescriptorsArray = CFArrayCreate(kCFAllocatorDefault,
171                                                             (const void **)&coreTextFontDescriptors,
172                                                             numberOfAttributes, NULL);
173 
174     coreTextFontCollection = CTFontCollectionCreateWithFontDescriptors(coreTextFontDescriptorsArray, 0);
175     if (coreTextFontCollection == NULL) {
176         msg_Warn(p_filter,"CTFontCollectionCreateWithFontDescriptors (1) failed!");
177         goto end;
178     }
179 
180     matchedFontDescriptions = CTFontCollectionCreateMatchingFontDescriptors(coreTextFontCollection);
181     if (matchedFontDescriptions == NULL) {
182         msg_Warn(p_filter, "CTFontCollectionCreateMatchingFontDescriptors (2) failed!");
183         goto end;
184     }
185 
186     CFIndex numberOfFoundFontDescriptions = CFArrayGetCount(matchedFontDescriptions);
187 
188     char *path = NULL;
189 
190     /* create a new family object */
191     p_family = NewFamily(p_filter, psz_lc, &p_sys->p_families, &p_sys->family_map, psz_lc);
192     if (unlikely(!p_family)) {
193         goto end;
194     }
195 
196     for (CFIndex i = 0; i < numberOfFoundFontDescriptions; i++) {
197         CTFontDescriptorRef iter = CFArrayGetValueAtIndex(matchedFontDescriptions, i);
198         path = getPathForFontDescription(iter);
199 
200         /* check if the path is empty, which can happen in rare circumstances */
201         if (path == NULL || *path == '\0') {
202             FREENULL(path);
203             continue;
204         }
205 
206         addNewFontToFamily(p_filter, iter, path, p_family);
207     }
208 
209 end:
210     if (matchedFontDescriptions != NULL) {
211         CFRelease(matchedFontDescriptions);
212     }
213     if (coreTextFontCollection != NULL) {
214         CFRelease(coreTextFontCollection);
215     }
216 
217     for (size_t x = 0; x < numberOfAttributes; x++) {
218         CFRelease(coreTextAttributes[x]);
219         CFRelease(coreTextFontDescriptors[x]);
220     }
221 
222     CFRelease(coreTextFontDescriptorsArray);
223     CFRelease(familyName);
224     free(psz_lc);
225 
226     return p_family;
227 }
228 
CoreText_GetFallbacks(filter_t * p_filter,const char * psz_family,uni_char_t codepoint)229 vlc_family_t *CoreText_GetFallbacks(filter_t *p_filter, const char *psz_family, uni_char_t codepoint)
230 {
231     filter_sys_t *p_sys = p_filter->p_sys;
232     if (unlikely(psz_family == NULL)) {
233         return NULL;
234     }
235 
236     vlc_family_t *p_family = NULL;
237     CFStringRef postScriptFallbackFontname = NULL;
238     CTFontDescriptorRef fallbackFontDescriptor = NULL;
239     char *psz_lc_fallback = NULL;
240     char *psz_fontPath = NULL;
241 
242     CFStringRef familyName = CFStringCreateWithCString(kCFAllocatorDefault,
243                                                        psz_family,
244                                                        kCFStringEncodingUTF8);
245     CTFontRef font = CTFontCreateWithName(familyName, 0, NULL);
246     uint32_t littleEndianCodePoint = OSSwapHostToLittleInt32(codepoint);
247     CFStringRef codepointString = CFStringCreateWithBytes(kCFAllocatorDefault,
248                                                           (const UInt8 *)&littleEndianCodePoint,
249                                                           sizeof(littleEndianCodePoint),
250                                                           kCFStringEncodingUTF32LE,
251                                                           false);
252     CTFontRef fallbackFont = CTFontCreateForString(font, codepointString, CFRangeMake(0,1));
253     CFStringRef fallbackFontFamilyName = CTFontCopyFamilyName(fallbackFont);
254 
255     /* create a new family object */
256     char *psz_fallbackFamilyName = CFStringCopyCString(fallbackFontFamilyName, kCFStringEncodingUTF8);
257     if (psz_fallbackFamilyName == NULL) {
258         msg_Warn(p_filter, "Failed to convert font family name CFString to C string");
259         goto done;
260     }
261 #ifndef NDEBUG
262     msg_Dbg(p_filter, "Will deploy fallback font '%s'", psz_fallbackFamilyName);
263 #endif
264 
265     psz_lc_fallback = ToLower(psz_fallbackFamilyName);
266 
267     p_family = vlc_dictionary_value_for_key(&p_sys->family_map, psz_lc_fallback);
268     if (p_family) {
269         goto done;
270     }
271 
272     p_family = NewFamily(p_filter, psz_lc_fallback, &p_sys->p_families, &p_sys->family_map, psz_lc_fallback);
273     if (unlikely(!p_family)) {
274         goto done;
275     }
276 
277     postScriptFallbackFontname = CTFontCopyPostScriptName(fallbackFont);
278     fallbackFontDescriptor = CTFontDescriptorCreateWithNameAndSize(postScriptFallbackFontname, 0.);
279     psz_fontPath = getPathForFontDescription(fallbackFontDescriptor);
280 
281     /* check if the path is empty, which can happen in rare circumstances */
282     if (psz_fontPath == NULL || *psz_fontPath == '\0') {
283         goto done;
284     }
285 
286     addNewFontToFamily(p_filter, fallbackFontDescriptor, strdup(psz_fontPath), p_family);
287 
288 done:
289     CFRelease(familyName);
290     CFRelease(font);
291     CFRelease(codepointString);
292     CFRelease(fallbackFont);
293     CFRelease(fallbackFontFamilyName);
294     free(psz_fallbackFamilyName);
295     free(psz_lc_fallback);
296     free(psz_fontPath);
297     if (postScriptFallbackFontname != NULL)
298         CFRelease(postScriptFallbackFontname);
299     if (fallbackFontDescriptor != NULL)
300         CFRelease(fallbackFontDescriptor);
301     return p_family;
302 }
303