summaryrefslogtreecommitdiff
path: root/pkg/macos/text/line.zig
blob: 248f8e6453469ede3e4527e0c560b34f5e113b9e (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
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const foundation = @import("../foundation.zig");
const graphics = @import("../graphics.zig");
const text = @import("../text.zig");
const c = @import("c.zig").c;

pub const Line = opaque {
    pub fn createWithAttributedString(str: *foundation.AttributedString) Allocator.Error!*Line {
        return @as(
            ?*Line,
            @ptrFromInt(@intFromPtr(c.CTLineCreateWithAttributedString(
                @ptrCast(str),
            ))),
        ) orelse Allocator.Error.OutOfMemory;
    }

    pub fn release(self: *Line) void {
        foundation.CFRelease(self);
    }

    pub fn getGlyphCount(self: *Line) usize {
        return @intCast(c.CTLineGetGlyphCount(
            @ptrCast(self),
        ));
    }

    pub fn getBoundsWithOptions(
        self: *Line,
        opts: LineBoundsOptions,
    ) graphics.Rect {
        return @bitCast(c.CTLineGetBoundsWithOptions(
            @ptrCast(self),
            @bitCast(opts),
        ));
    }

    pub fn getTypographicBounds(
        self: *Line,
        ascent: ?*f64,
        descent: ?*f64,
        leading: ?*f64,
    ) f64 {
        return c.CTLineGetTypographicBounds(
            @ptrCast(self),
            ascent,
            descent,
            leading,
        );
    }

    pub fn getGlyphRuns(self: *Line) *foundation.Array {
        return @ptrCast(@constCast(c.CTLineGetGlyphRuns(@ptrCast(self))));
    }
};

pub const LineBoundsOptions = packed struct {
    exclude_leading: bool = false,
    exclude_shifts: bool = false,
    hanging_punctuation: bool = false,
    glyph_path_bounds: bool = false,
    use_optical_bounds: bool = false,
    language_extents: bool = false,
    _padding: u58 = 0,

    test {
        try std.testing.expectEqual(
            @bitSizeOf(c.CTLineBoundsOptions),
            @bitSizeOf(LineBoundsOptions),
        );
    }

    test "bitcast" {
        const actual: c.CTLineBoundsOptions = c.kCTLineBoundsExcludeTypographicShifts |
            c.kCTLineBoundsUseOpticalBounds;
        const expected: LineBoundsOptions = .{
            .exclude_shifts = true,
            .use_optical_bounds = true,
        };

        try std.testing.expectEqual(actual, @as(c.CTLineBoundsOptions, @bitCast(expected)));
    }
};

test {
    @import("std").testing.refAllDecls(@This());
}

test "line" {
    const testing = std.testing;

    const font = font: {
        const name = try foundation.String.createWithBytes("Monaco", .utf8, false);
        defer name.release();
        const desc = try text.FontDescriptor.createWithNameAndSize(name, 12);
        defer desc.release();

        break :font try text.Font.createWithFontDescriptor(desc, 12);
    };
    defer font.release();

    const rep = try foundation.String.createWithBytes("hello", .utf8, false);
    defer rep.release();
    const str = try foundation.MutableAttributedString.create(rep.getLength());
    defer str.release();
    str.replaceString(foundation.Range.init(0, 0), rep);
    str.setAttribute(
        foundation.Range.init(0, rep.getLength()),
        text.StringAttribute.font,
        font,
    );

    const line = try Line.createWithAttributedString(@as(*foundation.AttributedString, @ptrCast(str)));
    defer line.release();

    try testing.expectEqual(@as(usize, 5), line.getGlyphCount());

    // TODO: this is a garbage value but should work...
    const bounds = line.getBoundsWithOptions(.{});
    _ = bounds;
    //std.log.warn("bounds={}", .{bounds});
}