// Copyright 2013 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Immutable strings. pub use core_foundation_sys::string::*; use base::{CFIndexConvertible, TCFType}; use core_foundation_sys::base::{Boolean, CFIndex, CFRange}; use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull}; use std::borrow::Cow; use std::fmt; use std::str::{self, FromStr}; use std::ptr; use std::ffi::CStr; declare_TCFType!{ /// An immutable string in one of a variety of encodings. CFString, CFStringRef } impl_TCFType!(CFString, CFStringRef, CFStringGetTypeID); impl FromStr for CFString { type Err = (); /// See also CFString::new for a variant of this which does not return a Result #[inline] fn from_str(string: &str) -> Result { Ok(CFString::new(string)) } } impl<'a> From<&'a str> for CFString { #[inline] fn from(string: &'a str) -> CFString { CFString::new(string) } } impl<'a> From<&'a CFString> for Cow<'a, str> { fn from(cf_str: &'a CFString) -> Cow<'a, str> { unsafe { // Do this without allocating if we can get away with it let c_string = CFStringGetCStringPtr(cf_str.0, kCFStringEncodingUTF8); if !c_string.is_null() { let c_str = CStr::from_ptr(c_string); Cow::Borrowed(str::from_utf8_unchecked(c_str.to_bytes())) } else { let char_len = cf_str.char_len(); // First, ask how big the buffer ought to be. let mut bytes_required: CFIndex = 0; CFStringGetBytes(cf_str.0, CFRange { location: 0, length: char_len }, kCFStringEncodingUTF8, 0, false as Boolean, ptr::null_mut(), 0, &mut bytes_required); // Then, allocate the buffer and actually copy. let mut buffer = vec![b'\x00'; bytes_required as usize]; let mut bytes_used: CFIndex = 0; let chars_written = CFStringGetBytes(cf_str.0, CFRange { location: 0, length: char_len }, kCFStringEncodingUTF8, 0, false as Boolean, buffer.as_mut_ptr(), buffer.len().to_CFIndex(), &mut bytes_used); assert_eq!(chars_written, char_len); // This is dangerous; we over-allocate and null-terminate the string (during // initialization). assert_eq!(bytes_used, buffer.len().to_CFIndex()); Cow::Owned(String::from_utf8_unchecked(buffer)) } } } } impl fmt::Display for CFString { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(&Cow::from(self)) } } impl fmt::Debug for CFString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "\"{}\"", self) } } impl CFString { /// Creates a new `CFString` instance from a Rust string. #[inline] pub fn new(string: &str) -> CFString { unsafe { let string_ref = CFStringCreateWithBytes(kCFAllocatorDefault, string.as_ptr(), string.len().to_CFIndex(), kCFStringEncodingUTF8, false as Boolean); CFString::wrap_under_create_rule(string_ref) } } /// Like `CFString::new`, but references a string that can be used as a backing store /// by virtue of being statically allocated. #[inline] pub fn from_static_string(string: &'static str) -> CFString { unsafe { let string_ref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, string.as_ptr(), string.len().to_CFIndex(), kCFStringEncodingUTF8, false as Boolean, kCFAllocatorNull); TCFType::wrap_under_create_rule(string_ref) } } /// Returns the number of characters in the string. #[inline] pub fn char_len(&self) -> CFIndex { unsafe { CFStringGetLength(self.0) } } } impl<'a> PartialEq<&'a str> for CFString { fn eq(&self, other: &&str) -> bool { unsafe { let temp = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, other.as_ptr(), other.len().to_CFIndex(), kCFStringEncodingUTF8, false as Boolean, kCFAllocatorNull); self.eq(&CFString::wrap_under_create_rule(temp)) } } } impl<'a> PartialEq for &'a str { #[inline] fn eq(&self, other: &CFString) -> bool { other.eq(self) } } impl PartialEq for String { #[inline] fn eq(&self, other: &CFString) -> bool { other.eq(&self.as_str()) } } impl PartialEq for CFString { #[inline] fn eq(&self, other: &String) -> bool { self.eq(&other.as_str()) } } #[test] fn str_cmp() { let cfstr = CFString::new("hello"); assert_eq!("hello", cfstr); assert_eq!(cfstr, "hello"); assert_ne!(cfstr, "wrong"); assert_ne!("wrong", cfstr); let hello = String::from("hello"); assert_eq!(hello, cfstr); assert_eq!(cfstr, hello); } #[test] fn string_and_back() { let original = "The quick brown fox jumped over the slow lazy dog."; let cfstr = CFString::from_static_string(original); let converted = cfstr.to_string(); assert_eq!(converted, original); }