]> granicus.if.org Git - libass/blob - libass/ass_coretext.c
fontselect: simplify get_fallback signature
[libass] / libass / ass_coretext.c
1 /*
2  * Copyright (C) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
3  *
4  * This file is part of libass.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "config.h"
20
21 #include <CoreFoundation/CoreFoundation.h>
22 #include <CoreText/CoreText.h>
23
24 #include "ass_coretext.h"
25
26 #define CT_FONTS_EAGER_LOAD 0
27 #define CT_FONTS_LAZY_LOAD  !CT_FONTS_EAGER_LOAD
28
29 static char *cfstr2buf(CFStringRef string)
30 {
31     const int encoding = kCFStringEncodingUTF8;
32     const char *buf_ptr = CFStringGetCStringPtr(string, encoding);
33     if (buf_ptr) {
34         return strdup(buf_ptr);
35     } else {
36         size_t len = CFStringGetLength(string);
37         CFIndex buf_len = CFStringGetMaximumSizeForEncoding(len, encoding);
38         char *buf = malloc(buf_len);
39         CFStringGetCString(string, buf, buf_len, encoding);
40         return buf;
41     }
42 }
43
44 static void destroy_font(void *priv)
45 {
46     CFCharacterSetRef set = priv;
47     CFRelease(set);
48 }
49
50 static int check_glyph(void *priv, uint32_t code)
51 {
52     CFCharacterSetRef set = priv;
53
54     if (!set)
55         return 1;
56
57     if (code == 0)
58         return 1;
59
60     return CFCharacterSetIsLongCharacterMember(set, code);
61 }
62
63 static char *get_font_file(CTFontDescriptorRef fontd)
64 {
65     CFURLRef url = CTFontDescriptorCopyAttribute(fontd, kCTFontURLAttribute);
66     CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
67     char *buffer = cfstr2buf(path);
68     CFRelease(path);
69     CFRelease(url);
70     return buffer;
71 }
72
73 static void get_name(CTFontDescriptorRef fontd, CFStringRef attr,
74                      char **array, int *idx)
75 {
76
77     CFStringRef name = CTFontDescriptorCopyAttribute(fontd, attr);
78     if (name) {
79         array[*idx] = cfstr2buf(name);
80         CFRelease(name);
81         *idx += 1;
82     }
83 }
84
85 static void get_trait(CFDictionaryRef traits, CFStringRef attribute,
86                       float *trait)
87 {
88     CFNumberRef cftrait = CFDictionaryGetValue(traits, attribute);
89     if (!CFNumberGetValue(cftrait, kCFNumberFloatType, trait))
90         *trait = 0.0;
91 }
92
93 static void get_font_traits(CTFontDescriptorRef fontd,
94                             ASS_FontProviderMetaData *meta)
95 {
96     float weight, slant, width;
97
98     CFDictionaryRef traits =
99         CTFontDescriptorCopyAttribute(fontd, kCTFontTraitsAttribute);
100
101     get_trait(traits, kCTFontWeightTrait, &weight);
102     get_trait(traits, kCTFontSlantTrait,  &slant);
103     get_trait(traits, kCTFontWidthTrait,  &width);
104
105     CFRelease(traits);
106
107     // Printed all of my system fonts (see if'deffed code below). Here is how
108     // CoreText 'normalized' weights maps to CSS/libass:
109
110     // opentype:   0   100   200   300   400   500   600   700   800   900
111     // css:                 LIGHT        REG   MED  SBOLD BOLD  BLACK  EXTRABL
112     // libass:                   LIGHT  MEDIUM            BOLD
113     // coretext:            -0.4         0.0   0.23  0.3   0.4   0.62
114
115     if (weight >= 0.62)
116         meta->weight = 800;
117     else if (weight >= 0.4)
118         meta->weight = 700;
119     else if (weight >= 0.3)
120         meta->weight = 600;
121     else if (weight >= 0.23)
122         meta->weight = 500;
123     else if (weight >= -0.4)
124         meta->weight = 400;
125     else
126         meta->weight = 200;
127
128     if (slant > 0.03)
129         meta->slant  = FONT_SLANT_ITALIC;
130     else
131         meta->slant  = FONT_SLANT_NONE;
132
133     if (width <= -0.2)
134         meta->width = FONT_WIDTH_CONDENSED;
135     else if (width >= 0.2)
136         meta->width = FONT_WIDTH_EXPANDED;
137     else
138         meta->width  = FONT_WIDTH_NORMAL;
139
140 #if 0
141     char *name[1];
142     int idx = 0;
143     get_name(fontd, kCTFontDisplayNameAttribute, name, &idx);
144     char *file = get_font_file(fontd);
145     printf(
146        "Font traits for: %-40s [%-50s] "
147        "<slant: %f, %03d>, <weight: (%f, %03d)>, <width: %f, %03d>\n",
148        name[0], file,
149        slant, meta->slant, weight, meta->weight, width, meta->width);
150     free(name[0]);
151     free(file);
152 #endif
153 }
154
155 static void process_descriptors(ASS_FontProvider *provider, CFArrayRef fontsd)
156 {
157     ASS_FontProviderMetaData meta;
158     char *families[1];
159     char *identifiers[1];
160     char *fullnames[1];
161
162     if (!fontsd)
163         return;
164
165     for (int i = 0; i < CFArrayGetCount(fontsd); i++) {
166         CTFontDescriptorRef fontd = CFArrayGetValueAtIndex(fontsd, i);
167         int index = -1;
168
169         char *path = get_font_file(fontd);
170         if (strcmp("", path) == 0) {
171             // skip the font if the URL field in the font descriptor is empty
172             free(path);
173             continue;
174         }
175
176         memset(&meta, 0, sizeof(meta));
177         get_font_traits(fontd, &meta);
178
179         get_name(fontd, kCTFontFamilyNameAttribute, families, &meta.n_family);
180         meta.families = families;
181
182         int zero = 0;
183         get_name(fontd, kCTFontNameAttribute, identifiers, &zero);
184         get_name(fontd, kCTFontDisplayNameAttribute, fullnames, &meta.n_fullname);
185         meta.fullnames = fullnames;
186
187         CFCharacterSetRef chset =
188             CTFontDescriptorCopyAttribute(fontd, kCTFontCharacterSetAttribute);
189         ass_font_provider_add_font(provider, &meta, path, index,
190                                    identifiers[0], (void*)chset);
191
192         for (int j = 0; j < meta.n_family; j++)
193             free(meta.families[j]);
194
195         for (int j = 0; j < meta.n_fullname; j++)
196             free(meta.fullnames[j]);
197
198         free(identifiers[0]);
199
200         free(path);
201     }
202 }
203
204 #if CT_FONTS_EAGER_LOAD
205 static void scan_fonts(ASS_Library *lib, ASS_FontProvider *provider)
206 {
207
208     CTFontCollectionRef coll = CTFontCollectionCreateFromAvailableFonts(NULL);
209     CFArrayRef fontsd = CTFontCollectionCreateMatchingFontDescriptors(coll);
210
211     process_descriptors(provider, fontsd);
212
213     CFRelease(fontsd);
214     CFRelease(coll);
215 }
216 #endif
217
218 #if CT_FONTS_LAZY_LOAD
219 static void match_fonts(ASS_Library *lib, ASS_FontProvider *provider,
220                         char *name)
221 {
222     const size_t attributes_n = 3;
223     CTFontDescriptorRef ctdescrs[attributes_n];
224     CFMutableDictionaryRef cfattrs[attributes_n];
225     CFStringRef attributes[attributes_n] = {
226         kCTFontFamilyNameAttribute,
227         kCTFontDisplayNameAttribute,
228         kCTFontNameAttribute,
229     };
230
231     CFStringRef cfname =
232         CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
233
234     for (int i = 0; i < attributes_n; i++) {
235         cfattrs[i] = CFDictionaryCreateMutable(NULL, 0, 0, 0);
236         CFDictionaryAddValue(cfattrs[i], attributes[i], cfname);
237         ctdescrs[i] = CTFontDescriptorCreateWithAttributes(cfattrs[i]);
238     }
239
240     CFArrayRef descriptors =
241         CFArrayCreate(NULL, (const void **)&ctdescrs, attributes_n, NULL);
242
243     CTFontCollectionRef ctcoll =
244         CTFontCollectionCreateWithFontDescriptors(descriptors, 0);
245
246     CFArrayRef fontsd =
247         CTFontCollectionCreateMatchingFontDescriptors(ctcoll);
248
249     process_descriptors(provider, fontsd);
250
251     if (fontsd)
252         CFRelease(fontsd);
253     CFRelease(ctcoll);
254
255     for (int i = 0; i < attributes_n; i++) {
256         CFRelease(cfattrs[i]);
257         CFRelease(ctdescrs[i]);
258     }
259
260     CFRelease(descriptors);
261     CFRelease(cfname);
262 }
263 #endif
264
265 static char *get_fallback(void *priv, const char *family, uint32_t codepoint)
266 {
267     char *failed = family;
268     CFStringRef name = CFStringCreateWithBytes(
269         0, (UInt8 *)failed, sizeof(failed), kCFStringEncodingUTF8, false);
270     CTFontRef font = CTFontCreateWithName(name, 0, NULL);
271     uint32_t codepointle = OSSwapHostToLittleInt32(codepoint);
272     CFStringRef r = CFStringCreateWithBytes(
273         0, (UInt8*)&codepointle, sizeof(codepointle),
274         kCFStringEncodingUTF32LE, false);
275     CTFontRef fb = CTFontCreateForString(font, r, CFRangeMake(0, 1));
276     CFStringRef cffamily = CTFontCopyFamilyName(fb);
277     char *res_family = cfstr2buf(cffamily);
278
279     CFRelease(name);
280     CFRelease(font);
281     CFRelease(r);
282     CFRelease(fb);
283     CFRelease(cffamily);
284
285     return res_family;
286 }
287
288 static ASS_FontProviderFuncs coretext_callbacks = {
289     .check_glyph        = check_glyph,
290     .destroy_font       = destroy_font,
291 #if !CT_FONTS_EAGER_LOAD
292     .match_fonts        = match_fonts,
293 #endif
294     .get_fallback       = get_fallback,
295 };
296
297 ASS_FontProvider *
298 ass_coretext_add_provider(ASS_Library *lib, ASS_FontSelector *selector,
299                           const char *config)
300 {
301     ASS_FontProvider *provider =
302         ass_font_provider_new(selector, &coretext_callbacks, NULL);
303
304 #if CT_FONTS_EAGER_LOAD
305     scan_fonts(lib, provider);
306 #endif
307
308     return provider;
309 }