summaryrefslogtreecommitdiffstats
path: root/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs
blob: aa4e301d32f368f8b541040dd63b77e666d8f2b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{ColorF, ColorU, FontKey, FontRenderMode, FontSize, GlyphDimensions};
use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
use core_foundation::data::CFData;
use core_foundation::base::TCFType;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::{CFNumber};
use core_foundation::string::CFString;
use core_foundation::url::{CFURL, kCFURLPOSIXPathStyle};
use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst};
use core_graphics::base::{kCGBitmapByteOrder32Little};
use core_graphics::color_space::CGColorSpace;
use core_graphics::context::CGContext;
use core_graphics::context::{CGBlendMode, CGTextDrawingMode};
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect};
use core_text;
use core_text::font::CTFont;
use core_text::font_descriptor::{CTFontDescriptor, kCTFontDefaultOrientation};
use core_text::font_descriptor::{kCTFontURLAttribute, kCTFontVariationAttribute};
use core_text::font_manager;
use euclid::default::Size2D;
use crate::gamma_lut::{ColorLut, GammaLut};
use crate::rasterizer::{FontInstance, FontTransform, GlyphKey};
use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
use crate::types::FastHashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;

const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32;

pub struct FontContext {
    ct_font_descs: FastHashMap<FontKey, CTFontDescriptor>,
    // Table mapping a sized font key with variations to its instantiated CoreText font.
    ct_fonts: FastHashMap<(FontKey, FontSize, Vec<FontVariation>), CTFont>,
    #[allow(dead_code)]
    graphics_context: GraphicsContext,
    #[allow(dead_code)]
    gamma_lut: GammaLut,
}

// core text is safe to use on multiple threads and non-shareable resources are
// all hidden inside their font context.
unsafe impl Send for FontContext {}

struct GlyphMetrics {
    rasterized_left: i32,
    #[allow(dead_code)]
    rasterized_descent: i32,
    rasterized_ascent: i32,
    rasterized_width: i32,
    rasterized_height: i32,
    advance: f32,
}

// There are a number of different OS prefs that control whether or not
// requesting font smoothing actually results in subpixel AA. This gets even
// murkier in newer macOS versions that deprecate subpixel AA, with the prefs
// potentially interacting and overriding each other. In an attempt to future-
// proof things against any new prefs or interpretation of those prefs in
// future macOS versions, we do a check here to request font smoothing and see
// what result it actually gives us much like Skia does. We need to check for
// each of three potential results and process them in the font backend in
// distinct ways:
// 1) subpixel AA (differing RGB channels) with dilation
// 2) grayscale AA (matching RGB channels) with dilation, a compatibility mode
// 3) grayscale AA without dilation as if font smoothing was not requested
// We can discern between case 1 and the rest by checking if the subpixels differ.
// We can discern between cases 2 and 3 by rendering with and without smoothing
// and comparing the two to determine if there was some dilation.
// This returns the actual FontRenderMode needed to support each case, if any.
fn determine_font_smoothing_mode() -> Option<FontRenderMode> {
    let mut smooth_context = CGContext::create_bitmap_context(
        None,
        12,
        12,
        8,
        12 * 4,
        &CGColorSpace::create_device_rgb(),
        kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
    );
    smooth_context.set_should_smooth_fonts(true);
    smooth_context.set_should_antialias(true);
    smooth_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
    let mut gray_context = CGContext::create_bitmap_context(
        None,
        12,
        12,
        8,
        12 * 4,
        &CGColorSpace::create_device_rgb(),
        kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
    );
    gray_context.set_should_smooth_fonts(false);
    gray_context.set_should_antialias(true);
    gray_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);

    // Autorelease pool for CTFont
    objc::rc::autoreleasepool(|| {
        // Lucida Grande 12 is the default fallback font in Firefox
        let ct_font = core_text::font::new_from_name("Lucida Grande", 12.).unwrap();
        let point = CGPoint { x: 0., y: 0. };
        let glyph = 'X' as CGGlyph;
        ct_font.draw_glyphs(&[glyph], &[point], smooth_context.clone());
        ct_font.draw_glyphs(&[glyph], &[point], gray_context.clone());
    });

    let mut mode = None;
    for (smooth, gray) in smooth_context.data().chunks(4).zip(gray_context.data().chunks(4)) {
        if smooth[0] != smooth[1] || smooth[1] != smooth[2] {
            return Some(FontRenderMode::Subpixel);
        }
        if smooth[0] != gray[0] || smooth[1] != gray[1] || smooth[2] != gray[2] {
            mode = Some(FontRenderMode::Alpha);
        }
    }
    return mode;
}

// We cache the font smoothing mode globally, rather than storing it in each FontContext,
// to avoid having to determine this redundantly in each context and to avoid needing to
// lock them to access this setting in prepare_font.
lazy_static! {
    static ref FONT_SMOOTHING_MODE: Option<FontRenderMode> = determine_font_smoothing_mode();
}

fn should_use_white_on_black(color: ColorU) -> bool {
    let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32);
    // These thresholds were determined on 10.12 by observing what CG does.
    r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255
}

fn get_glyph_metrics(
    ct_font: &CTFont,
    transform: Option<&CGAffineTransform>,
    glyph: CGGlyph,
    x_offset: f64,
    y_offset: f64,
    extra_width: f64,
) -> GlyphMetrics {
    let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);

    if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() ||
        bounds.size.height.is_nan()
    {
        // If an unexpected glyph index is requested, core text will return NaN values
        // which causes us to do bad thing as the value is cast into an integer and
        // overflow when expanding the bounds a few lines below.
        // Instead we are better off returning zero-sized metrics because this special
        // case is handled by the callers of this method.
        return GlyphMetrics {
            rasterized_left: 0,
            rasterized_width: 0,
            rasterized_height: 0,
            rasterized_ascent: 0,
            rasterized_descent: 0,
            advance: 0.0,
        };
    }

    let mut advance = CGSize { width: 0.0, height: 0.0 };
    unsafe {
        ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
    }

    if bounds.size.width > 0.0 {
        bounds.size.width += extra_width;
    }
    if advance.width > 0.0 {
        advance.width += extra_width;
    }

    if let Some(transform) = transform {
        bounds = bounds.apply_transform(transform);
    }

    // First round out to pixel boundaries
    // CG Origin is bottom left
    let mut left = bounds.origin.x.floor() as i32;
    let mut bottom = bounds.origin.y.floor() as i32;
    let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32;
    let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32;

    // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
    // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
    // is not currently known, as CG dilates the outlines by some percentage.
    // This is taken from Skia.
    left -= 1;
    bottom -= 1;
    right += 1;
    top += 1;

    let width = right - left;
    let height = top - bottom;

    GlyphMetrics {
        rasterized_left: left,
        rasterized_width: width,
        rasterized_height: height,
        rasterized_ascent: top,
        rasterized_descent: -bottom,
        advance: advance.width as f32,
    }
}

fn new_ct_font_with_variations(ct_font_desc: &CTFontDescriptor, size: f64, variations: &[FontVariation]) -> CTFont {
    let ct_font = core_text::font::new_from_descriptor(ct_font_desc, size);
    if variations.is_empty() {
        return ct_font;
    }
    let mut vals: Vec<(CFNumber, CFNumber)> = Vec::with_capacity(variations.len() as usize);
    for variation in variations {
        vals.push((CFNumber::from(variation.tag as i64), CFNumber::from(variation.value as f64)));
    }
    if vals.is_empty() {
        return ct_font;
    }
    let vals_dict = CFDictionary::from_CFType_pairs(&vals);
    let variation_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontVariationAttribute) };
    let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, vals_dict)]);
    let ct_var_font_desc = ct_font.copy_descriptor().create_copy_with_attributes(attrs_dict.to_untyped()).unwrap();
    core_text::font::new_from_descriptor(&ct_var_font_desc, size)

}

// We rely on Gecko to determine whether the font may have color glyphs to avoid
// needing to load the font ahead of time to query its symbolic traits.
fn is_bitmap_font(font: &FontInstance) -> bool {
    font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS)
}

impl FontContext {
    pub fn distribute_across_threads() -> bool {
        true
    }

    pub fn new() -> FontContext {
        debug!("Test for subpixel AA support: {:?}", *FONT_SMOOTHING_MODE);

        // Force CG to use sRGB color space to gamma correct.
        let contrast = 0.0;
        let gamma = 0.0;

        FontContext {
            ct_font_descs: FastHashMap::default(),
            ct_fonts: FastHashMap::default(),
            graphics_context: GraphicsContext::new(),
            gamma_lut: GammaLut::new(contrast, gamma, gamma),
        }
    }

    pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) {
        if self.ct_font_descs.contains_key(font_key) {
            return;
        }

        assert_eq!(index, 0);
        let data = CFData::from_arc(bytes);
        let ct_font_desc = match font_manager::create_font_descriptor_with_data(data) {
            Err(_) => return,
            Ok(cg_font) => cg_font,
        };
        self.ct_font_descs.insert(*font_key, ct_font_desc);
    }

    pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) {
        if self.ct_font_descs.contains_key(font_key) {
            return;
        }

        // There's no way great way to go from a CGFont to a CTFontDescriptor
        // We could use the postscript name but that doesn't work for the
        // system UI fonts on newer macOS versions. Instead we create a CTFont
        // and use the descriptor for that. Normally we'd try to avoid new_from_CGFont
        // because that adds the CGFont to the descriptor cache which can keep the CGFont
        // around for a long time, but that should be ok for non-web (native) fonts.
        let cf_name = CFString::new(&native_font_handle.name);

        // For "hidden" system fonts, whose names start with a period,
        // we can't instantiate CTFonts via a descriptor. We're really
        // supposed to use CTFontCreateUIFontForLanguage, but for now
        // we just use the CGFont.
        let mut desc = if native_font_handle.name.starts_with('.') {
            let cg_font = match CGFont::from_name(&cf_name) {
                Ok(cg_font) => cg_font,
                Err(_) => {
                    // If for some reason we failed to load a font descriptor, then our
                    // only options are to either abort or substitute a fallback font.
                    // It is preferable to use a fallback font instead so that rendering
                    // can at least still proceed in some fashion without erroring.
                    // Lucida Grande is the fallback font in Gecko, so use that here.
                    CGFont::from_name(&CFString::from_static_string("Lucida Grande"))
                        .expect("couldn't find font with postscript name and couldn't load fallback font")
                }
            };
            core_text::font::new_from_CGFont(&cg_font, 0.).copy_descriptor()
        } else {
            core_text::font_descriptor::new_from_postscript_name(&cf_name)
        };

        // If the NativeFontHandle includes a file path, add this to the descriptor
        // to disambiguate cases where multiple installed fonts have the same psname.
        if native_font_handle.path.len() > 0 {
            let cf_path = CFString::new(&native_font_handle.path);
            let url_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontURLAttribute) };
            let attrs = CFDictionary::from_CFType_pairs(&[
                (url_attribute, CFURL::from_file_system_path(cf_path, kCFURLPOSIXPathStyle, false)),
            ]);
            if let Ok(desc_with_path) = desc.create_copy_with_attributes(attrs.to_untyped()) {
                desc = desc_with_path;
            }
        }

        self.ct_font_descs
            .insert(*font_key, desc);
    }

    pub fn delete_font(&mut self, font_key: &FontKey) {
        if let Some(_) = self.ct_font_descs.remove(font_key) {
            self.ct_fonts.retain(|k, _| k.0 != *font_key);
        }
    }

    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
        // Remove the CoreText font corresponding to this instance.
        let size = FontSize::from_f64_px(instance.get_transformed_size());
        self.ct_fonts.remove(&(instance.font_key, size, instance.variations.clone()));
    }

    fn get_ct_font(
        &mut self,
        font_key: FontKey,
        size: f64,
        variations: &[FontVariation],
    ) -> Option<CTFont> {
        // Interacting with CoreText can create autorelease garbage.
        objc::rc::autoreleasepool(|| {
            match self.ct_fonts.entry((font_key, FontSize::from_f64_px(size), variations.to_vec())) {
                Entry::Occupied(entry) => Some((*entry.get()).clone()),
                Entry::Vacant(entry) => {
                    let ct_font_desc = self.ct_font_descs.get(&font_key)?;
                    let ct_font = new_ct_font_with_variations(ct_font_desc, size, variations);
                    entry.insert(ct_font.clone());
                    Some(ct_font)
                }
            }
        })
    }

    pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
        let character = ch as u16;
        let mut glyph = 0;

        self.get_ct_font(font_key, 16.0, &[])
            .and_then(|ct_font| {
                unsafe {
                    let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);

                    if result {
                        Some(glyph as u32)
                    } else {
                        None
                    }
                }
            })
    }

    pub fn get_glyph_dimensions(
        &mut self,
        font: &FontInstance,
        key: &GlyphKey,
    ) -> Option<GlyphDimensions> {
        let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
        let size = font.size.to_f64_px() * y_scale;
        self.get_ct_font(font.font_key, size, &font.variations)
            .and_then(|ct_font| {
                let glyph = key.index() as CGGlyph;
                let bitmap = is_bitmap_font(font);
                let (mut shape, (x_offset, y_offset)) = if bitmap {
                    (FontTransform::identity(), (0.0, 0.0))
                } else {
                    (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
                };
                if font.flags.contains(FontInstanceFlags::FLIP_X) {
                    shape = shape.flip_x();
                }
                if font.flags.contains(FontInstanceFlags::FLIP_Y) {
                    shape = shape.flip_y();
                }
                if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
                    shape = shape.swap_xy();
                }
                let (mut tx, mut ty) = (0.0, 0.0);
                if font.synthetic_italics.is_enabled() {
                    let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size);
                    shape = shape_;
                    tx = tx_;
                    ty = ty_;
                }
                let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
                    Some(CGAffineTransform {
                        a: shape.scale_x as f64,
                        b: -shape.skew_y as f64,
                        c: -shape.skew_x as f64,
                        d: shape.scale_y as f64,
                        tx: tx,
                        ty: -ty,
                    })
                } else {
                    None
                };
                let (strike_scale, pixel_step) = if bitmap {
                    (y_scale, 1.0)
                } else {
                    (x_scale, y_scale / x_scale)
                };
                let extra_strikes = font.get_extra_strikes(
                    FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD,
                    strike_scale,
                );
                let metrics = get_glyph_metrics(
                    &ct_font,
                    transform.as_ref(),
                    glyph,
                    x_offset,
                    y_offset,
                    extra_strikes as f64 * pixel_step,
                );
                if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
                    None
                } else {
                    Some(GlyphDimensions {
                        left: metrics.rasterized_left,
                        top: metrics.rasterized_ascent,
                        width: metrics.rasterized_width,
                        height: metrics.rasterized_height,
                        advance: metrics.advance,
                    })
                }
            })
    }

    // Assumes the pixels here are linear values from CG
    fn gamma_correct_pixels(
        &self,
        pixels: &mut Vec<u8>,
        render_mode: FontRenderMode,
        color: ColorU,
    ) {
        // Then convert back to gamma corrected values.
        match render_mode {
            FontRenderMode::Alpha => {
                self.gamma_lut.preblend_grayscale(pixels, color);
            }
            FontRenderMode::Subpixel => {
                self.gamma_lut.preblend(pixels, color);
            }
            _ => {} // Again, give mono untouched since only the alpha matters.
        }
    }

    #[allow(dead_code)]
    fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) {
        // Rust doesn't have step_by support on stable :(
        debug!("Width is: {:?} height: {:?}", width, height);
        for i in 0 .. height {
            let current_height = i * width * 4;

            for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
                let b = pixel[0];
                let g = pixel[1];
                let r = pixel[2];
                let a = pixel[3];
                debug!("({}, {}, {}, {}) ", r, g, b, a);
            }
        }
    }

    pub fn prepare_font(font: &mut FontInstance) {
        if is_bitmap_font(font) {
            // Render mode is ignored for bitmap fonts. Also, avoid normalizing the color
            // in case CoreText needs the current color for rendering glyph color layers.
            font.render_mode = FontRenderMode::Mono;
            font.disable_subpixel_position();
            return;
        }
        // Sanitize the render mode for font smoothing. If font smoothing is supported,
        // then we just need to ensure the render mode is limited to what is supported.
        // If font smoothing is actually disabled, then we need to fall back to grayscale.
        if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) ||
            font.render_mode == FontRenderMode::Subpixel {
            match *FONT_SMOOTHING_MODE {
                Some(mode) => {
                    font.render_mode = font.render_mode.limit_by(mode);
                    font.flags.insert(FontInstanceFlags::FONT_SMOOTHING);
                }
                None => {
                    font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
                    font.flags.remove(FontInstanceFlags::FONT_SMOOTHING);
                }
            }
        }
        match font.render_mode {
            FontRenderMode::Mono => {
                // In mono mode the color of the font is irrelevant.
                font.color = ColorU::new(255, 255, 255, 255);
                // Subpixel positioning is disabled in mono mode.
                font.disable_subpixel_position();
            }
            FontRenderMode::Alpha => {
                font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) {
                    // Only the G channel is used to index grayscale tables,
                    // so use R and B to preserve light/dark determination.
                    let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil();
                    let rb = if should_use_white_on_black(font.color) { 255 } else { 0 };
                    ColorU::new(rb, g, rb, a)
                } else {
                    ColorU::new(255, 255, 255, 255)
                };
            }
            FontRenderMode::Subpixel => {
                // Quantization may change the light/dark determination, so quantize in the
                // direction necessary to respect the threshold.
                font.color = if should_use_white_on_black(font.color) {
                    font.color.quantized_ceil()
                } else {
                    font.color.quantized_floor()
                };
            }
        }
    }

    pub fn begin_rasterize(_font: &FontInstance) {
    }

    pub fn end_rasterize(_font: &FontInstance) {
    }

    pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
        objc::rc::autoreleasepool(|| {
        let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
        let size = font.size.to_f64_px() * y_scale;
        let ct_font =
            self.get_ct_font(font.font_key, size, &font.variations).ok_or(GlyphRasterError::LoadFailed)?;
        let glyph_type = if is_bitmap_font(font) {
            GlyphType::Bitmap
        } else {
            GlyphType::Vector
        };

        let (mut shape, (x_offset, y_offset)) = match glyph_type {
            GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)),
            GlyphType::Vector => {
                (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
            }
        };
        if font.flags.contains(FontInstanceFlags::FLIP_X) {
            shape = shape.flip_x();
        }
        if font.flags.contains(FontInstanceFlags::FLIP_Y) {
            shape = shape.flip_y();
        }
        if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
            shape = shape.swap_xy();
        }
        let (mut tx, mut ty) = (0.0, 0.0);
        if font.synthetic_italics.is_enabled() {
            let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size);
            shape = shape_;
            tx = tx_;
            ty = ty_;
        }
        let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) {
            Some(CGAffineTransform {
                a: shape.scale_x as f64,
                b: -shape.skew_y as f64,
                c: -shape.skew_x as f64,
                d: shape.scale_y as f64,
                tx: tx,
                ty: -ty,
            })
        } else {
            None
        };

        let glyph = key.index() as CGGlyph;
        let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap {
            (y_scale, 1.0)
        } else {
            (x_scale, y_scale / x_scale)
        };
        let extra_strikes = font.get_extra_strikes(
            FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD,
            strike_scale,
        );
        let metrics = get_glyph_metrics(
            &ct_font,
            transform.as_ref(),
            glyph,
            x_offset,
            y_offset,
            extra_strikes as f64 * pixel_step,
        );
        if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
            return Err(GlyphRasterError::LoadFailed);
        }

        let raster_size = Size2D::new(
            metrics.rasterized_width as u32,
            metrics.rasterized_height as u32
        );

        // If the font render mode is Alpha, we support two different ways to
        // compute the grayscale mask, depending on the value of the platform
        // options' font_smoothing flag:
        //  - Alpha + smoothing:
        //    We will recover a grayscale mask from a subpixel rasterization, in
        //    such a way that the result looks as close to subpixel text
        //    blending as we can make it. This involves gamma correction,
        //    luminance computations and preblending based on the text color,
        //    just like with the Subpixel render mode.
        //  - Alpha without smoothing:
        //    We will ask CoreGraphics to rasterize the text with font_smoothing
        //    off. This will cause it to use grayscale anti-aliasing with
        //    comparatively thin text. This method of text rendering is not
        //    gamma-aware.
        //
        // For subpixel rasterization, starting with macOS 10.11, CoreGraphics
        // uses different glyph dilation based on the text color. Bright text
        // uses less font dilation (looks thinner) than dark text.
        // As a consequence, when we ask CG to rasterize with subpixel AA, we
        // will render white-on-black text as opposed to black-on-white text if
        // the text color brightness exceeds a certain threshold. This applies
        // to both the Subpixel and the "Alpha + smoothing" modes, but not to
        // the "Alpha without smoothing" and Mono modes.
        //
        // Fonts with color glyphs may, depending on the state within per-glyph
        // table data, require the current font color to determine the output
        // color. For such fonts we must thus supply the current font color just
        // in case it is necessary.
        let use_white_on_black = should_use_white_on_black(font.color);
        let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING);
        let (antialias, smooth, text_color, bg_color, invert) = match glyph_type {
            GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT, false),
            GlyphType::Vector => {
                match (font.render_mode, use_font_smoothing) {
                    (FontRenderMode::Subpixel, _) |
                    (FontRenderMode::Alpha, true) => if use_white_on_black {
                        (true, true, ColorF::WHITE, ColorF::BLACK, false)
                    } else {
                        (true, true, ColorF::BLACK, ColorF::WHITE, true)
                    },
                    (FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE, true),
                    (FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE, true),
                }
            }
        };

        {
            let cg_context = self.graphics_context.get_context(&raster_size, glyph_type);

            // These are always true in Gecko, even for non-AA fonts
            cg_context.set_allows_font_subpixel_positioning(true);
            cg_context.set_should_subpixel_position_fonts(true);

            // Don't quantize because we're doing it already.
            cg_context.set_allows_font_subpixel_quantization(false);
            cg_context.set_should_subpixel_quantize_fonts(false);

            cg_context.set_should_smooth_fonts(smooth);
            cg_context.set_should_antialias(antialias);

            // Fill the background. This could be opaque white, opaque black, or
            // transparency.
            cg_context.set_rgb_fill_color(
                bg_color.r.into(),
                bg_color.g.into(),
                bg_color.b.into(),
                bg_color.a.into(),
            );
            let rect = CGRect {
                origin: CGPoint { x: 0.0, y: 0.0 },
                size: CGSize {
                    width: metrics.rasterized_width as f64,
                    height: metrics.rasterized_height as f64,
                },
            };

            // Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER
            // operator, which can't clear to the transparent color!
            cg_context.set_blend_mode(CGBlendMode::Copy);
            cg_context.fill_rect(rect);
            cg_context.set_blend_mode(CGBlendMode::Normal);

            // Set the text color and draw the glyphs.
            cg_context.set_rgb_fill_color(
                text_color.r.into(),
                text_color.g.into(),
                text_color.b.into(),
                1.0,
            );
            cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);

            // CG Origin is bottom left, WR is top left. Need -y offset
            let mut draw_origin = CGPoint {
                x: -metrics.rasterized_left as f64 + x_offset + tx,
                y: metrics.rasterized_descent as f64 - y_offset - ty,
            };

            if let Some(transform) = transform {
                cg_context.set_text_matrix(&transform);

                draw_origin = draw_origin.apply_transform(&transform.invert());
            } else {
                // Make sure to reset this because some previous glyph rasterization might have
                // changed it.
                cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY);
            }

            ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());

            // We'd like to render all the strikes in a single ct_font.draw_glyphs call,
            // passing an array of glyph IDs and an array of origins, but unfortunately
            // with some fonts, Core Text may inappropriately pixel-snap the rasterization,
            // such that the strikes overprint instead of being offset. Rendering the
            // strikes with individual draw_glyphs calls avoids this.
            // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1633397 for details.)
            for i in 1 ..= extra_strikes {
                let origin = CGPoint {
                    x: draw_origin.x + i as f64 * pixel_step,
                    y: draw_origin.y,
                };
                ct_font.draw_glyphs(&[glyph], &[origin], cg_context.clone());
            }
        }

        let mut rasterized_pixels = self.graphics_context
                                        .get_rasterized_pixels(&raster_size, glyph_type);

        if glyph_type == GlyphType::Vector {
            // We rendered text into an opaque surface. The code below needs to
            // ignore the current value of each pixel's alpha channel. But it's
            // allowed to write to the alpha channel, because we're done calling
            // CG functions now.

            if smooth {
                // Convert to linear space for subpixel AA.
                // We explicitly do not do this for grayscale AA ("Alpha without
                // smoothing" or Mono) because those rendering modes are not
                // gamma-aware in CoreGraphics.
                self.gamma_lut.coregraphics_convert_to_linear(
                    &mut rasterized_pixels,
                );
            }

            for pixel in rasterized_pixels.chunks_mut(4) {
                if invert {
                    pixel[0] = 255 - pixel[0];
                    pixel[1] = 255 - pixel[1];
                    pixel[2] = 255 - pixel[2];
                }

                // Set alpha to the value of the green channel. For grayscale
                // text, all three channels have the same value anyway.
                // For subpixel text, the mask's alpha only makes a difference
                // when computing the destination alpha on destination pixels
                // that are not completely opaque. Picking an alpha value
                // that's somehow based on the mask at least ensures that text
                // blending doesn't modify the destination alpha on pixels where
                // the mask is entirely zero.
                pixel[3] = pixel[1];
            }

            if smooth {
                // Convert back from linear space into device space, and perform
                // some "preblending" based on the text color.
                // In Alpha + smoothing mode, this will also convert subpixel AA
                // into grayscale AA.
                self.gamma_correct_pixels(
                    &mut rasterized_pixels,
                    font.render_mode,
                    font.color,
                );
            }
        }

        Ok(RasterizedGlyph {
            left: metrics.rasterized_left as f32,
            top: metrics.rasterized_ascent as f32,
            width: metrics.rasterized_width,
            height: metrics.rasterized_height,
            scale: match glyph_type {
                GlyphType::Bitmap => y_scale.recip() as f32,
                GlyphType::Vector => 1.0,
            },
            format: match glyph_type {
                GlyphType::Bitmap => GlyphFormat::ColorBitmap,
                GlyphType::Vector => font.get_glyph_format(),
            },
            bytes: rasterized_pixels,
        })})
    }
}

// Avoids taking locks by recycling Core Graphics contexts.
#[allow(dead_code)]
struct GraphicsContext {
    vector_context: CGContext,
    vector_context_size: Size2D<u32>,
    bitmap_context: CGContext,
    bitmap_context_size: Size2D<u32>,
}

impl GraphicsContext {
    fn new() -> GraphicsContext {
        let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH);
        GraphicsContext {
            vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector),
            vector_context_size: size,
            bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap),
            bitmap_context_size: size,
        }
    }

    #[allow(dead_code)]
    fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
                   -> &mut CGContext {
        let (cached_context, cached_size) = match glyph_type {
            GlyphType::Vector => {
                (&mut self.vector_context, &mut self.vector_context_size)
            }
            GlyphType::Bitmap => {
                (&mut self.bitmap_context, &mut self.bitmap_context_size)
            }
        };
        let rounded_size = Size2D::new(size.width.next_power_of_two(),
                                       size.height.next_power_of_two());
        if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height {
            *cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width),
                                       u32::max(cached_size.height, rounded_size.height));
            *cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type);
        }
        cached_context
    }

    #[allow(dead_code)]
    fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
                             -> Vec<u8> {
        let (cached_context, cached_size) = match glyph_type {
            GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size),
            GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size),
        };
        let cached_data = cached_context.data();
        let cached_stride = cached_size.width as usize * 4;

        let result_len = size.width as usize * size.height as usize * 4;
        let mut result = Vec::with_capacity(result_len);
        for y in (cached_size.height - size.height)..cached_size.height {
            let cached_start = y as usize * cached_stride;
            let cached_end = cached_start + size.width as usize * 4;
            result.extend_from_slice(&cached_data[cached_start..cached_end]);
        }
        debug_assert_eq!(result.len(), result_len);
        result
    }

    fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext {
        // The result of rasterization, in all render modes, is going to be a
        // BGRA surface with white text on transparency using premultiplied
        // alpha. For subpixel text, the RGB values will be the mask value for
        // the individual components. For bitmap glyphs, the RGB values will be
        // the (premultiplied) color of the pixel. For Alpha and Mono, each
        // pixel will have R==G==B==A at the end of this function.
        // We access the color channels in little-endian order.
        // The CGContext will create and own our pixel buffer.
        // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
        // an opaque background. In order to hit the most efficient path in CG
        // for this, we will tell CG that the CGContext is opaque, by passing
        // an "[...]AlphaNone[...]" context flag. This creates a slight
        // contradiction to the way we use the buffer after CG is done with it,
        // because we will convert it into text-on-transparency. But that's ok;
        // we still get four bytes per pixel and CG won't mess with the alpha
        // channel after we've stopped calling CG functions. We just need to
        // make sure that we don't look at the alpha values of the pixels that
        // we get from CG, and compute our own alpha value only from RGB.
        // Note that CG requires kCGBitmapByteOrder32Little in order to do
        // subpixel AA at all (which we need it to do in both Subpixel and
        // Alpha+smoothing mode). But little-endian is what we want anyway, so
        // this works out nicely.
        let color_type = match glyph_type {
            GlyphType::Vector => kCGImageAlphaNoneSkipFirst,
            GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst,
        };

        CGContext::create_bitmap_context(None,
                                         size.width as usize,
                                         size.height as usize,
                                         8,
                                         size.width as usize * 4,
                                         &CGColorSpace::create_device_rgb(),
                                         kCGBitmapByteOrder32Little | color_type)
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
enum GlyphType {
    Vector,
    Bitmap,
}