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