summaryrefslogtreecommitdiff
path: root/platform/darwin/src/local_glyph_rasterizer.mm
blob: f7304b18f402da61925329680a083d69b3b2c137 (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
#include <mbgl/text/local_glyph_rasterizer.hpp>
#include <mbgl/util/i18n.hpp>

#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import <ImageIO/ImageIO.h>

namespace {

template <typename T, typename S, void (*Releaser)(S)>
struct CFHandle {
    CFHandle(T t_): t(t_) {}
    ~CFHandle() { Releaser(t); }
    T operator*() { return t; }
    operator bool() { return t; }
private:
    T t;
};

} // namespace


namespace mbgl {

/*
    Initial implementation of darwin TinySDF support:
    Draw any CJK glyphs using a default system font
 
    Where to take this:
     - Configure whether to use local fonts, and which fonts to use,
       based on map or even style layer options
     - Build heuristics for choosing fonts based on input FontStack
       (maybe a globally configurable FontStack -> UIFontDescriptor map would make sense?
     - Extract glyph metrics so that this can be used with more than just fixed width glyphs
*/

bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) {
    // TODO: This is a rough approximation of the set of glyphs that will work with fixed glyph metrics
    // Either narrow this down to be conservative, or actually extract glyph metrics in rasterizeGlyph
    return util::i18n::allowsIdeographicBreaking(glyphID);
}

using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>;
using CGColorSpaceHandle = CFHandle<CGColorSpaceRef, CGColorSpaceRef, CGColorSpaceRelease>;

Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack&, GlyphID glyphID) {
    Glyph fixedMetrics;
    fixedMetrics.id = glyphID;

    fixedMetrics.metrics.width = 24;
    fixedMetrics.metrics.height = 24;
    fixedMetrics.metrics.left = 0;
    fixedMetrics.metrics.top = -8;
    fixedMetrics.metrics.advance = 24;

    uint32_t width = 30;
    uint32_t height = 30;
    Size size(width, height);

    fixedMetrics.bitmap = AlphaImage(size);
    
    NSDictionary *fontAttributes =
              [NSDictionary dictionaryWithObjectsAndKeys:
                      [NSNumber numberWithFloat:24.0], (NSString *)kCTFontSizeAttribute,
                      nil];
    // Create a descriptor.
    CTFontDescriptorRef descriptor =
              CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);

    // Create a font using the descriptor.
    CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
    CFRelease(descriptor);


    CFStringRef string = CFStringCreateWithCharacters(NULL, (UniChar*)&glyphID, 1);

    PremultipliedImage image(size);

    CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
    // TODO: Is there a way to just draw a single alpha channel instead of copying it out of an RGB image? Doesn't seem like the grayscale colorspace is what I'm looking for...
    if (!colorSpace) {
        throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
    }
    
    constexpr const size_t bitsPerComponent = 8;
    constexpr const size_t bytesPerPixel = 4;
    const size_t bytesPerRow = bytesPerPixel * width;

    CGContextHandle context(CGBitmapContextCreate(
        image.data.get(),
        width,
        height,
        bitsPerComponent,
        bytesPerRow,
        *colorSpace,
        kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast));
    if (!context) {
        throw std::runtime_error("CGBitmapContextCreate failed");
    }

    CFStringRef keys[] = { kCTFontAttributeName };
    CFTypeRef values[] = { font };

    CFDictionaryRef attributes =
        CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
            (const void**)&values, sizeof(keys) / sizeof(keys[0]),
            &kCFTypeDictionaryKeyCallBacks,
            &kCFTypeDictionaryValueCallBacks);

    CFAttributedStringRef attrString =
        CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
    CFRelease(string);
    CFRelease(attributes);

    CTLineRef line = CTLineCreateWithAttributedString(attrString); // TODO: Get glyph runs (for metric extraction) and use showGlyphs API instead?

    // Set text position and draw the line into the graphics context
    CGContextSetTextPosition(*context, 0.0, 0.0);
    CTLineDraw(line, *context);
    CFRelease(line);
    CFRelease(font);
    CFRelease(string); // TODO: Surely leaking something here, wrap these up!
    
    for (uint32_t i = 0; i < width * height; i++) {
        fixedMetrics.bitmap.data[i] = image.data[4 * i + 3]; // alpha value
    }

    return fixedMetrics;
}

} // namespace mbgl