const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const foundation = @import("../foundation.zig"); const c = @import("c.zig").c; pub const String = opaque { pub fn createWithBytes( bs: []const u8, encoding: StringEncoding, external: bool, ) Allocator.Error!*String { return @as(?*String, @ptrFromInt(@intFromPtr(c.CFStringCreateWithBytes( null, bs.ptr, @intCast(bs.len), @intFromEnum(encoding), @intFromBool(external), )))) orelse Allocator.Error.OutOfMemory; } pub fn createWithCharactersNoCopy( unichars: []const u16, ) *String { return @as(*String, @ptrFromInt(@intFromPtr(c.CFStringCreateWithCharactersNoCopy( null, @ptrCast(unichars.ptr), @intCast(unichars.len), foundation.c.kCFAllocatorNull, )))); } pub fn release(self: *String) void { c.CFRelease(self); } pub fn getLength(self: *String) usize { return @intCast(c.CFStringGetLength(@ptrCast(self))); } pub fn hasPrefix(self: *String, prefix: *String) bool { return c.CFStringHasPrefix( @ptrCast(self), @ptrCast(prefix), ) == 1; } pub fn compare( self: *String, other: *String, options: StringComparison, ) foundation.ComparisonResult { return @enumFromInt(c.CFStringCompare( @ptrCast(self), @ptrCast(other), @intCast(@as(c_int, @bitCast(options))), )); } pub fn cstring(self: *String, buf: []u8, encoding: StringEncoding) ?[]const u8 { if (c.CFStringGetCString( @ptrCast(self), buf.ptr, @intCast(buf.len), @intFromEnum(encoding), ) == 0) return null; return std.mem.sliceTo(buf, 0); } pub fn cstringPtr(self: *String, encoding: StringEncoding) ?[:0]const u8 { const ptr = c.CFStringGetCStringPtr( @ptrCast(self), @intFromEnum(encoding), ); if (ptr == null) return null; return std.mem.sliceTo(ptr, 0); } }; pub const MutableString = opaque { pub fn create(cap: usize) !*MutableString { return @ptrCast(c.CFStringCreateMutable( null, @intCast(cap), ) orelse return Allocator.Error.OutOfMemory); } pub fn release(self: *MutableString) void { foundation.CFRelease(self); } pub fn string(self: *MutableString) *String { return @ptrCast(self); } pub fn appendCharacters(self: *MutableString, chars: []const u16) void { c.CFStringAppendCharacters( @ptrCast(self), chars.ptr, @intCast(chars.len), ); } }; pub const StringComparison = packed struct { case_insensitive: bool = false, _unused_2: bool = false, backwards: bool = false, anchored: bool = false, nonliteral: bool = false, localized: bool = false, numerically: bool = false, diacritic_insensitive: bool = false, width_insensitive: bool = false, forced_ordering: bool = false, _padding: u22 = 0, test { try std.testing.expectEqual(@bitSizeOf(c_int), @bitSizeOf(StringComparison)); } }; /// https://developer.apple.com/documentation/corefoundation/cfstringencoding?language=objc pub const StringEncoding = enum(u32) { invalid = 0xffffffff, mac_roman = 0, windows_latin1 = 0x0500, iso_latin1 = 0x0201, nextstep_latin = 0x0B01, ascii = 0x0600, unicode = 0x0100, utf8 = 0x08000100, non_lossy_ascii = 0x0BFF, utf16_be = 0x10000100, utf16_le = 0x14000100, utf32 = 0x0c000100, utf32_be = 0x18000100, utf32_le = 0x1c000100, }; pub fn stringGetSurrogatePairForLongCharacter( ch: u32, surrogates: []u16, ) bool { assert(surrogates.len >= 2); return c.CFStringGetSurrogatePairForLongCharacter(ch, surrogates.ptr) == 1; } test "string" { const testing = std.testing; const str = try String.createWithBytes("hello world", .ascii, false); defer str.release(); const prefix = try String.createWithBytes("hello", .ascii, false); defer prefix.release(); try testing.expect(str.hasPrefix(prefix)); try testing.expectEqual(foundation.ComparisonResult.equal, str.compare(str, .{})); try testing.expectEqualStrings("hello world", str.cstringPtr(.ascii).?); { var buf: [128]u8 = undefined; const cstr = str.cstring(&buf, .ascii).?; try testing.expectEqualStrings("hello world", cstr); } } test "unichar" { const testing = std.testing; var unichars: [2]u16 = undefined; try testing.expect(!stringGetSurrogatePairForLongCharacter('A', &unichars)); }