core_text/
font.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10#![allow(non_upper_case_globals)]
11
12use crate::font_descriptor;
13use crate::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef, CTFontOrientation};
14use crate::font_descriptor::{
15    CTFontSymbolicTraits, CTFontTraits, SymbolicTraitAccessors, TraitAccessors,
16};
17use crate::font_manager::create_font_descriptor;
18
19use core_foundation::array::{CFArray, CFArrayRef};
20use core_foundation::base::{CFIndex, CFOptionFlags, CFType, CFTypeID, CFTypeRef, TCFType};
21use core_foundation::data::{CFData, CFDataRef};
22use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
23use core_foundation::number::CFNumber;
24use core_foundation::string::{CFString, CFStringRef, UniChar};
25use core_foundation::url::{CFURLRef, CFURL};
26use core_foundation::{declare_TCFType, impl_CFTypeDescription, impl_TCFType};
27use core_graphics::base::CGFloat;
28use core_graphics::context::CGContext;
29use core_graphics::font::{CGFont, CGGlyph};
30use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize};
31use core_graphics::path::CGPath;
32
33use foreign_types::ForeignType;
34use std::ptr;
35
36type CGContextRef = *mut <CGContext as ForeignType>::CType;
37type CGFontRef = *mut <CGFont as ForeignType>::CType;
38type CGPathRef = *mut <CGPath as ForeignType>::CType;
39
40pub type CTFontUIFontType = u32;
41// kCTFontNoFontType: CTFontUIFontType = -1;
42pub const kCTFontUserFontType: CTFontUIFontType = 0;
43pub const kCTFontUserFixedPitchFontType: CTFontUIFontType = 1;
44pub const kCTFontSystemFontType: CTFontUIFontType = 2;
45pub const kCTFontEmphasizedSystemFontType: CTFontUIFontType = 3;
46pub const kCTFontSmallSystemFontType: CTFontUIFontType = 4;
47pub const kCTFontSmallEmphasizedSystemFontType: CTFontUIFontType = 5;
48pub const kCTFontMiniSystemFontType: CTFontUIFontType = 6;
49pub const kCTFontMiniEmphasizedSystemFontType: CTFontUIFontType = 7;
50pub const kCTFontViewsFontType: CTFontUIFontType = 8;
51pub const kCTFontApplicationFontType: CTFontUIFontType = 9;
52pub const kCTFontLabelFontType: CTFontUIFontType = 10;
53pub const kCTFontMenuTitleFontType: CTFontUIFontType = 11;
54pub const kCTFontMenuItemFontType: CTFontUIFontType = 12;
55pub const kCTFontMenuItemMarkFontType: CTFontUIFontType = 13;
56pub const kCTFontMenuItemCmdKeyFontType: CTFontUIFontType = 14;
57pub const kCTFontWindowTitleFontType: CTFontUIFontType = 15;
58pub const kCTFontPushButtonFontType: CTFontUIFontType = 16;
59pub const kCTFontUtilityWindowTitleFontType: CTFontUIFontType = 17;
60pub const kCTFontAlertHeaderFontType: CTFontUIFontType = 18;
61pub const kCTFontSystemDetailFontType: CTFontUIFontType = 19;
62pub const kCTFontEmphasizedSystemDetailFontType: CTFontUIFontType = 20;
63pub const kCTFontToolbarFontType: CTFontUIFontType = 21;
64pub const kCTFontSmallToolbarFontType: CTFontUIFontType = 22;
65pub const kCTFontMessageFontType: CTFontUIFontType = 23;
66pub const kCTFontPaletteFontType: CTFontUIFontType = 24;
67pub const kCTFontToolTipFontType: CTFontUIFontType = 25;
68pub const kCTFontControlContentFontType: CTFontUIFontType = 26;
69
70pub type CTFontTableTag = u32;
71// TODO: create bindings for enum with 'chars' values
72
73pub type CTFontTableOptions = u32;
74pub const kCTFontTableOptionsNoOptions: CTFontTableOptions = 0;
75pub const kCTFontTableOptionsExcludeSynthetic: CTFontTableOptions = 1 << 0;
76
77pub type CTFontOptions = CFOptionFlags;
78pub const kCTFontOptionsDefault: CTFontOptions = 0;
79pub const kCTFontOptionsPreventAutoActivation: CTFontOptions = 1 << 0;
80pub const kCTFontOptionsPreferSystemFont: CTFontOptions = 1 << 2;
81
82pub enum CTFontNameSpecifier {
83    Copyright,
84    Family,
85    SubFamily,
86    Style,
87    Unique,
88    Full,
89    Version,
90    PostScript,
91    Trademark,
92    Manufacturer,
93    Designer,
94    Description,
95    VendorURL,
96    DesignerURL,
97    License,
98    LicenseURL,
99    SampleText,
100    PostScriptCID,
101}
102
103impl From<CTFontNameSpecifier> for CFStringRef {
104    fn from(val: CTFontNameSpecifier) -> Self {
105        unsafe {
106            match val {
107                CTFontNameSpecifier::Copyright => kCTFontCopyrightNameKey,
108                CTFontNameSpecifier::Family => kCTFontFamilyNameKey,
109                CTFontNameSpecifier::SubFamily => kCTFontSubFamilyNameKey,
110                CTFontNameSpecifier::Style => kCTFontStyleNameKey,
111                CTFontNameSpecifier::Unique => kCTFontUniqueNameKey,
112                CTFontNameSpecifier::Full => kCTFontFullNameKey,
113                CTFontNameSpecifier::Version => kCTFontVersionNameKey,
114                CTFontNameSpecifier::PostScript => kCTFontPostScriptNameKey,
115                CTFontNameSpecifier::Trademark => kCTFontTrademarkNameKey,
116                CTFontNameSpecifier::Manufacturer => kCTFontManufacturerNameKey,
117                CTFontNameSpecifier::Designer => kCTFontDesignerNameKey,
118                CTFontNameSpecifier::Description => kCTFontDescriptionNameKey,
119                CTFontNameSpecifier::VendorURL => kCTFontVendorURLNameKey,
120                CTFontNameSpecifier::DesignerURL => kCTFontDesignerURLNameKey,
121                CTFontNameSpecifier::License => kCTFontLicenseNameKey,
122                CTFontNameSpecifier::LicenseURL => kCTFontLicenseURLNameKey,
123                CTFontNameSpecifier::SampleText => kCTFontSampleTextNameKey,
124                CTFontNameSpecifier::PostScriptCID => kCTFontPostScriptCIDNameKey,
125            }
126        }
127    }
128}
129
130#[repr(C)]
131pub struct __CTFont(core::ffi::c_void);
132
133pub type CTFontRef = *const __CTFont;
134
135declare_TCFType! {
136    CTFont, CTFontRef
137}
138impl_TCFType!(CTFont, CTFontRef, CTFontGetTypeID);
139impl_CFTypeDescription!(CTFont);
140
141unsafe impl Send for CTFont {}
142unsafe impl Sync for CTFont {}
143
144pub fn new_from_CGFont(cgfont: &CGFont, pt_size: f64) -> CTFont {
145    unsafe {
146        let font_ref = CTFontCreateWithGraphicsFont(
147            cgfont.as_ptr() as *mut _,
148            pt_size as CGFloat,
149            ptr::null(),
150            ptr::null(),
151        );
152        CTFont::wrap_under_create_rule(font_ref)
153    }
154}
155
156pub fn new_from_CGFont_with_variations(
157    cgfont: &CGFont,
158    pt_size: f64,
159    variations: &CFDictionary<CFString, CFNumber>,
160) -> CTFont {
161    unsafe {
162        let font_desc = font_descriptor::new_from_variations(variations);
163        let font_ref = CTFontCreateWithGraphicsFont(
164            cgfont.as_ptr() as *mut _,
165            pt_size as CGFloat,
166            ptr::null(),
167            font_desc.as_concrete_TypeRef(),
168        );
169        CTFont::wrap_under_create_rule(font_ref)
170    }
171}
172
173pub fn new_from_descriptor(desc: &CTFontDescriptor, pt_size: f64) -> CTFont {
174    unsafe {
175        let font_ref = CTFontCreateWithFontDescriptor(
176            desc.as_concrete_TypeRef(),
177            pt_size as CGFloat,
178            ptr::null(),
179        );
180        CTFont::wrap_under_create_rule(font_ref)
181    }
182}
183
184pub fn new_from_descriptor_and_options(
185    desc: &CTFontDescriptor,
186    pt_size: f64,
187    options: CTFontOptions,
188) -> CTFont {
189    unsafe {
190        let font_ref = CTFontCreateWithFontDescriptorAndOptions(
191            desc.as_concrete_TypeRef(),
192            pt_size as CGFloat,
193            ptr::null(),
194            options,
195        );
196        CTFont::wrap_under_create_rule(font_ref)
197    }
198}
199
200pub fn new_from_buffer(buffer: &[u8]) -> Result<CTFont, ()> {
201    let ct_font_descriptor = create_font_descriptor(buffer)?;
202    Ok(new_from_descriptor(&ct_font_descriptor, 16.0))
203}
204
205pub fn new_from_name(name: &str, pt_size: f64) -> Result<CTFont, ()> {
206    unsafe {
207        let name: CFString = name.parse().unwrap();
208        let font_ref =
209            CTFontCreateWithName(name.as_concrete_TypeRef(), pt_size as CGFloat, ptr::null());
210        if font_ref.is_null() {
211            Err(())
212        } else {
213            Ok(CTFont::wrap_under_create_rule(font_ref))
214        }
215    }
216}
217
218pub fn new_from_name_and_options(
219    name: &str,
220    pt_size: f64,
221    options: CTFontOptions,
222) -> Result<CTFont, ()> {
223    unsafe {
224        let name: CFString = name.parse().unwrap();
225        let font_ref = CTFontCreateWithNameAndOptions(
226            name.as_concrete_TypeRef(),
227            pt_size as CGFloat,
228            ptr::null(),
229            options,
230        );
231        if font_ref.is_null() {
232            Err(())
233        } else {
234            Ok(CTFont::wrap_under_create_rule(font_ref))
235        }
236    }
237}
238
239pub fn new_ui_font_for_language(
240    ui_type: CTFontUIFontType,
241    size: f64,
242    language: Option<CFString>,
243) -> CTFont {
244    unsafe {
245        let font_ref = CTFontCreateUIFontForLanguage(
246            ui_type,
247            size as CGFloat,
248            language
249                .as_ref()
250                .map(|x| x.as_concrete_TypeRef())
251                .unwrap_or(std::ptr::null()),
252        );
253        if font_ref.is_null() {
254            // CTFontCreateUIFontForLanguage can fail, but is unlikely to do so during
255            // normal usage (if you pass a bad ui_type it will). To make things more
256            // convenient, just panic if it fails.
257            panic!();
258        } else {
259            CTFont::wrap_under_create_rule(font_ref)
260        }
261    }
262}
263
264impl CTFont {
265    // Properties
266    pub fn symbolic_traits(&self) -> CTFontSymbolicTraits {
267        unsafe { CTFontGetSymbolicTraits(self.0) }
268    }
269}
270
271impl CTFont {
272    // Creation methods
273    pub fn copy_to_CGFont(&self) -> CGFont {
274        unsafe {
275            let cgfont_ref = CTFontCopyGraphicsFont(self.0, ptr::null_mut());
276            CGFont::from_ptr(cgfont_ref as *mut _)
277        }
278    }
279
280    pub fn copy_descriptor(&self) -> CTFontDescriptor {
281        unsafe {
282            let desc = CTFontCopyFontDescriptor(self.0);
283            CTFontDescriptor::wrap_under_create_rule(desc)
284        }
285    }
286
287    pub fn clone_with_font_size(&self, size: f64) -> CTFont {
288        unsafe {
289            let font_ref =
290                CTFontCreateCopyWithAttributes(self.0, size as CGFloat, ptr::null(), ptr::null());
291            CTFont::wrap_under_create_rule(font_ref)
292        }
293    }
294
295    pub fn clone_with_symbolic_traits(
296        &self,
297        trait_value: CTFontSymbolicTraits,
298        trait_mask: CTFontSymbolicTraits,
299    ) -> Option<CTFont> {
300        unsafe {
301            let font_ref = CTFontCreateCopyWithSymbolicTraits(
302                self.0,
303                0.0,
304                ptr::null(),
305                trait_value,
306                trait_mask,
307            );
308            if font_ref.is_null() {
309                None
310            } else {
311                Some(CTFont::wrap_under_create_rule(font_ref))
312            }
313        }
314    }
315
316    // Names
317    pub fn get_string_by_name_key(&self, name_key: CTFontNameSpecifier) -> Option<String> {
318        unsafe {
319            let result = CTFontCopyName(self.as_concrete_TypeRef(), name_key.into());
320            if result.is_null() {
321                None
322            } else {
323                Some(CFString::wrap_under_create_rule(result).to_string())
324            }
325        }
326    }
327
328    pub fn family_name(&self) -> String {
329        let value = self.get_string_by_name_key(CTFontNameSpecifier::Family);
330        value.expect("Fonts should always have a family name.")
331    }
332
333    pub fn face_name(&self) -> String {
334        let value = self.get_string_by_name_key(CTFontNameSpecifier::SubFamily);
335        value.expect("Fonts should always have a face name.")
336    }
337
338    pub fn unique_name(&self) -> String {
339        let value = self.get_string_by_name_key(CTFontNameSpecifier::Unique);
340        value.expect("Fonts should always have a unique name.")
341    }
342
343    pub fn postscript_name(&self) -> String {
344        let value = self.get_string_by_name_key(CTFontNameSpecifier::PostScript);
345        value.expect("Fonts should always have a PostScript name.")
346    }
347
348    pub fn display_name(&self) -> String {
349        let value = self.get_string_by_name_key(CTFontNameSpecifier::Full);
350        value.expect("Fonts should always have a PostScript name.")
351    }
352
353    pub fn style_name(&self) -> String {
354        let value = self.get_string_by_name_key(CTFontNameSpecifier::Style);
355        value.expect("Fonts should always have a style name.")
356    }
357
358    pub fn all_traits(&self) -> CTFontTraits {
359        unsafe { CTFontTraits::wrap_under_create_rule(CTFontCopyTraits(self.0)) }
360    }
361
362    // Font metrics
363    pub fn ascent(&self) -> CGFloat {
364        unsafe { CTFontGetAscent(self.0) }
365    }
366
367    pub fn descent(&self) -> CGFloat {
368        unsafe { CTFontGetDescent(self.0) }
369    }
370
371    pub fn underline_thickness(&self) -> CGFloat {
372        unsafe { CTFontGetUnderlineThickness(self.0) }
373    }
374
375    pub fn underline_position(&self) -> CGFloat {
376        unsafe { CTFontGetUnderlinePosition(self.0) }
377    }
378
379    pub fn slant_angle(&self) -> CGFloat {
380        unsafe { CTFontGetSlantAngle(self.0) }
381    }
382
383    pub fn cap_height(&self) -> CGFloat {
384        unsafe { CTFontGetCapHeight(self.0) }
385    }
386
387    pub fn bounding_box(&self) -> CGRect {
388        unsafe { CTFontGetBoundingBox(self.0) }
389    }
390
391    pub fn leading(&self) -> CGFloat {
392        unsafe { CTFontGetLeading(self.0) }
393    }
394
395    pub fn units_per_em(&self) -> core::ffi::c_uint {
396        unsafe { CTFontGetUnitsPerEm(self.0) }
397    }
398
399    pub fn x_height(&self) -> CGFloat {
400        unsafe { CTFontGetXHeight(self.0) }
401    }
402
403    pub fn pt_size(&self) -> CGFloat {
404        unsafe { CTFontGetSize(self.0) }
405    }
406
407    pub fn get_glyph_with_name(&self, glyph_name: &str) -> CGGlyph {
408        let glyph_name = CFString::new(glyph_name);
409        unsafe { CTFontGetGlyphWithName(self.0, glyph_name.as_concrete_TypeRef()) }
410    }
411
412    pub unsafe fn get_glyphs_for_characters(
413        &self,
414        characters: *const UniChar,
415        glyphs: *mut CGGlyph,
416        count: CFIndex,
417    ) -> bool {
418        CTFontGetGlyphsForCharacters(self.0, characters, glyphs, count)
419    }
420
421    pub unsafe fn get_advances_for_glyphs(
422        &self,
423        orientation: CTFontOrientation,
424        glyphs: *const CGGlyph,
425        advances: *mut CGSize,
426        count: CFIndex,
427    ) -> f64 {
428        CTFontGetAdvancesForGlyphs(self.0, orientation, glyphs, advances, count)
429    }
430
431    pub unsafe fn get_vertical_translations_for_glyphs(
432        &self,
433        orientation: CTFontOrientation,
434        glyphs: *const CGGlyph,
435        translations: *mut CGSize,
436        count: CFIndex,
437    ) {
438        CTFontGetVerticalTranslationsForGlyphs(self.0, orientation, glyphs, translations, count)
439    }
440
441    pub fn has_table(&self, tag: u32) -> bool {
442        unsafe { CTFontHasTable(self.0, tag as CTFontTableTag) }
443    }
444
445    pub fn get_font_table(&self, tag: u32) -> Option<CFData> {
446        unsafe {
447            let result = CTFontCopyTable(
448                self.0,
449                tag as CTFontTableTag,
450                kCTFontTableOptionsExcludeSynthetic,
451            );
452            if result.is_null() {
453                None
454            } else {
455                Some(CFData::wrap_under_create_rule(result))
456            }
457        }
458    }
459
460    pub fn get_available_font_tables(&self) -> Option<CFArray<CTFontTableTag>> {
461        unsafe {
462            let result = CTFontCopyAvailableTables(self.0, kCTFontTableOptionsExcludeSynthetic);
463            if result.is_null() {
464                None
465            } else {
466                Some(TCFType::wrap_under_create_rule(result))
467            }
468        }
469    }
470
471    pub fn get_bounding_rects_for_glyphs(
472        &self,
473        orientation: CTFontOrientation,
474        glyphs: &[CGGlyph],
475    ) -> CGRect {
476        unsafe {
477            CTFontGetBoundingRectsForGlyphs(
478                self.as_concrete_TypeRef(),
479                orientation,
480                glyphs.as_ptr(),
481                ptr::null_mut(),
482                glyphs.len() as CFIndex,
483            )
484        }
485    }
486
487    pub fn draw_glyphs(&self, glyphs: &[CGGlyph], positions: &[CGPoint], context: CGContext) {
488        assert_eq!(glyphs.len(), positions.len());
489        unsafe {
490            CTFontDrawGlyphs(
491                self.as_concrete_TypeRef(),
492                glyphs.as_ptr(),
493                positions.as_ptr(),
494                glyphs.len(),
495                context.as_ptr(),
496            )
497        }
498    }
499
500    pub fn get_matrix(&self) -> CGAffineTransform {
501        unsafe { CTFontGetMatrix(self.as_concrete_TypeRef()) }
502    }
503
504    pub fn url(&self) -> Option<CFURL> {
505        unsafe {
506            let result = CTFontCopyAttribute(self.0, kCTFontURLAttribute);
507            if result.is_null() {
508                None
509            } else {
510                Some(CFURL::wrap_under_create_rule(result as CFURLRef))
511            }
512        }
513    }
514
515    pub fn get_variation_axes(&self) -> Option<CFArray<CFDictionary<CFString, CFType>>> {
516        unsafe {
517            let axes = CTFontCopyVariationAxes(self.0);
518            if axes.is_null() {
519                return None;
520            }
521            Some(TCFType::wrap_under_create_rule(axes))
522        }
523    }
524
525    pub fn create_path_for_glyph(
526        &self,
527        glyph: CGGlyph,
528        matrix: &CGAffineTransform,
529    ) -> Result<CGPath, ()> {
530        unsafe {
531            let path = CTFontCreatePathForGlyph(self.0, glyph, matrix);
532            if path.is_null() {
533                Err(())
534            } else {
535                Ok(CGPath::from_ptr(path))
536            }
537        }
538    }
539
540    #[inline]
541    pub fn glyph_count(&self) -> CFIndex {
542        unsafe { CTFontGetGlyphCount(self.0) }
543    }
544}
545
546// Helper methods
547pub fn debug_font_names(font: &CTFont) {
548    fn get_key(font: &CTFont, key: CTFontNameSpecifier) -> String {
549        font.get_string_by_name_key(key).unwrap()
550    }
551
552    println!(
553        "kCTFontFamilyNameKey: {}",
554        get_key(font, CTFontNameSpecifier::Family)
555    );
556    println!(
557        "kCTFontSubFamilyNameKey: {}",
558        get_key(font, CTFontNameSpecifier::SubFamily)
559    );
560    println!(
561        "kCTFontStyleNameKey: {}",
562        get_key(font, CTFontNameSpecifier::Style)
563    );
564    println!(
565        "kCTFontUniqueNameKey: {}",
566        get_key(font, CTFontNameSpecifier::Unique)
567    );
568    println!(
569        "kCTFontFullNameKey: {}",
570        get_key(font, CTFontNameSpecifier::Full)
571    );
572    println!(
573        "kCTFontPostScriptNameKey: {}",
574        get_key(font, CTFontNameSpecifier::PostScript)
575    );
576}
577
578pub fn debug_font_traits(font: &CTFont) {
579    let sym = font.symbolic_traits();
580    println!("kCTFontItalicTrait: {}", sym.is_italic());
581    println!("kCTFontBoldTrait: {}", sym.is_bold());
582    println!("kCTFontExpandedTrait: {}", sym.is_expanded());
583    println!("kCTFontCondensedTrait: {}", sym.is_condensed());
584    println!("kCTFontMonoSpaceTrait: {}", sym.is_monospace());
585
586    let traits = font.all_traits();
587    println!("kCTFontWeightTrait: {}", traits.normalized_weight());
588    println!("kCTFontWidthTrait: {}", traits.normalized_width());
589    //    println!("kCTFontSlantTrait: {}", traits.normalized_slant());
590}
591
592#[cfg(feature = "mountainlion")]
593pub fn cascade_list_for_languages(
594    font: &CTFont,
595    language_pref_list: &CFArray<CFString>,
596) -> CFArray<CTFontDescriptor> {
597    unsafe {
598        let font_collection_ref = CTFontCopyDefaultCascadeListForLanguages(
599            font.as_concrete_TypeRef(),
600            language_pref_list.as_concrete_TypeRef(),
601        );
602        CFArray::wrap_under_create_rule(font_collection_ref)
603    }
604}
605
606#[cfg_attr(feature = "link", link(name = "CoreText", kind = "framework"))]
607extern "C" {
608    /*
609     * CTFont.h
610     */
611
612    /* Name Specifier Constants */
613    static kCTFontCopyrightNameKey: CFStringRef;
614    static kCTFontFamilyNameKey: CFStringRef;
615    static kCTFontSubFamilyNameKey: CFStringRef;
616    static kCTFontStyleNameKey: CFStringRef;
617    static kCTFontUniqueNameKey: CFStringRef;
618    static kCTFontFullNameKey: CFStringRef;
619    static kCTFontVersionNameKey: CFStringRef;
620    static kCTFontPostScriptNameKey: CFStringRef;
621    static kCTFontTrademarkNameKey: CFStringRef;
622    static kCTFontManufacturerNameKey: CFStringRef;
623    static kCTFontDesignerNameKey: CFStringRef;
624    static kCTFontDescriptionNameKey: CFStringRef;
625    static kCTFontVendorURLNameKey: CFStringRef;
626    static kCTFontDesignerURLNameKey: CFStringRef;
627    static kCTFontLicenseNameKey: CFStringRef;
628    static kCTFontLicenseURLNameKey: CFStringRef;
629    static kCTFontSampleTextNameKey: CFStringRef;
630    static kCTFontPostScriptCIDNameKey: CFStringRef;
631
632    #[cfg(test)]
633    static kCTFontVariationAxisIdentifierKey: CFStringRef;
634    //static kCTFontVariationAxisMinimumValueKey: CFStringRef;
635    #[cfg(test)]
636    static kCTFontVariationAxisMaximumValueKey: CFStringRef;
637    //static kCTFontVariationAxisDefaultValueKey: CFStringRef;
638    //static kCTFontVariationAxisNameKey: CFStringRef;
639
640    //static kCTFontFeatureTypeIdentifierKey: CFStringRef;
641    //static kCTFontFeatureTypeNameKey: CFStringRef;
642    //static kCTFontFeatureTypeExclusiveKey: CFStringRef;
643    //static kCTFontFeatureTypeSelectorsKey: CFStringRef;
644    //static kCTFontFeatureSelectorIdentifierKey: CFStringRef;
645    //static kCTFontFeatureSelectorNameKey: CFStringRef;
646    //static kCTFontFeatureSelectorDefaultKey: CFStringRef;
647    //static kCTFontFeatureSelectorSettingKey: CFStringRef;
648
649    static kCTFontURLAttribute: CFStringRef;
650
651    // N.B. Unlike most Cocoa bindings, this extern block is organized according
652    // to the documentation's Functions By Task listing, because there so many functions.
653
654    /* Creating Fonts */
655    fn CTFontCreateWithName(
656        name: CFStringRef,
657        size: CGFloat,
658        matrix: *const CGAffineTransform,
659    ) -> CTFontRef;
660    fn CTFontCreateWithNameAndOptions(
661        name: CFStringRef,
662        size: CGFloat,
663        matrix: *const CGAffineTransform,
664        options: CTFontOptions,
665    ) -> CTFontRef;
666    fn CTFontCreateWithFontDescriptor(
667        descriptor: CTFontDescriptorRef,
668        size: CGFloat,
669        matrix: *const CGAffineTransform,
670    ) -> CTFontRef;
671    fn CTFontCreateWithFontDescriptorAndOptions(
672        descriptor: CTFontDescriptorRef,
673        size: CGFloat,
674        matrix: *const CGAffineTransform,
675        options: CTFontOptions,
676    ) -> CTFontRef;
677    fn CTFontCreateUIFontForLanguage(
678        uiType: CTFontUIFontType,
679        size: CGFloat,
680        language: CFStringRef,
681    ) -> CTFontRef;
682    fn CTFontCreateCopyWithAttributes(
683        font: CTFontRef,
684        size: CGFloat,
685        matrix: *const CGAffineTransform,
686        attributes: CTFontDescriptorRef,
687    ) -> CTFontRef;
688    fn CTFontCreateCopyWithSymbolicTraits(
689        font: CTFontRef,
690        size: CGFloat,
691        matrix: *const CGAffineTransform,
692        symTraitValue: CTFontSymbolicTraits,
693        symTraitMask: CTFontSymbolicTraits,
694    ) -> CTFontRef;
695    //fn CTFontCreateCopyWithFamily
696    //fn CTFontCreateForString
697
698    /* Getting Font Data */
699    fn CTFontCopyFontDescriptor(font: CTFontRef) -> CTFontDescriptorRef;
700    fn CTFontCopyAttribute(font: CTFontRef, attribute: CFStringRef) -> CFTypeRef;
701    fn CTFontGetSize(font: CTFontRef) -> CGFloat;
702    fn CTFontGetMatrix(font: CTFontRef) -> CGAffineTransform;
703    fn CTFontGetSymbolicTraits(font: CTFontRef) -> CTFontSymbolicTraits;
704    fn CTFontCopyTraits(font: CTFontRef) -> CFDictionaryRef;
705
706    /* Getting Font Names */
707    //fn CTFontCopyPostScriptName(font: CTFontRef) -> CFStringRef;
708    //fn CTFontCopyFamilyName(font: CTFontRef) -> CFStringRef;
709    //fn CTFontCopyFullName(font: CTFontRef) -> CFStringRef;
710    //fn CTFontCopyDisplayName(font: CTFontRef) -> CFStringRef;
711    fn CTFontCopyName(font: CTFontRef, nameKey: CFStringRef) -> CFStringRef;
712    //fn CTFontCopyLocalizedName(font: CTFontRef, nameKey: CFStringRef,
713    //                           language: *CFStringRef) -> CFStringRef;
714    #[cfg(feature = "mountainlion")]
715    fn CTFontCopyDefaultCascadeListForLanguages(
716        font: CTFontRef,
717        languagePrefList: CFArrayRef,
718    ) -> CFArrayRef;
719
720    /* Working With Encoding */
721    //fn CTFontCopyCharacterSet
722    //fn CTFontGetStringEncoding
723    //fn CTFontCopySupportedLanguages
724
725    /* Getting Font Metrics */
726    fn CTFontGetAscent(font: CTFontRef) -> CGFloat;
727    fn CTFontGetDescent(font: CTFontRef) -> CGFloat;
728    fn CTFontGetLeading(font: CTFontRef) -> CGFloat;
729    fn CTFontGetUnitsPerEm(font: CTFontRef) -> core::ffi::c_uint;
730    fn CTFontGetGlyphCount(font: CTFontRef) -> CFIndex;
731    fn CTFontGetBoundingBox(font: CTFontRef) -> CGRect;
732    fn CTFontGetUnderlinePosition(font: CTFontRef) -> CGFloat;
733    fn CTFontGetUnderlineThickness(font: CTFontRef) -> CGFloat;
734    fn CTFontGetSlantAngle(font: CTFontRef) -> CGFloat;
735    fn CTFontGetCapHeight(font: CTFontRef) -> CGFloat;
736    fn CTFontGetXHeight(font: CTFontRef) -> CGFloat;
737
738    /* Getting Glyph Data */
739    fn CTFontCreatePathForGlyph(
740        font: CTFontRef,
741        glyph: CGGlyph,
742        matrix: *const CGAffineTransform,
743    ) -> CGPathRef;
744    fn CTFontGetGlyphWithName(font: CTFontRef, glyphName: CFStringRef) -> CGGlyph;
745    fn CTFontGetBoundingRectsForGlyphs(
746        font: CTFontRef,
747        orientation: CTFontOrientation,
748        glyphs: *const CGGlyph,
749        boundingRects: *mut CGRect,
750        count: CFIndex,
751    ) -> CGRect;
752    fn CTFontGetAdvancesForGlyphs(
753        font: CTFontRef,
754        orientation: CTFontOrientation,
755        glyphs: *const CGGlyph,
756        advances: *mut CGSize,
757        count: CFIndex,
758    ) -> core::ffi::c_double;
759    fn CTFontGetVerticalTranslationsForGlyphs(
760        font: CTFontRef,
761        orientation: CTFontOrientation,
762        glyphs: *const CGGlyph,
763        translations: *mut CGSize,
764        count: CFIndex,
765    );
766
767    /* Working With Font Variations */
768    fn CTFontCopyVariationAxes(font: CTFontRef) -> CFArrayRef;
769    //fn CTFontCopyVariation
770
771    /* Getting Font Features */
772    //fn CTFontCopyFeatures
773    //fn CTFontCopyFeatureSettings
774
775    /* Working with Glyphs */
776    fn CTFontGetGlyphsForCharacters(
777        font: CTFontRef,
778        characters: *const UniChar,
779        glyphs: *mut CGGlyph,
780        count: CFIndex,
781    ) -> bool;
782    fn CTFontDrawGlyphs(
783        font: CTFontRef,
784        glyphs: *const CGGlyph,
785        positions: *const CGPoint,
786        count: usize,
787        context: CGContextRef,
788    );
789    //fn CTFontGetLigatureCaretPositions
790
791    /* Converting Fonts */
792    fn CTFontCopyGraphicsFont(font: CTFontRef, attributes: *mut CTFontDescriptorRef) -> CGFontRef;
793    fn CTFontCreateWithGraphicsFont(
794        graphicsFont: CGFontRef,
795        size: CGFloat,
796        matrix: *const CGAffineTransform,
797        attributes: CTFontDescriptorRef,
798    ) -> CTFontRef;
799    //fn CTFontGetPlatformFont
800    //fn CTFontCreateWithPlatformFont
801    //fn CTFontCreateWithQuickdrawInstance
802
803    /* Getting Font Table Data */
804    fn CTFontCopyAvailableTables(font: CTFontRef, options: CTFontTableOptions) -> CFArrayRef;
805    fn CTFontCopyTable(
806        font: CTFontRef,
807        table: CTFontTableTag,
808        options: CTFontTableOptions,
809    ) -> CFDataRef;
810
811    fn CTFontGetTypeID() -> CFTypeID;
812
813    fn CTFontHasTable(font: CTFontRef, table: CTFontTableTag) -> bool;
814}
815
816#[test]
817fn copy_font() {
818    use std::io::Read;
819    let mut f = std::fs::File::open("/System/Library/Fonts/ZapfDingbats.ttf").unwrap();
820    let mut font_data = Vec::new();
821    f.read_to_end(&mut font_data).unwrap();
822    let desc = crate::font_manager::create_font_descriptor(&font_data).unwrap();
823    let font = new_from_descriptor(&desc, 12.);
824    drop(desc);
825    let desc = font.copy_descriptor();
826    drop(font);
827    let font = new_from_descriptor(&desc, 14.);
828    assert_eq!(font.family_name(), "Zapf Dingbats");
829}
830
831#[cfg(test)]
832fn macos_version() -> (i32, i32, i32) {
833    use std::io::Read;
834
835    // This is the same approach that Firefox uses for detecting versions
836    let file = "/System/Library/CoreServices/SystemVersion.plist";
837    let mut f = std::fs::File::open(file).unwrap();
838    let mut system_version_data = Vec::new();
839    f.read_to_end(&mut system_version_data).unwrap();
840
841    use core_foundation::propertylist;
842    let (list, _) = propertylist::create_with_data(
843        core_foundation::data::CFData::from_buffer(&system_version_data),
844        propertylist::kCFPropertyListImmutable,
845    )
846    .unwrap();
847    let k = unsafe { propertylist::CFPropertyList::wrap_under_create_rule(list) };
848
849    let dict = unsafe {
850        std::mem::transmute::<CFDictionary, CFDictionary<CFType, CFType>>(
851            k.downcast::<CFDictionary>().unwrap(),
852        )
853    };
854
855    let version = dict
856        .find(CFString::new("ProductVersion").as_CFType())
857        .as_ref()
858        .unwrap()
859        .downcast::<CFString>()
860        .unwrap()
861        .to_string();
862
863    match version
864        .split('.')
865        .map(|x| x.parse().unwrap())
866        .collect::<Vec<_>>()[..]
867    {
868        [a, b, c] => (a, b, c),
869        [a, b] => (a, b, 0),
870        _ => panic!(),
871    }
872}
873
874#[test]
875fn copy_system_font() {
876    use crate::*;
877
878    let small = new_ui_font_for_language(kCTFontSystemDetailFontType, 19., None);
879    let big = small.clone_with_font_size(20.);
880
881    // ensure that we end up with different fonts for the different sizes before 10.15
882    if macos_version() < (10, 15, 0) {
883        assert_ne!(big.url(), small.url());
884    } else {
885        assert_eq!(big.url(), small.url());
886    }
887
888    let ps = small.postscript_name();
889    let desc = small.copy_descriptor();
890
891    // check that we can construct a new version by descriptor
892    let ctfont = new_from_descriptor(&desc, 20.);
893    assert_eq!(big.postscript_name(), ctfont.postscript_name());
894
895    // check that we can construct a new version by attributes
896    let attr = desc.attributes();
897    let desc_from_attr = font_descriptor::new_from_attributes(&attr);
898    let font_from_attr = new_from_descriptor(&desc_from_attr, 19.);
899    assert_eq!(font_from_attr.postscript_name(), small.postscript_name());
900
901    // on newer versions of macos we can't construct by name anymore
902    if macos_version() < (10, 13, 0) {
903        let ui_font_by_name = new_from_name(&small.postscript_name(), 19.).unwrap();
904        assert_eq!(ui_font_by_name.postscript_name(), small.postscript_name());
905
906        let ui_font_by_name = new_from_name(&small.postscript_name(), 20.).unwrap();
907        assert_eq!(ui_font_by_name.postscript_name(), small.postscript_name());
908
909        let ui_font_by_name = new_from_name(&big.postscript_name(), 20.).unwrap();
910        assert_eq!(ui_font_by_name.postscript_name(), big.postscript_name());
911    }
912
913    // but we can still construct the CGFont by name
914    let cgfont = CGFont::from_name(&CFString::new(&ps)).unwrap();
915    let cgfont = new_from_CGFont(&cgfont, 0.);
916    println!("{cgfont:?}");
917    let desc = cgfont.copy_descriptor();
918    let matching = unsafe {
919        crate::font_descriptor::CTFontDescriptorCreateMatchingFontDescriptor(
920            desc.as_concrete_TypeRef(),
921            std::ptr::null(),
922        )
923    };
924    let matching = unsafe { CTFontDescriptor::wrap_under_create_rule(matching) };
925
926    println!("{:?}", cgfont.copy_descriptor());
927    assert!(desc
928        .attributes()
929        .find(CFString::from_static_string("NSFontSizeAttribute"))
930        .is_some());
931
932    println!("{matching:?}");
933    println!(
934        "{:?}",
935        matching
936            .attributes()
937            .find(CFString::from_static_string("NSFontSizeAttribute"))
938    );
939
940    assert!(matching
941        .attributes()
942        .find(CFString::from_static_string("NSFontSizeAttribute"))
943        .is_none());
944
945    assert_eq!(small.postscript_name(), cgfont.postscript_name());
946}
947
948// Tests what happens when variations have values not inbetween min and max
949#[test]
950fn out_of_range_variations() {
951    use crate::*;
952    use core_foundation::base::ItemRef;
953
954    let small = new_ui_font_for_language(kCTFontSystemDetailFontType, 19., None);
955
956    let axes = small.get_variation_axes();
957    let version = dbg!(macos_version());
958    if version < (10, 12, 0) {
959        assert!(axes.is_none());
960        return;
961    }
962    let axes = axes.unwrap();
963    let mut vals = Vec::new();
964    dbg!(&axes);
965    for axis in axes.iter() {
966        let tag = axis
967            .find(unsafe { kCTFontVariationAxisIdentifierKey })
968            .unwrap()
969            .downcast::<CFNumber>()
970            .unwrap()
971            .to_i64()
972            .unwrap();
973        let max = axis
974            .find(unsafe { kCTFontVariationAxisMaximumValueKey })
975            .unwrap()
976            .downcast::<CFNumber>()
977            .unwrap()
978            .to_f64()
979            .unwrap();
980        vals.push((CFNumber::from(tag), CFNumber::from(max + 1.)));
981    }
982    let vals_dict = CFDictionary::from_CFType_pairs(&vals);
983    let variation_attribute =
984        unsafe { CFString::wrap_under_get_rule(font_descriptor::kCTFontVariationAttribute) };
985    let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute.clone(), vals_dict)]);
986    let ct_var_font_desc = small
987        .copy_descriptor()
988        .create_copy_with_attributes(attrs_dict.to_untyped())
989        .unwrap();
990    let variation_font = crate::font::new_from_descriptor(&ct_var_font_desc, 19.);
991    let var_desc = variation_font.copy_descriptor();
992    let var_attrs = var_desc.attributes();
993    dbg!(&var_attrs);
994
995    // Handling of attributes greater than max changed between versions
996    let var_attrs = var_attrs.find(variation_attribute);
997    if version >= (14, 0, 0) {
998        check_attrs(0., var_attrs, axes);
999    } else if version >= (12, 0, 0) && version < (13, 0, 0) {
1000        check_attrs(1., var_attrs, axes);
1001    } else if version >= (10, 15, 0) {
1002        assert!(var_attrs.is_none());
1003    } else {
1004        let var_attrs = var_attrs.unwrap().downcast::<CFDictionary>().unwrap();
1005        assert!(var_attrs.is_empty());
1006    }
1007
1008    fn check_attrs(
1009        clamp_diff: f64,
1010        var_attrs: Option<ItemRef<'_, CFType>>,
1011        axes: CFArray<CFDictionary<CFString, CFType>>,
1012    ) {
1013        let var_attrs = var_attrs.unwrap().downcast::<CFDictionary>().unwrap();
1014        assert!(!var_attrs.is_empty());
1015        let var_attrs: CFDictionary<CFType, CFType> = unsafe { std::mem::transmute(var_attrs) };
1016        // attributes greater than max remain
1017        for axis in axes.iter() {
1018            let tag = axis
1019                .find(unsafe { kCTFontVariationAxisIdentifierKey })
1020                .unwrap();
1021            let max = axis
1022                .find(unsafe { kCTFontVariationAxisMaximumValueKey })
1023                .unwrap()
1024                .downcast::<CFNumber>()
1025                .unwrap()
1026                .to_f64()
1027                .unwrap();
1028            let val = var_attrs
1029                .find(tag.clone())
1030                .unwrap()
1031                .downcast::<CFNumber>()
1032                .unwrap()
1033                .to_f64()
1034                .unwrap();
1035
1036            let expected = max + clamp_diff;
1037            assert_eq!(
1038                val, expected,
1039                "axis {tag:?} = {val:?} (expected {expected:?})",
1040            );
1041        }
1042    }
1043}
1044
1045#[test]
1046fn equal_descriptor_different_font() {
1047    use crate::*;
1048
1049    let variation_attribute =
1050        unsafe { CFString::wrap_under_get_rule(font_descriptor::kCTFontVariationAttribute) };
1051    let size_attribute =
1052        unsafe { CFString::wrap_under_get_rule(font_descriptor::kCTFontSizeAttribute) };
1053
1054    let sys_font = new_ui_font_for_language(kCTFontSystemDetailFontType, 19., None);
1055
1056    // but we can still construct the CGFont by name
1057    let create_vars = |desc| {
1058        let vals: Vec<(CFNumber, CFNumber)> =
1059            vec![(CFNumber::from(0x6f70737a), CFNumber::from(17.))];
1060        let vals_dict = CFDictionary::from_CFType_pairs(&vals);
1061        let attrs_dict =
1062            CFDictionary::from_CFType_pairs(&[(variation_attribute.clone(), vals_dict)]);
1063        let size_attrs_dict =
1064            CFDictionary::from_CFType_pairs(&[(size_attribute.clone(), CFNumber::from(120.))]);
1065        dbg!(&desc);
1066        let from_font_desc = new_from_descriptor(&desc, 120.).copy_descriptor();
1067        let resized_font_desc = desc
1068            .create_copy_with_attributes(size_attrs_dict.to_untyped())
1069            .unwrap();
1070        if macos_version() >= (11, 0, 0) {
1071            assert_eq!(from_font_desc, resized_font_desc);
1072        } else {
1073            // we won't have a name if we're using system font desc
1074            if from_font_desc
1075                .attributes()
1076                .find(unsafe { font_descriptor::kCTFontNameAttribute })
1077                .is_none()
1078            {
1079                // it's surprising that desc's are the not equal but the attributes are
1080                assert_ne!(from_font_desc, resized_font_desc);
1081                assert_eq!(
1082                    from_font_desc.attributes().to_untyped(),
1083                    resized_font_desc.attributes().to_untyped()
1084                );
1085            } else if macos_version() >= (10, 13, 0) {
1086                // this is unsurprising
1087                assert_ne!(from_font_desc, resized_font_desc);
1088                assert_ne!(
1089                    from_font_desc.attributes().to_untyped(),
1090                    resized_font_desc.attributes().to_untyped()
1091                );
1092            } else {
1093                assert_ne!(from_font_desc, resized_font_desc);
1094                assert_eq!(
1095                    from_font_desc.attributes().to_untyped(),
1096                    resized_font_desc.attributes().to_untyped()
1097                );
1098            }
1099        }
1100
1101        let from_font_desc = from_font_desc
1102            .create_copy_with_attributes(attrs_dict.to_untyped())
1103            .unwrap();
1104        let resized_font_desc = resized_font_desc
1105            .create_copy_with_attributes(attrs_dict.to_untyped())
1106            .unwrap();
1107        (from_font_desc, resized_font_desc)
1108    };
1109
1110    // setting the variation works properly if we use system font desc
1111    let (from_font_desc, resized_font_desc) = create_vars(sys_font.copy_descriptor());
1112    assert_eq!(from_font_desc, resized_font_desc);
1113    assert!(resized_font_desc
1114        .attributes()
1115        .find(variation_attribute.clone())
1116        .is_some());
1117
1118    // but doesn't if we refer to it by name
1119    let ps = sys_font.postscript_name();
1120    let cgfont = CGFont::from_name(&CFString::new(&ps)).unwrap();
1121    let ctfont = new_from_CGFont(&cgfont, 0.);
1122
1123    let (from_font_desc, resized_font_desc) = create_vars(ctfont.copy_descriptor());
1124    if macos_version() >= (10, 15, 0) {
1125        assert_ne!(from_font_desc, resized_font_desc);
1126    }
1127
1128    if macos_version() >= (10, 13, 0) {
1129        assert!(from_font_desc
1130            .attributes()
1131            .find(variation_attribute.clone())
1132            .is_some());
1133        if macos_version() >= (11, 0, 0) {
1134            assert!(resized_font_desc
1135                .attributes()
1136                .find(variation_attribute)
1137                .is_none());
1138        } else {
1139            assert!(resized_font_desc
1140                .attributes()
1141                .find(variation_attribute)
1142                .is_some());
1143        };
1144    }
1145}
1146
1147#[test]
1148fn system_font_variation() {
1149    use crate::*;
1150
1151    let small = new_ui_font_for_language(kCTFontSystemDetailFontType, 19., None);
1152
1153    // but we can still construct the CGFont by name
1154    let ps = small.postscript_name();
1155    let cgfont = CGFont::from_name(&CFString::new(&ps)).unwrap();
1156    let cgfont = new_from_CGFont(&cgfont, 0.);
1157    let desc = cgfont.copy_descriptor();
1158
1159    let vals: Vec<(CFNumber, CFNumber)> =
1160        vec![(CFNumber::from(0x6f70737a /* opsz */), CFNumber::from(17.))];
1161    let vals_dict = CFDictionary::from_CFType_pairs(&vals);
1162    let variation_attribute =
1163        unsafe { CFString::wrap_under_get_rule(font_descriptor::kCTFontVariationAttribute) };
1164    let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, vals_dict)]);
1165    let ct_var_font_desc = desc
1166        .create_copy_with_attributes(attrs_dict.to_untyped())
1167        .unwrap();
1168    let attrs = ct_var_font_desc.attributes();
1169    let var_attr = attrs.find(CFString::from_static_string("NSCTFontVariationAttribute"));
1170    if macos_version() >= (11, 0, 0) {
1171        // the variation goes away
1172        assert!(var_attr.is_none());
1173    } else {
1174        assert!(var_attr.is_some());
1175    }
1176
1177    dbg!(ct_var_font_desc);
1178}
1179
1180#[test]
1181fn ui_font() {
1182    // pass some garbagey inputs
1183    new_ui_font_for_language(
1184        kCTFontSystemDetailFontType,
1185        10000009.,
1186        Some(CFString::from("Gofld")),
1187    );
1188}