// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // // stringlint:disable import Foundation public extension NSAttributedString { static func with(_ attrStrings: [NSAttributedString]) -> NSAttributedString { let mutableString: NSMutableAttributedString = NSMutableAttributedString() for attrString in attrStrings { mutableString.append(attrString) } return mutableString } func appending(_ attrString: NSAttributedString) -> NSAttributedString { let mutableString: NSMutableAttributedString = NSMutableAttributedString(attributedString: self) mutableString.append(attrString) return mutableString } func appending(string: String, attributes: [Key: Any]? = nil) -> NSAttributedString { return appending(NSAttributedString(string: string, attributes: attributes)) } func adding(attributes: [Key: Any], range: NSRange) -> NSAttributedString { let mutableString: NSMutableAttributedString = NSMutableAttributedString(attributedString: self) mutableString.addAttributes(attributes, range: range) return mutableString } // The actual Swift implementation of 'uppercased' is pretty nuts (see // https://github.com/apple/swift/blob/main/stdlib/public/core/String.swift#L901) // this approach is definitely less efficient but is much simpler and less likely to break private enum CharacterCasing { static let map: [UTF16.CodeUnit: String.UTF16View] = [ "a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "k": "K", "l": "L", "m": "M", "n": "N", "o": "O", "p": "P", "q": "Q", "r": "R", "s": "S", "t": "T", "u": "U", "v": "V", "w": "W", "x": "X", "y": "Y", "z": "Z" ] .reduce(into: [:]) { prev, next in prev[next.key.utf16.first ?? UTF16.CodeUnit()] = next.value.utf16 } } func uppercased() -> NSAttributedString { let result = NSMutableAttributedString(attributedString: self) let uppercasedCharacters = result.string.utf16.map { utf16Char in // Try convert the individual utf16 character to it's uppercase variant // or fallback to the original character (CharacterCasing.map[utf16Char]?.first ?? utf16Char) } result.replaceCharacters( in: NSRange(location: 0, length: length), with: String(utf16CodeUnits: uppercasedCharacters, count: length) ) return result } }