blob: d2b8f0e8924b3804fa3e79b93221d95483e33dda [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016, 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
import CoreFoundation
extension NSAttributedString {
public struct Key: RawRepresentable, Equatable, Hashable {
public let rawValue: String
public init(_ rawValue: String) {
self.rawValue = rawValue
}
public init(rawValue: String) {
self.rawValue = rawValue
}
}
}
extension NSAttributedString.Key: _ObjectiveCBridgeable {
public func _bridgeToObjectiveC() -> NSString {
return rawValue as NSString
}
public static func _forceBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) {
result = NSAttributedString.Key(source as String)
}
public static func _conditionallyBridgeFromObjectiveC(_ source: NSString, result: inout NSAttributedString.Key?) -> Bool {
result = NSAttributedString.Key(source as String)
return true
}
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSString?) -> NSAttributedString.Key {
guard let source = source else { return NSAttributedString.Key("") }
return NSAttributedString.Key(source as String)
}
}
@available(*, unavailable, renamed: "NSAttributedString.Key")
public typealias NSAttributedStringKey = NSAttributedString.Key
open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID())
fileprivate var _string: NSString
fileprivate var _attributeArray: CFRunArrayRef
public required init?(coder aDecoder: NSCoder) {
let mutableAttributedString = NSMutableAttributedString(string: "")
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
return nil
}
// use the resulting _string and _attributeArray to initialize a new instance, just like init
_string = mutableAttributedString._string
_attributeArray = mutableAttributedString._attributeArray
}
open func encode(with aCoder: NSCoder) {
guard aCoder.allowsKeyedCoding else { fatalError("We do not support saving to a non-keyed coder.") }
aCoder.encode(string, forKey: "NSString")
let length = self.length
if length > 0 {
var range = NSMakeRange(NSNotFound, NSNotFound)
var loc = 0
var dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
if range.length == length {
// Special single-attribute run case
// If NSAttributeInfo is not written, then NSAttributes is a dictionary
aCoder.encode(dict, forKey: "NSAttributes")
} else {
let attrsArray = NSMutableArray(capacity: 20)
let data = NSMutableData(capacity: 100) ?? NSMutableData()
let attrsTable = NSMutableDictionary()
while true {
var arraySlot = 0
if let cachedSlot = attrsTable.object(forKey: dict) as? Int {
arraySlot = cachedSlot
} else {
arraySlot = attrsArray.count
attrsTable.setObject(arraySlot, forKey: dict)
attrsArray.add(dict)
}
_NSWriteIntToMutableAttributedStringCoding(range.length, data)
_NSWriteIntToMutableAttributedStringCoding(arraySlot, data)
loc += range.length
guard loc < length else { break }
dict = attributes(at: loc, effectiveRange: &range) as NSDictionary
}
aCoder.encode(attrsArray, forKey: "NSAttributes")
aCoder.encode(data, forKey: "NSAttributeInfo")
}
}
}
static public var supportsSecureCoding: Bool {
return true
}
open override func copy() -> Any {
return copy(with: nil)
}
open func copy(with zone: NSZone? = nil) -> Any {
return self
}
open override func mutableCopy() -> Any {
return mutableCopy(with: nil)
}
open func mutableCopy(with zone: NSZone? = nil) -> Any {
return NSMutableAttributedString(attributedString: self)
}
/// The character contents of the receiver as an NSString object.
open var string: String {
return _string._swiftObject
}
/// Returns the attributes for the character at a given index.
open func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: false,
longestEffectiveRangeSearchRange: nil)
return _attributes(at: location, rangeInfo: rangeInfo)
}
/// The length of the receiver’s string object.
open var length: Int {
return CFAttributedStringGetLength(_cfObject)
}
/// Returns the value for an attribute with a given name of the character at a given index, and by reference the range over which the attribute applies.
open func attribute(_ attrName: NSAttributedString.Key, at location: Int, effectiveRange range: NSRangePointer?) -> Any? {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: false,
longestEffectiveRangeSearchRange: nil)
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
}
/// Returns an NSAttributedString object consisting of the characters and attributes within a given range in the receiver.
open func attributedSubstring(from range: NSRange) -> NSAttributedString {
let attributedSubstring = CFAttributedStringCreateWithSubstring(kCFAllocatorDefault, _cfObject, CFRange(range))
return unsafeBitCast(attributedSubstring, to: NSAttributedString.self)
}
/// Returns the attributes for the character at a given index, and by reference the range over which the attributes apply.
open func attributes(at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> [NSAttributedString.Key: Any] {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: true,
longestEffectiveRangeSearchRange: rangeLimit)
return _attributes(at: location, rangeInfo: rangeInfo)
}
/// Returns the value for the attribute with a given name of the character at a given index, and by reference the range over which the attribute applies.
open func attribute(_ attrName: NSAttributedString.Key, at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> Any? {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: true,
longestEffectiveRangeSearchRange: rangeLimit)
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
}
open override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? NSAttributedString else { return false }
return isEqual(to: other)
}
/// Returns a Boolean value that indicates whether the receiver is equal to another given attributed string.
open func isEqual(to other: NSAttributedString) -> Bool {
guard let runtimeClass = _CFRuntimeGetClassWithTypeID(CFAttributedStringGetTypeID()) else {
fatalError("Could not obtain CFRuntimeClass of CFAttributedString")
}
guard let equalFunction = runtimeClass.pointee.equal else {
fatalError("Could not obtain equal function from CFRuntimeClass of CFAttributedString")
}
return equalFunction(_cfObject, other._cfObject) == true
}
/// Returns an NSAttributedString object initialized with the characters of a given string and no attribute information.
public init(string: String) {
_string = string._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)
super.init()
addAttributesToAttributeArray(attrs: nil)
}
/// Returns an NSAttributedString object initialized with a given string and attributes.
public init(string: String, attributes attrs: [NSAttributedString.Key: Any]? = nil) {
_string = string._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)
super.init()
addAttributesToAttributeArray(attrs: attrs)
}
/// Returns an NSAttributedString object initialized with the characters and attributes of another given attributed string.
public init(attributedString: NSAttributedString) {
// create an empty mutable attr string then immediately replace all of its contents
let mutableAttributedString = NSMutableAttributedString(string: "")
mutableAttributedString.setAttributedString(attributedString)
// use the resulting _string and _attributeArray to initialize a new instance
_string = mutableAttributedString._string
_attributeArray = mutableAttributedString._attributeArray
}
/// Executes the block for each attribute in the range.
open func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: ([NSAttributedString.Key: Any], NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
var attributesEffectiveRange = NSRange(location: NSNotFound, length: 0)
let attributesInRange: [NSAttributedString.Key: Any]
if opts.contains(.longestEffectiveRangeNotRequired) {
attributesInRange = attributes(at: currentIndex, effectiveRange: &attributesEffectiveRange)
} else {
attributesInRange = attributes(at: currentIndex, longestEffectiveRange: &attributesEffectiveRange, in: enumerationRange)
}
var shouldStop: ObjCBool = false
block(attributesInRange, attributesEffectiveRange, &shouldStop)
stop.pointee = shouldStop
return attributesEffectiveRange
}
}
/// Executes the block for the specified attribute run in the specified range.
open func enumerateAttribute(_ attrName: NSAttributedString.Key, in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
var attributeEffectiveRange = NSRange(location: NSNotFound, length: 0)
let attributeInRange: Any?
if opts.contains(.longestEffectiveRangeNotRequired) {
attributeInRange = attribute(attrName, at: currentIndex, effectiveRange: &attributeEffectiveRange)
} else {
attributeInRange = attribute(attrName, at: currentIndex, longestEffectiveRange: &attributeEffectiveRange, in: enumerationRange)
}
var shouldStop: ObjCBool = false
block(attributeInRange, attributeEffectiveRange, &shouldStop)
stop.pointee = shouldStop
return attributeEffectiveRange
}
}
}
private extension NSAttributedString {
struct AttributeEnumerationRange {
let startIndex: Int
let endIndex: Int
let reversed: Bool
var currentIndex: Int
var hasMore: Bool {
if reversed {
return currentIndex >= endIndex
} else {
return currentIndex <= endIndex
}
}
init(range: NSRange, reversed: Bool) {
let lowerBound = range.location
let upperBound = range.location + range.length - 1
self.reversed = reversed
startIndex = reversed ? upperBound : lowerBound
endIndex = reversed ? lowerBound : upperBound
currentIndex = startIndex
}
mutating func advance(step: Int = 1) {
if reversed {
currentIndex -= step
} else {
currentIndex += step
}
}
}
struct RangeInfo {
let rangePointer: NSRangePointer?
let shouldFetchLongestEffectiveRange: Bool
let longestEffectiveRangeSearchRange: NSRange?
}
func _attributes(at location: Int, rangeInfo: RangeInfo) -> [NSAttributedString.Key: Any] {
var cfRange = CFRange()
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> [NSAttributedString.Key: Any] in
// Get attributes value using CoreFoundation function
let value: CFDictionary
if rangeInfo.shouldFetchLongestEffectiveRange, let searchRange = rangeInfo.longestEffectiveRangeSearchRange {
value = CFAttributedStringGetAttributesAndLongestEffectiveRange(_cfObject, location, CFRange(searchRange), cfRangePointer)
} else {
value = CFAttributedStringGetAttributes(_cfObject, location, cfRangePointer)
}
// Convert the value to [String : AnyObject]
let dictionary = unsafeBitCast(value, to: NSDictionary.self)
var results = [NSAttributedString.Key: Any]()
for (key, value) in dictionary {
guard let stringKey = (key as? NSString)?._swiftObject else {
continue
}
results[NSAttributedString.Key(stringKey)] = value
}
// Update effective range and return the results
rangeInfo.rangePointer?.pointee.location = cfRangePointer.pointee.location
rangeInfo.rangePointer?.pointee.length = cfRangePointer.pointee.length
return results
}
}
func _attribute(_ attrName: NSAttributedString.Key, atIndex location: Int, rangeInfo: RangeInfo) -> Any? {
var cfRange = CFRange()
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> AnyObject? in
// Get attribute value using CoreFoundation function
let attribute: AnyObject?
if rangeInfo.shouldFetchLongestEffectiveRange, let searchRange = rangeInfo.longestEffectiveRangeSearchRange {
attribute = CFAttributedStringGetAttributeAndLongestEffectiveRange(_cfObject, location, attrName.rawValue._cfObject, CFRange(searchRange), cfRangePointer)
} else {
attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName.rawValue._cfObject, cfRangePointer)
}
// Update effective range and return the result
rangeInfo.rangePointer?.pointee.location = cfRangePointer.pointee.location
rangeInfo.rangePointer?.pointee.length = cfRangePointer.pointee.length
return attribute
}
}
func _enumerate(in enumerationRange: NSRange, reversed: Bool, using block: (Int, UnsafeMutablePointer<ObjCBool>) -> NSRange) {
var attributeEnumerationRange = AttributeEnumerationRange(range: enumerationRange, reversed: reversed)
while attributeEnumerationRange.hasMore {
var stop: ObjCBool = false
let effectiveRange = block(attributeEnumerationRange.currentIndex, &stop)
attributeEnumerationRange.advance(step: effectiveRange.length)
if stop.boolValue {
break
}
}
}
func addAttributesToAttributeArray(attrs: [NSAttributedString.Key: Any]?) {
guard _string.length > 0 else {
return
}
let range = CFRange(location: 0, length: _string.length)
var attributes: [String : Any] = [:]
if let attrs = attrs {
attrs.forEach { attributes[$0.rawValue] = $1 }
}
CFRunArrayInsert(_attributeArray, range, attributes._cfObject)
}
}
extension NSAttributedString: _CFBridgeable {
internal var _cfObject: CFAttributedString { return unsafeBitCast(self, to: CFAttributedString.self) }
}
extension NSAttributedString {
public struct EnumerationOptions: OptionSet {
public let rawValue: UInt
public init(rawValue: UInt) {
self.rawValue = rawValue
}
public static let reverse = EnumerationOptions(rawValue: 1 << 1)
public static let longestEffectiveRangeNotRequired = EnumerationOptions(rawValue: 1 << 20)
}
}
open class NSMutableAttributedString : NSAttributedString {
open func replaceCharacters(in range: NSRange, with str: String) {
CFAttributedStringReplaceString(_cfMutableObject, CFRange(range), str._cfObject)
}
open func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
guard let attrs = attrs else {
CFAttributedStringSetAttributes(_cfMutableObject, CFRange(range), nil, true)
return
}
CFAttributedStringSetAttributes(_cfMutableObject, CFRange(range), attributesCFDictionary(from: attrs), true)
}
open var mutableString: NSMutableString {
return _string as! NSMutableString
}
open func addAttribute(_ name: NSAttributedString.Key, value: Any, range: NSRange) {
CFAttributedStringSetAttribute(_cfMutableObject, CFRange(range), name.rawValue._cfObject, __SwiftValue.store(value))
}
open func addAttributes(_ attrs: [NSAttributedString.Key: Any], range: NSRange) {
CFAttributedStringSetAttributes(_cfMutableObject, CFRange(range), attributesCFDictionary(from: attrs), false)
}
open func removeAttribute(_ name: NSAttributedString.Key, range: NSRange) {
CFAttributedStringRemoveAttribute(_cfMutableObject, CFRange(range), name.rawValue._cfObject)
}
open func replaceCharacters(in range: NSRange, with attrString: NSAttributedString) {
CFAttributedStringReplaceAttributedString(_cfMutableObject, CFRange(range), attrString._cfObject)
}
open func insert(_ attrString: NSAttributedString, at loc: Int) {
let insertRange = NSRange(location: loc, length: 0)
replaceCharacters(in: insertRange, with: attrString)
}
open func append(_ attrString: NSAttributedString) {
let appendRange = NSRange(location: length, length: 0)
replaceCharacters(in: appendRange, with: attrString)
}
open func deleteCharacters(in range: NSRange) {
// To delete a range of the attributed string, call CFAttributedStringReplaceString() with empty string and specified range
let emptyString = ""._cfObject
CFAttributedStringReplaceString(_cfMutableObject, CFRange(range), emptyString)
}
open func setAttributedString(_ attrString: NSAttributedString) {
let fullStringRange = NSRange(location: 0, length: length)
replaceCharacters(in: fullStringRange, with: attrString)
}
open func beginEditing() {
CFAttributedStringBeginEditing(_cfMutableObject)
}
open func endEditing() {
CFAttributedStringEndEditing(_cfMutableObject)
}
open override func copy(with zone: NSZone? = nil) -> Any {
return NSAttributedString(attributedString: self)
}
public override init(string: String) {
super.init(string: string)
_string = NSMutableString(string: string)
}
public override init(string: String, attributes attrs: [NSAttributedString.Key: Any]? = nil) {
super.init(string: string, attributes: attrs)
_string = NSMutableString(string: string)
}
public override init(attributedString: NSAttributedString) {
super.init(attributedString: attributedString)
_string = NSMutableString(string: attributedString.string)
}
public required init?(coder aDecoder: NSCoder) {
let mutableAttributedString = NSMutableAttributedString(string: "")
guard _NSReadMutableAttributedStringWithCoder(aDecoder, mutableAttributedString: mutableAttributedString) else {
return nil
}
super.init(attributedString: mutableAttributedString)
_string = NSMutableString(string: mutableAttributedString.string)
}
}
extension NSMutableAttributedString {
internal var _cfMutableObject: CFMutableAttributedString { return unsafeBitCast(self, to: CFMutableAttributedString.self) }
}
private extension NSMutableAttributedString {
func attributesCFDictionary(from attrs: [NSAttributedString.Key: Any]) -> CFDictionary {
var attributesDictionary = [String : Any]()
for (key, value) in attrs {
attributesDictionary[key.rawValue] = value
}
return attributesDictionary._cfObject
}
}
// MARK: Coding
fileprivate let _allowedCodingClasses: [AnyClass] = [
NSNumber.self,
NSArray.self,
NSDictionary.self,
NSURL.self,
NSString.self,
]
internal func _NSReadIntFromMutableAttributedStringCoding(_ data: NSData, _ startingOffset: Int) -> (value: Int, newOffset: Int)? {
var multiplier = 1
var offset = startingOffset
let length = data.length
var value = 0
while offset < length {
let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self))
offset += 1
let isLast = i < 128
let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128))
guard !intermediateValue.overflow else { return nil }
let newValue = value.addingReportingOverflow(intermediateValue.partialValue)
guard !newValue.overflow else { return nil }
value = newValue.partialValue
if isLast {
return (value: value, newOffset: offset)
}
multiplier *= 128
}
return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes
}
internal func _NSWriteIntToMutableAttributedStringCoding(_ i: Int, _ data: NSMutableData) {
if i > 127 {
let byte = UInt8(128 + i % 128);
data.append(Data([byte]))
_NSWriteIntToMutableAttributedStringCoding(i / 128, data)
} else {
data.append(Data([UInt8(i)]))
}
}
internal func _NSReadMutableAttributedStringWithCoder(_ decoder: NSCoder, mutableAttributedString: NSMutableAttributedString) -> Bool {
// NSAttributedString.Key is not currently bridging correctly every time we'd like it to.
// Ensure we manually go through String in the meanwhile. SR-XXXX.
func toAttributesDictionary(_ ns: NSDictionary) -> [NSAttributedString.Key: Any]? {
if let bridged = __SwiftValue.fetch(ns) as? [String: Any] {
return Dictionary(bridged.map { (NSAttributedString.Key($0.key), $0.value) }, uniquingKeysWith: { $1 })
} else {
return nil
}
}
guard decoder.allowsKeyedCoding else { /* Unkeyed unarchiving is not supported. */ return false }
let string = decoder.decodeObject(of: NSString.self, forKey: "NSString") ?? ""
mutableAttributedString.replaceCharacters(in: NSMakeRange(0, 0), with: string as String)
guard string.length > 0 else { return true }
var allowed = _allowedCodingClasses
for aClass in decoder.allowedClasses ?? [] {
if !allowed.contains(where: { $0 === aClass }) {
allowed.append(aClass)
}
}
let attributes = decoder.decodeObject(of: allowed, forKey: "NSAttributes")
// If this is present, 'attributes' should be an array; otherwise, a dictionary:
let attrData = decoder.decodeObject(of: NSData.self, forKey: "NSAttributeInfo")
if attrData == nil, let attributesNS = attributes as? NSDictionary, let attributes = toAttributesDictionary(attributesNS) {
mutableAttributedString.setAttributes(attributes, range: NSMakeRange(0, string.length))
return true
} else if let attrData = attrData, let attributesNS = attributes as? [NSDictionary] {
let attributes = attributesNS.compactMap { toAttributesDictionary($0) }
guard attributes.count == attributesNS.count else { return false }
var loc = 0
var offset = 0
let length = string.length
while loc < length {
var rangeLen = 0, arraySlot = 0
guard let intResult1 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
rangeLen = intResult1.value
offset = intResult1.newOffset
guard let intResult2 = _NSReadIntFromMutableAttributedStringCoding(attrData, offset) else { return false }
arraySlot = intResult2.value
offset = intResult2.newOffset
guard arraySlot < attributes.count else { return false }
mutableAttributedString.setAttributes(attributes[arraySlot], range: NSMakeRange(loc, rangeLen))
loc += rangeLen
}
return true
}
return false
}