├── .gitignore ├── CTAdditionsSwiftHelpers ├── CTAdditionsSwiftHelpers.m └── include │ └── CTAdditionsSwiftHelpers.h ├── ColorSyncHelpers ├── CMM.swift ├── ColorSyncDevice.swift ├── ColorSyncHelpers.h ├── ColorSyncProfileClass.swift ├── ColorSyncTransformClass.swift ├── Error.swift └── Swift Extensions │ ├── ColorSyncCMMSwift.swift │ ├── ColorSyncProfileSwift.swift │ └── ColorSyncTransformSwift.swift ├── ColorSyncHelpersTests └── ColorSyncHelpersTests.swift ├── CoreTextAdditions ├── CTAFontManagerErrors.h ├── CTAFontManagerErrors.swift ├── CTAttributedStringAdditions.swift ├── CTFontAdditions.swift ├── CTFontCollectionAdditions.swift ├── CTFontDescriptorAdditions.swift ├── CTFontManagerAdditions.swift ├── CTFrameAdditions.swift ├── CTFramesetterAdditions.swift ├── CTGlyphInfoAdditions.swift ├── CTLineAdditions.swift ├── CTMisc.swift ├── CTParagraphStyleAdditions.swift ├── CTRubyAnnotationAdditions.swift ├── CTRunAdditions.swift ├── CTRunDelegateAdditions.swift ├── CTStringAttributesAdditions.swift ├── CTTextTabAdditions.swift ├── CTTypesetterAdditions.swift ├── CoreTextAdditions.h └── Info.plist ├── CoreTextAdditionsTests ├── CoreTextAdditionsTests.swift ├── Info.plist └── PostCrypt.pfb ├── FoundationAdditions ├── AdditionalAdditions.swift ├── CFBinaryHeap.swift ├── CFBitVector.swift ├── CFMessagePortAdditions.swift ├── CFTypeProtocol.swift ├── CocoaComparable.swift ├── Endianness.swift ├── FoundationAdditions.h ├── FoundationTypesAdditions.swift └── Info.plist ├── FoundationAdditionsTests ├── FoundationAdditionsTests.swift └── Info.plist ├── License.txt ├── Package.swift ├── QuartzAdditions ├── CATransform3DAdditions.swift ├── Info.plist └── QuartzAdditions.h ├── QuartzAdditionsTests ├── Info.plist └── QuartzAdditionsTests.swift ├── README.md ├── SIMDAdditions ├── Info.plist ├── SIMD2.swift ├── SIMD3.swift ├── SIMD4.swift └── SIMDAdditions.h ├── SIMDAdditionsTests ├── Info.plist └── SIMDAdditionsTests.swift ├── SwiftAdditions.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── All Additions-iOS.xcscheme │ ├── All Additions.xcscheme │ ├── ColorSync.xcscheme │ ├── CoreTextAdditions.xcscheme │ ├── FoundationAdditions.xcscheme │ └── TISAdditions.xcscheme ├── SwiftAdditions ├── AppKit.swift ├── AppleScriptFoundation.swift ├── CGColorSpace.swift ├── Characters.swift ├── CoreGraphics.swift ├── Info.plist ├── MacTypesAdditions.swift ├── SAMacError.h ├── SAMacError.m ├── SAMacError.swift └── SwiftAdditions.h ├── SwiftAdditionsTests ├── CharacterAdditionsTests.swift ├── Info.plist ├── MacAdditionsTests.swift ├── MacErrorTests.swift └── SwiftAdditionsTests.swift ├── SwiftAudioAdditions ├── AUOutputBL.swift ├── AudioFileClass.swift ├── AudioFileExt.swift ├── AudioFileFormats.swift ├── AudioUnit.swift ├── CoreAudioError.swift ├── ExtAudioFileClass.swift ├── ExtAudioFileExt.swift ├── Info.plist ├── SAAError.h ├── SAAError.m ├── SoundBankAdditions.swift └── SwiftAudioAdditions.h ├── SwiftAudioAdditionsTests ├── Info.plist └── SwiftAudioAdditionsTests.swift ├── SwiftIOKitAdditions ├── Info.plist ├── KextManager.swift ├── Power Management │ └── pwr mgt.swift ├── SwiftIOKitAdditions.h └── hid │ ├── IOHIDDevice.swift │ ├── IOHIDElement.swift │ ├── IOHIDManager.swift │ ├── IOHIDQueue.swift │ ├── IOHIDTransaction.swift │ ├── IOHIDValue.swift │ └── misc.swift ├── SwiftIOKitAdditionsTests ├── Info.plist └── SwiftIOKitAdditionsTests.swift ├── TISAdditions ├── TISAdditions.docc │ └── TISAdditions.md ├── TISAdditions.h └── TextInputSources.swift ├── TISAdditionsTests └── TISAdditionsTests.swift ├── UTTypeOSTypes ├── UTTypeOSTypes.docc │ └── UTTypeOSTypes.md ├── UTTypeOSTypes.h └── UTTypes.swift └── UTTypeOSTypesTests └── UTTypeOSTypesTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | # 3 | # We recommend against adding the Pods directory to your .gitignore. However 4 | # you should judge for yourself, the pros and cons are mentioned at: 5 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 6 | # 7 | # Pods/ 8 | 9 | .DS_Store 10 | 11 | **/xcuserdata/ 12 | **/project.xcworkspace/ 13 | .build/ 14 | .swiftpm 15 | -------------------------------------------------------------------------------- /CTAdditionsSwiftHelpers/CTAdditionsSwiftHelpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // blankFile.m 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 10/18/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CTAdditionsSwiftHelpers.h" 11 | 12 | NSArray *__nullable CTAFontCopyAvailableTables(CTFontRef __nonnull font, CTFontTableOptions options) 13 | { 14 | CFArrayRef preGoodArray = CTFontCopyAvailableTables(font, options); 15 | if (preGoodArray) { 16 | const CFIndex count = CFArrayGetCount(preGoodArray); 17 | NSMutableArray *ourArray = [[NSMutableArray alloc] initWithCapacity:count]; 18 | for (NSInteger i = 0; i < count; i++) { 19 | CTFontTableTag newTag = (CTFontTableTag)(uintptr_t)CFArrayGetValueAtIndex(preGoodArray, i); 20 | [ourArray addObject:@(newTag)]; 21 | } 22 | 23 | CFRelease(preGoodArray); 24 | return ourArray; 25 | } 26 | 27 | return nil; 28 | } 29 | -------------------------------------------------------------------------------- /CTAdditionsSwiftHelpers/include/CTAdditionsSwiftHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTAdditionsSwiftHelpers.h 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 10/18/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #ifndef CTAdditionsSwiftHelpers_h 10 | #define CTAdditionsSwiftHelpers_h 11 | 12 | #import 13 | #include 14 | 15 | //! Returns an array of font table tags. 16 | //! 17 | //! Calls `CTFontCopyAvailableTables` and returns the values as `OSType`-encoded `NSNumber`s. 18 | //! Needed because `CTFontCopyAvailableTables` returns a `CFArrayRef` with unboxed values, which Swift does not like **at all**. 19 | NSArray *__nullable CTAFontCopyAvailableTables(CTFontRef __nonnull font, CTFontTableOptions options) NS_RETURNS_RETAINED; 20 | 21 | #endif /* CTAdditionsSwiftHelpers_h */ 22 | -------------------------------------------------------------------------------- /ColorSyncHelpers/CMM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CMM.swift 3 | // ColorSyncHelpers 4 | // 5 | // Created by C.W. Betts on 2/14/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @preconcurrency import ColorSync 11 | 12 | private func cmmIterator(_ cmm: ColorSyncCMM?, userInfo: UnsafeMutableRawPointer?) -> Bool { 13 | guard let userInfo = userInfo, let cmm = cmm else { 14 | return false 15 | } 16 | let array = Unmanaged.fromOpaque(userInfo).takeUnretainedValue() 17 | 18 | array.add(CSCMM(cmm: cmm)) 19 | 20 | return true 21 | } 22 | 23 | public final class CSCMM: CustomStringConvertible, CustomDebugStringConvertible { 24 | let cmmInt: ColorSyncCMM 25 | 26 | /// The system-supplied CMM 27 | public static var appleCMM: CSCMM { 28 | let cmms = installedCMMs 29 | for cmm in cmms { 30 | if cmm.bundle == nil { 31 | return cmm 32 | } 33 | } 34 | return cmms.first! 35 | } 36 | 37 | /// Returns all of the available CMMs. 38 | public static var installedCMMs: [CSCMM] { 39 | let cmms = NSMutableArray() 40 | 41 | ColorSyncIterateInstalledCMMs({ cmm, userInfo in 42 | guard let userInfo = userInfo, let cmm = cmm else { 43 | return false 44 | } 45 | let array = Unmanaged.fromOpaque(userInfo).takeUnretainedValue() 46 | 47 | array.add(CSCMM(cmm: cmm)) 48 | 49 | return true 50 | }, UnsafeMutableRawPointer(Unmanaged.passUnretained(cmms).toOpaque())) 51 | 52 | return cmms as! [CSCMM] 53 | } 54 | 55 | fileprivate init(cmm: ColorSyncCMM) { 56 | cmmInt = cmm 57 | } 58 | 59 | /// Creates a CSCMM object from the supplied bundle. 60 | public convenience init?(bundle: Bundle) { 61 | if let newBund = CFBundleCreate(kCFAllocatorDefault, bundle.bundleURL as NSURL) { 62 | self.init(bundle: newBund) 63 | } else { 64 | return nil 65 | } 66 | } 67 | 68 | /// Creates a CSCMM object from the supplied bundle. 69 | public convenience init?(bundle: CFBundle) { 70 | guard let newCmm = ColorSyncCMMCreate(bundle)?.takeRetainedValue() else { 71 | return nil 72 | } 73 | self.init(cmm: newCmm) 74 | } 75 | 76 | /// Will return `nil` for Apple's built-in CMM 77 | public var bundle: Bundle? { 78 | if let cfBundle = ColorSyncCMMGetBundle(cmmInt)?.takeUnretainedValue() { 79 | let aURL = CFBundleCopyBundleURL(cfBundle) as URL 80 | return Bundle(url: aURL)! 81 | } 82 | return nil 83 | } 84 | 85 | /// Returns the localized name of the ColorSync module 86 | public var localizedName: String { 87 | return ColorSyncCMMCopyLocalizedName(cmmInt)!.takeRetainedValue() as String 88 | } 89 | 90 | /// Returns the identifier of the ColorSync module 91 | public var identifier: String { 92 | return ColorSyncCMMCopyCMMIdentifier(cmmInt)!.takeRetainedValue() as String 93 | } 94 | 95 | public var description: String { 96 | return "\(identifier), \"\(localizedName)\"" 97 | } 98 | 99 | public var debugDescription: String { 100 | return CFCopyDescription(cmmInt) as String 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ColorSyncHelpers/ColorSyncHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSyncHelpers.h 3 | // ColorSyncHelpers 4 | // 5 | // Created by C.W. Betts on 9/6/22. 6 | // Copyright © 2022 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | 12 | //! Project version number for ColorSyncHelpers. 13 | FOUNDATION_EXPORT double ColorSyncHelpersVersionNumber; 14 | 15 | //! Project version string for ColorSyncHelpers. 16 | FOUNDATION_EXPORT const unsigned char ColorSyncHelpersVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | 20 | //! Because the declaration in the ColorSync framework is misspelled. 21 | CSEXTERN CFStringRef kColorSyncTransformInfo; 22 | 23 | -------------------------------------------------------------------------------- /ColorSyncHelpers/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // ColorSyncHelpers 4 | // 5 | // Created by C.W. Betts on 2/14/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc public enum CSError: Int, Error { 12 | /// Could not unwrap the error that was returned from a failed function call. 13 | case unwrappingError = -1 14 | } 15 | 16 | extension CSError: CustomStringConvertible, CustomDebugStringConvertible { 17 | public var description: String { 18 | switch self { 19 | case .unwrappingError: 20 | return "Unable to unwrap error returned by a ColorSync function failing" 21 | @unknown default: 22 | return "Unknown error \(self.rawValue)" 23 | } 24 | } 25 | 26 | public var debugDescription: String { 27 | switch self { 28 | case .unwrappingError: 29 | return "Unable to unwrap the error returned by a ColorSync function failing.\n\nThere's nothing you can do, other than create a ticket at https://feedbackassistant.apple.com as fixing this issue is impossible from an outside developer." 30 | @unknown default: 31 | return "Unknown error \(self.rawValue)" 32 | } 33 | } 34 | 35 | public var localizedFailureReason: String { 36 | switch self { 37 | case .unwrappingError: 38 | return "Unable to unwrap the error returned by a ColorSync function failing.\n\nThere's nothing you can do, other than create a ticket at https://feedbackassistant.apple.com as fixing this issue is impossible from an outside developer." 39 | @unknown default: 40 | return "Unknown error \(self.rawValue)" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ColorSyncHelpers/Swift Extensions/ColorSyncCMMSwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSyncCMMSwift.swift 3 | // ColorSyncHelpers 4 | // 5 | // Created by C.W. Betts on 9/19/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @preconcurrency import ColorSync 11 | import FoundationAdditions 12 | 13 | @available(macOS 10.4, iOS 16.0, macCatalyst 16.0, *) 14 | public extension ColorSyncCMM { 15 | /// Will return `nil` for Apple's built-in CMM 16 | @inlinable var bundle: Bundle? { 17 | if let cfBundle = ColorSyncCMMGetBundle(self)?.takeUnretainedValue() { 18 | let aURL = CFBundleCopyBundleURL(cfBundle) as URL 19 | return Bundle(url: aURL)! 20 | } 21 | return nil 22 | } 23 | 24 | /// Returns the localized name of the ColorSync module 25 | @inlinable var localizedName: String { 26 | return ColorSyncCMMCopyLocalizedName(self)!.takeRetainedValue() as String 27 | } 28 | 29 | /// Returns the identifier of the ColorSync module 30 | @inlinable var identifier: String { 31 | return ColorSyncCMMCopyCMMIdentifier(self)!.takeRetainedValue() as String 32 | } 33 | } 34 | 35 | @available(macOS 10.4, iOS 16.0, macCatalyst 16.0, *) 36 | extension ColorSyncCMM: @retroactive CFTypeProtocol { 37 | /// The type identifier of all `ColorSyncCMM` instances. 38 | @inlinable public static var typeID: CFTypeID { 39 | return ColorSyncCMMGetTypeID() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ColorSyncHelpers/Swift Extensions/ColorSyncProfileSwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorSyncProfileSwift.swift 3 | // ColorSyncHelpers 4 | // 5 | // Created by C.W. Betts on 9/19/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @preconcurrency import ColorSync 11 | import FoundationAdditions 12 | 13 | @available(macOS 10.4, tvOS 16.0, iOS 16.0, macCatalyst 16.0, *) 14 | public extension ColorSyncProfile { 15 | /// The data associated with the signature. 16 | /// - parameter tag: signature of the tag to be retrieved 17 | @inlinable subscript (tag: String) -> Data? { 18 | get { 19 | if let data = ColorSyncProfileCopyTag(self, tag as NSString)?.takeRetainedValue() { 20 | return data as Data 21 | } 22 | return nil 23 | } 24 | } 25 | 26 | /// Tests if the profiles has a specified tag. 27 | /// 28 | /// - parameter signature: signature of the tag to be searched for 29 | /// 30 | /// - returns: `true` if tag exists, or `false` if it does not. 31 | @inlinable final func containsTag(_ signature: String) -> Bool { 32 | return ColorSyncProfileContainsTag(self, signature as NSString) 33 | } 34 | 35 | /// Returns MD5 digest for the profile calculated as defined by 36 | /// ICC specification, or `nil` in case of failure. 37 | final var md5: ColorSyncMD5? { 38 | let toRet = ColorSyncProfileGetMD5(self) 39 | var theMD5 = toRet 40 | return withUnsafePointer(to: &theMD5.digest) { (TheT) -> ColorSyncMD5? in 41 | let newErr = UnsafeRawPointer(TheT).assumingMemoryBound(to: UInt.self) 42 | let toCheck = UnsafeBufferPointer(start: newErr, count: MemoryLayout.size / MemoryLayout.size) 43 | for i in toCheck { 44 | if i != 0 { 45 | return toRet 46 | } 47 | } 48 | return nil 49 | } 50 | } 51 | 52 | /// The URL of the profile, or `nil` on error. 53 | final var url: Foundation.URL? { 54 | return ColorSyncProfileGetURL(self, nil)?.takeUnretainedValue() as URL? 55 | } 56 | 57 | /// Returns the URL of the profile, or throws on error. 58 | final func getURL() throws -> URL { 59 | var errVal: Unmanaged? 60 | guard let theURL = ColorSyncProfileGetURL(self, &errVal)?.takeUnretainedValue() else { 61 | guard let errStuff = errVal?.takeRetainedValue() else { 62 | throw CSError.unwrappingError 63 | } 64 | throw errStuff 65 | } 66 | return theURL as URL 67 | } 68 | 69 | /// `Data` containing the header data in host endianess. 70 | @inlinable var header: Data? { 71 | guard let dat = ColorSyncProfileCopyHeader(self)?.takeRetainedValue() else { 72 | return nil 73 | } 74 | return Data(referencing: dat) 75 | } 76 | 77 | #if os(macOS) 78 | /// Estimates the gamma of the profile. 79 | final func estimateGamma() throws -> Float { 80 | var errVal: Unmanaged? 81 | let aRet = ColorSyncProfileEstimateGamma(self, &errVal) 82 | 83 | if aRet == 0.0 { 84 | guard let errStuff = errVal?.takeRetainedValue() else { 85 | throw CSError.unwrappingError 86 | } 87 | throw errStuff 88 | } 89 | return aRet 90 | } 91 | #endif 92 | 93 | /// Return the flattened data. 94 | final func rawData() throws -> Data { 95 | var errVal: Unmanaged? 96 | guard let aDat = ColorSyncProfileCopyData(self, &errVal)?.takeRetainedValue() else { 97 | guard let errStuff = errVal?.takeRetainedValue() else { 98 | throw CSError.unwrappingError 99 | } 100 | throw errStuff 101 | } 102 | return Data(referencing: aDat) 103 | } 104 | 105 | #if os(macOS) 106 | /// An utility function creating three tables of floats (redTable, greenTable, blueTable) 107 | /// each of size `samplesPerChannel`, packed into contiguous memory contained in the `Data` 108 | /// to be returned from the `vcgt` tag of the profile (if `vcgt` tag exists in the profile). 109 | @inlinable final func displayTransferTablesFromVCGT(_ samplesPerChannel: inout Int) -> Data? { 110 | guard let dat = ColorSyncProfileCreateDisplayTransferTablesFromVCGT(self, &samplesPerChannel)?.takeRetainedValue() else { 111 | return nil 112 | } 113 | return Data(referencing: dat) 114 | } 115 | 116 | /// A utility function converting `vcgt` tag (if `vcgt` tag exists in the profile and 117 | /// conversion possible) to formula components used by `CGSetDisplayTransferByFormula`. 118 | final func displayTransferFormulaFromVCGT() -> (red: (min: Float, max: Float, gamma: Float), green: (min: Float, max: Float, gamma: Float), blue: (min: Float, max: Float, gamma: Float))? { 119 | typealias Component = (min: Float, max: Float, gamma: Float) 120 | var red = Component(0, 0, 0) 121 | var green = Component(0, 0, 0) 122 | var blue = Component(0, 0, 0) 123 | if !ColorSyncProfileGetDisplayTransferFormulaFromVCGT(self, &red.min, &red.max, &red.gamma, &green.min, &green.max, &green.gamma, &blue.min, &blue.max, &blue.gamma) { 124 | return nil 125 | } 126 | 127 | return (red, green, blue) 128 | } 129 | #endif 130 | 131 | /// A variable estimating gamut of a display profile. 132 | @inlinable final var isWideGamut: Bool { 133 | return ColorSyncProfileIsWideGamut(self) 134 | } 135 | 136 | /// A variable verifying if a profile is matrix-based. 137 | @inlinable final var isMatrixBased: Bool { 138 | return ColorSyncProfileIsMatrixBased(self) 139 | } 140 | 141 | /// A variable verifying if a profile is using ITU BT.2100 PQ transfer functions. 142 | @inlinable final var isPQBased: Bool { 143 | return ColorSyncProfileIsPQBased(self); 144 | } 145 | 146 | /// A variable verifying if a profile is using ITU BT.2100 HLG transfer functions. 147 | @inlinable final var isHLGBased: Bool { 148 | return ColorSyncProfileIsHLGBased(self) 149 | } 150 | } 151 | 152 | @available(macOS 10.4, tvOS 16.0, iOS 16.0, macCatalyst 16.0, *) 153 | extension ColorSyncProfile: @retroactive CFTypeProtocol { 154 | /// The type identifier of all `ColorSyncProfile` instances. 155 | @inlinable public static var typeID: CFTypeID { 156 | return ColorSyncProfileGetTypeID() 157 | } 158 | } 159 | 160 | @available(macOS 10.4, tvOS 16.0, iOS 16.0, macCatalyst 16.0, *) 161 | public extension ColorSyncMutableProfile { 162 | /* 163 | /// `Data` containing the header data in host endianess 164 | override public var header: Data? { 165 | get { 166 | return super.header 167 | } 168 | set { 169 | if let aHeader = newValue { 170 | ColorSyncProfileSetHeader(self, aHeader as NSData) 171 | } else { 172 | print("header was sent nil, not doing anything!") 173 | } 174 | } 175 | }*/ 176 | 177 | @inlinable func setHeaderData(_ aHeader: Data) { 178 | ColorSyncProfileSetHeader(self, aHeader as NSData) 179 | } 180 | 181 | /// Removes a tag named `named`. 182 | @inlinable func removeTag(_ named: String) { 183 | ColorSyncProfileRemoveTag(self, named as NSString) 184 | } 185 | 186 | func setTag(_ named: String, to newValue: Data?) { 187 | if let data = newValue { 188 | ColorSyncProfileSetTag(self, named as NSString, data as NSData) 189 | } else { 190 | removeTag(named) 191 | } 192 | } 193 | 194 | /* 195 | /// The data associated with the signature. 196 | /// - parameter tag: signature of the tag to be retrieved 197 | override public subscript (tag: String) -> Data? { 198 | get { 199 | return super[tag] 200 | } 201 | set { 202 | if let data = newValue { 203 | ColorSyncProfileSetTag(self, tag as NSString, data as NSData) 204 | } else { 205 | removeTag(tag) 206 | } 207 | } 208 | }*/ 209 | } 210 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTAFontManagerErrors.h: -------------------------------------------------------------------------------- 1 | // 2 | // Header.h 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 7/18/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #ifndef CTAFontManagerErrors_h 10 | #define CTAFontManagerErrors_h 11 | 12 | #import 13 | #include 14 | 15 | /** 16 | @enum CTAFontManagerError 17 | @abstract Font registration errors 18 | @discussion Errors that would prevent registration of fonts for a specified font file URL. 19 | */ 20 | typedef NS_ERROR_ENUM(kCTFontManagerErrorDomain, CTAFontManagerError) { 21 | //! The file does not exist at the specified URL. 22 | CTAFontManagerErrorFileNotFound = kCTFontManagerErrorFileNotFound, 23 | //! Cannot access the file due to insufficient permissions. 24 | CTAFontManagerErrorInsufficientPermissions = kCTFontManagerErrorInsufficientPermissions, 25 | //! The file is not a recognized or supported font file format. 26 | CTAFontManagerErrorUnrecognizedFormat = kCTFontManagerErrorUnrecognizedFormat, 27 | //! The file contains invalid font data that could cause system problems. 28 | CTAFontManagerErrorInvalidFontData = kCTFontManagerErrorInvalidFontData, 29 | //! The file has already been registered in the specified scope. 30 | CTAFontManagerErrorAlreadyRegistered = kCTFontManagerErrorAlreadyRegistered, 31 | //! The operation failed due to a system limitation. 32 | CTAFontManagerErrorExceededResourceLimit = kCTFontManagerErrorExceededResourceLimit, 33 | 34 | //! The font was not found in an asset catalog. 35 | CTAFontManagerErrorAssetNotFound = kCTFontManagerErrorAssetNotFound, 36 | //! The file is not registered in the specified scope. 37 | CTAFontManagerErrorNotRegistered = kCTFontManagerErrorNotRegistered, 38 | //! The font file is actively in use and cannot be unregistered. 39 | CTAFontManagerErrorInUse = kCTFontManagerErrorInUse, 40 | //! The file is required by the system and cannot be unregistered. 41 | CTAFontManagerErrorSystemRequired = kCTFontManagerErrorSystemRequired, 42 | //! The file could not be processed due to an unexpected FontProvider error. 43 | CTAFontManagerErrorRegistrationFailed = kCTFontManagerErrorRegistrationFailed, 44 | //! The file could not be processed because the provider does not have a necessary entitlement. 45 | CTAFontManagerErrorMissingEntitlement = kCTFontManagerErrorMissingEntitlement, 46 | //! The font descriptor does not have information to specify a font file. 47 | CTAFontManagerErrorInsufficientInfo = kCTFontManagerErrorInsufficientInfo, 48 | //! The operation was cancelled by the user. 49 | CTAFontManagerErrorCancelledByUser = kCTFontManagerErrorCancelledByUser, 50 | //! The file could not be registered because of a duplicated font name. 51 | CTAFontManagerErrorDuplicatedName = kCTFontManagerErrorDuplicatedName, 52 | //! The file is not in an allowed location. It must be either in the application's 53 | //! bundle or an on-demand resource. 54 | CTAFontManagerErrorInvalidFilePath = kCTFontManagerErrorInvalidFilePath, 55 | 56 | //! The specified scope is not supported. 57 | CTAFontManagerErrorUnsupportedScope = kCTFontManagerErrorUnsupportedScope 58 | }; 59 | 60 | #endif /* CTAFontManagerErrors_h */ 61 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTAFontManagerErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by C.W. Betts on 9/1/21. 6 | // 7 | 8 | #if SWIFT_PACKAGE 9 | 10 | import Foundation 11 | import CoreText 12 | 13 | /// Font registration errors 14 | /// 15 | /// Errors that would prevent registration of fonts for a specified font file URL. 16 | public struct CTAFontManagerError: Error, _BridgedStoredNSError, CustomNSError, Hashable { 17 | /// Retrieves the embedded NSError. 18 | public let _nsError: NSError 19 | 20 | public init(_nsError: NSError) { 21 | precondition(_nsError.domain == kCTFontManagerErrorDomain as String) 22 | self._nsError = _nsError 23 | } 24 | 25 | public static var errorDomain: String { 26 | return kCTFontManagerErrorDomain as String 27 | } 28 | 29 | /// Font registration errors 30 | /// 31 | /// Errors that would prevent registration of fonts for a specified font file URL. 32 | public enum Code : Int, @unchecked Sendable, _ErrorCodeProtocol, Hashable, Equatable { 33 | 34 | /// Font registration errors 35 | /// 36 | /// Errors that would prevent registration of fonts for a specified font file URL. 37 | public typealias _ErrorType = CTAFontManagerError 38 | 39 | 40 | /// The file does not exist at the specified URL. 41 | case fileNotFound = 101 42 | 43 | /// Cannot access the file due to insufficient permissions. 44 | case insufficientPermissions = 102 45 | 46 | /// The file is not a recognized or supported font file format. 47 | case unrecognizedFormat = 103 48 | 49 | /// The file contains invalid font data that could cause system problems. 50 | case invalidFontData = 104 51 | 52 | /// The file has already been registered in the specified scope. 53 | case alreadyRegistered = 105 54 | 55 | /// The operation failed due to a system limitation. 56 | case exceededResourceLimit = 106 57 | 58 | 59 | /// The font was not found in an asset catalog. 60 | case assetNotFound = 107 61 | 62 | /// The file is not registered in the specified scope. 63 | case notRegistered = 201 64 | 65 | /// The font file is actively in use and cannot be unregistered. 66 | case inUse = 202 67 | 68 | /// The file is required by the system and cannot be unregistered. 69 | case systemRequired = 203 70 | 71 | /// The file could not be processed due to an unexpected FontProvider error. 72 | case registrationFailed = 301 73 | 74 | /// The file could not be processed because the provider does not have a necessary entitlement. 75 | case missingEntitlement = 302 76 | 77 | /// The font descriptor does not have information to specify a font file. 78 | case insufficientInfo = 303 79 | 80 | /// The operation was cancelled by the user. 81 | case cancelledByUser = 304 82 | 83 | /// The file could not be registered because of a duplicated font name. 84 | case duplicatedName = 305 85 | 86 | /// The file is not in an allowed location. It must be either in the application's 87 | /// bundle or an on-demand resource. 88 | case invalidFilePath = 306 89 | 90 | 91 | /// The specified scope is not supported. 92 | case unsupportedScope = 307 93 | } 94 | 95 | /// The file does not exist at the specified URL. 96 | public static var fileNotFound: CTAFontManagerError.Code { 97 | return .fileNotFound 98 | } 99 | 100 | /// Cannot access the file due to insufficient permissions. 101 | public static var insufficientPermissions: CTAFontManagerError.Code { 102 | return .insufficientPermissions 103 | } 104 | 105 | /// The file is not a recognized or supported font file format. 106 | public static var unrecognizedFormat: CTAFontManagerError.Code { 107 | return .unrecognizedFormat 108 | } 109 | 110 | /// The file contains invalid font data that could cause system problems. 111 | public static var invalidFontData: CTAFontManagerError.Code { 112 | return .invalidFontData 113 | } 114 | 115 | /// The file has already been registered in the specified scope. 116 | public static var alreadyRegistered: CTAFontManagerError.Code { 117 | return .alreadyRegistered 118 | } 119 | 120 | /// The operation failed due to a system limitation. 121 | public static var exceededResourceLimit: CTAFontManagerError.Code { 122 | return .exceededResourceLimit 123 | } 124 | 125 | /// The font was not found in an asset catalog. 126 | public static var assetNotFound: CTAFontManagerError.Code { 127 | return .assetNotFound 128 | } 129 | 130 | /// The file is not registered in the specified scope. 131 | public static var notRegistered: CTAFontManagerError.Code { 132 | return .notRegistered 133 | } 134 | 135 | /// The font file is actively in use and cannot be unregistered. 136 | public static var inUse: CTAFontManagerError.Code { 137 | return .inUse 138 | } 139 | 140 | /// The file is required by the system and cannot be unregistered. 141 | public static var systemRequired: CTAFontManagerError.Code { 142 | return .systemRequired 143 | } 144 | 145 | /// The file could not be processed due to an unexpected FontProvider error. 146 | public static var registrationFailed: CTAFontManagerError.Code { 147 | return .registrationFailed 148 | } 149 | 150 | /// The file could not be processed because the provider does not have a necessary entitlement. 151 | public static var missingEntitlement: CTAFontManagerError.Code { 152 | return .missingEntitlement 153 | } 154 | 155 | /// The font descriptor does not have information to specify a font file. 156 | public static var insufficientInfo: CTAFontManagerError.Code { 157 | return .insufficientInfo 158 | } 159 | 160 | /// The operation was cancelled by the user. 161 | public static var cancelledByUser: CTAFontManagerError.Code { 162 | return .cancelledByUser 163 | } 164 | 165 | /// The file could not be registered because of a duplicated font name. 166 | public static var duplicatedName: CTAFontManagerError.Code { 167 | return .duplicatedName 168 | } 169 | 170 | /// The file is not in an allowed location. It must be either in the application's 171 | /// bundle or an on-demand resource. 172 | public static var invalidFilePath: CTAFontManagerError.Code { 173 | return .invalidFilePath 174 | } 175 | 176 | /// The specified scope is not supported. 177 | public static var unsupportedScope: CTAFontManagerError.Code { 178 | return .unsupportedScope 179 | } 180 | } 181 | 182 | #endif 183 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTGlyphInfoAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTGlyphInfoAdditions.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 9/12/22. 6 | // Copyright © 2022 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreText 11 | 12 | public extension CTGlyphInfo { 13 | 14 | // MARK: Glyph Info Creation 15 | 16 | /// Creates an immutable glyph info object with a glyph name. 17 | /// 18 | /// This function creates an immutable glyph info object for a glyph name such as copyright using a specified font. 19 | /// 20 | /// - parameter name: The name of the glyph. 21 | /// - parameter font: The font to be associated with the returned `CTGlyphInfo` object. 22 | /// - parameter baseString: The part of the string the returned object is intended to override. 23 | /// - Returns: A valid reference to an immutable `CTGlyphInfo` object if glyph info creation was successful; otherwise, `nil`. 24 | @inlinable static func create(glyphName name: String, font: CTFont, baseString base: String) -> CTGlyphInfo? { 25 | return CTGlyphInfoCreateWithGlyphName(name as NSString, font, base as NSString) 26 | } 27 | 28 | /// Creates an immutable glyph info object. 29 | /// 30 | /// This function creates an immutable glyph info object for a glyph 31 | /// index and a specified font. 32 | /// - parameter glyph: The glyph identifier. 33 | /// - parameter font: The font to be associated with the returned `CTGlyphInfo` object. 34 | /// - parameter baseString: The part of the string the returned object is intended to override. 35 | /// - Returns: This function will return a reference to a `CTGlyphInfo` object. 36 | @inlinable static func create(glyph: CGGlyph, font: CTFont, baseString: String) -> CTGlyphInfo? { 37 | return CTGlyphInfoCreateWithGlyph(glyph, font, baseString as NSString) 38 | } 39 | 40 | /// Creates an immutable glyph info object. 41 | /// 42 | /// This function creates an immutable glyph info object for a 43 | /// character identifier and a character collection. 44 | /// - parameter cid: A character identifier. 45 | /// - parameter collection: A character collection identifier. 46 | /// - parameter baseString: The part of the string the returned object is intended to override. 47 | /// - Returns: This function will return a reference to a `CTGlyphInfo` object. 48 | @inlinable static func create(characterIdentifier cid: CGFontIndex, collection: CTCharacterCollection, baseString: String) -> CTGlyphInfo? { 49 | return CTGlyphInfoCreateWithCharacterIdentifier(cid, collection, baseString as NSString) 50 | } 51 | 52 | // MARK: Glyph Info Access 53 | 54 | /// The glyph name, if the glyph info object was created with a name; otherwise, `nil`. 55 | var glyphName: String? { 56 | return CTGlyphInfoGetGlyphName(self) as String? 57 | } 58 | 59 | /// A `CGGlyph` value, if the glyph info object was created with a font; otherwise, *0*. 60 | @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) 61 | @inlinable var glyph: CGGlyph { 62 | return CTGlyphInfoGetGlyph(self) 63 | } 64 | 65 | /// The character identifier. 66 | @inlinable var characterIdentifier: CGFontIndex { 67 | return CTGlyphInfoGetCharacterIdentifier(self) 68 | } 69 | 70 | /// The character collection for a glyph info object. 71 | /// 72 | /// If the glyph info object was created with a glyph name or a glyph index, its character collection is `.identityMapping`. 73 | @inlinable var characterCollection: CTCharacterCollection { 74 | return CTGlyphInfoGetCharacterCollection(self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTMisc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTMisc.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 5/29/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import FoundationAdditions 11 | import CoreText 12 | 13 | extension CTFont: @retroactive CFTypeProtocol { 14 | /// Returns the Core Foundation type identifier for for the opaque type `CTFont`. 15 | @inlinable public class var typeID: CFTypeID { 16 | return CTFontGetTypeID() 17 | } 18 | } 19 | 20 | extension CTFontCollection: @retroactive CFTypeProtocol { 21 | /// The Core Foundation type identifier for the opaque type `CTFontCollection`. 22 | @inlinable public class var typeID: CFTypeID { 23 | return CTFontCollectionGetTypeID() 24 | } 25 | } 26 | 27 | extension CTFontDescriptor: @retroactive CFTypeProtocol { 28 | /// Returns the Core Foundation type identifier for the opaque type `CTFontDescriptor`. 29 | @inlinable public class var typeID: CFTypeID { 30 | return CTFontDescriptorGetTypeID() 31 | } 32 | } 33 | 34 | extension CTFrame: @retroactive CFTypeProtocol { 35 | /// Returns the Core Foundation type identifier for the opaque type `CTFrame`. 36 | @inlinable public class var typeID: CFTypeID { 37 | return CTFrameGetTypeID() 38 | } 39 | } 40 | 41 | extension CTFramesetter: @retroactive CFTypeProtocol { 42 | /// Returns the Core Foundation type identifier the opaque type `CTFramesetter`. 43 | @inlinable public class var typeID: CFTypeID { 44 | return CTFramesetterGetTypeID() 45 | } 46 | } 47 | 48 | extension CTLine: @retroactive CFTypeProtocol { 49 | /// Returns the Core Foundation type identifier for the opaque type `CTLineRef`. 50 | @inlinable public class var typeID: CFTypeID { 51 | return CTLineGetTypeID() 52 | } 53 | } 54 | 55 | extension CTParagraphStyle: @retroactive CFTypeProtocol { 56 | /// Returns the Core Foundation type identifier of the paragraph style object. 57 | @inlinable public class var typeID: CFTypeID { 58 | return CTParagraphStyleGetTypeID() 59 | } 60 | } 61 | 62 | extension CTRubyAnnotation: @retroactive CFTypeProtocol { 63 | /// Returns the Core Foundation type identifier for the opaque type `CTRubyAnnotation`. 64 | @inlinable public class var typeID: CFTypeID { 65 | return CTRubyAnnotationGetTypeID() 66 | } 67 | } 68 | 69 | extension CTRun: @retroactive CFTypeProtocol { 70 | /// Returns the Core Foundation type identifier for the opaque type `CTRun`. 71 | @inlinable public class var typeID: CFTypeID { 72 | return CTRunGetTypeID() 73 | } 74 | } 75 | 76 | extension CTRunDelegate: @retroactive CFTypeProtocol { 77 | /// The Core Foundation type identifier for the opaque type `CTRunDelegate`. 78 | @inlinable public static var typeID: CFTypeID { 79 | return CTRunDelegateGetTypeID() 80 | } 81 | } 82 | 83 | extension CTTextTab: @retroactive CFTypeProtocol { 84 | /// Returns the Core Foundation type identifier for the opaque type `CTTextTab`. 85 | @inlinable public class var typeID: CFTypeID { 86 | return CTTextTabGetTypeID() 87 | } 88 | } 89 | 90 | extension CTTypesetter: @retroactive CFTypeProtocol { 91 | /// Returns the Core Foundation type identifier for the opaque type `CTTypesetter`. 92 | @inlinable public static var typeID: CFTypeID { 93 | return CTTypesetterGetTypeID() 94 | } 95 | } 96 | 97 | extension CTGlyphInfo: @retroactive CFTypeProtocol { 98 | /// Returns the Core Foundation type identifier for the opaque type `CTGlyphInfo`. 99 | @inlinable public static var typeID: CFTypeID { 100 | return CTGlyphInfoGetTypeID() 101 | } 102 | } 103 | 104 | extension CTUnderlineStyle: @retroactive Hashable { 105 | 106 | } 107 | 108 | public enum CTBaselineClass: RawLosslessStringConvertibleCFString, Hashable, Codable, @unchecked Sendable { 109 | public typealias RawValue = CFString 110 | 111 | /// Creates a `CTBaselineClass` from a supplied string. 112 | /// If `rawValue` doesn't match any of the `kCTBaselineClass...`s, returns `nil`. 113 | /// - parameter rawValue: The string value to attempt to init `CTBaselineClass` into. 114 | public init?(rawValue: CFString) { 115 | switch rawValue { 116 | case kCTBaselineClassMath: 117 | self = .math 118 | 119 | case kCTBaselineClassRoman: 120 | self = .roman 121 | 122 | case kCTBaselineClassHanging: 123 | self = .hanging 124 | 125 | case kCTBaselineClassIdeographicLow: 126 | self = .ideographicLow 127 | 128 | case kCTBaselineClassIdeographicHigh: 129 | self = .ideographicHigh 130 | 131 | case kCTBaselineClassIdeographicCentered: 132 | self = .ideographicCentered 133 | 134 | default: 135 | return nil 136 | } 137 | } 138 | 139 | public var rawValue: CFString { 140 | switch self { 141 | case .math: 142 | return kCTBaselineClassMath 143 | 144 | case .roman: 145 | return kCTBaselineClassRoman 146 | 147 | case .hanging: 148 | return kCTBaselineClassHanging 149 | 150 | case .ideographicLow: 151 | return kCTBaselineClassIdeographicLow 152 | 153 | case .ideographicHigh: 154 | return kCTBaselineClassIdeographicHigh 155 | 156 | case .ideographicCentered: 157 | return kCTBaselineClassIdeographicCentered 158 | } 159 | } 160 | 161 | case math 162 | 163 | case roman 164 | 165 | case hanging 166 | 167 | case ideographicLow 168 | 169 | case ideographicHigh 170 | 171 | case ideographicCentered 172 | 173 | /// Creates a `CTBaselineClass` from a supplied string. 174 | /// If `stringValue` doesn't match any of the `kCTBaselineClass...`s, returns `nil`. 175 | /// - parameter stringValue: The string value to attempt to init `CTBaselineClass` from. 176 | public init?(_ stringValue: String) { 177 | self.init(rawValue: stringValue as CFString) 178 | } 179 | 180 | /// Deprecated. Use the newer initializer. 181 | @available(*, deprecated, renamed: "CTBaselineClass(_:)") 182 | public init?(stringValue: String) { 183 | self.init(rawValue: stringValue as CFString) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTRubyAnnotationAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTRubyAnnotationAdditions.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 11/1/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreText 11 | 12 | @available(OSX 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) 13 | public extension CTRubyAnnotation { 14 | /// These constants specify how to align the ruby annotation and the base text relative to each other when 15 | /// they don't have the same length. 16 | typealias Alignment = CTRubyAlignment 17 | /// These constants specify whether, and on which side, ruby text is allowed to overhang adjacent text if 18 | /// it is wider than the base text. 19 | typealias Overhang = CTRubyOverhang 20 | /// These constants specify the position of the ruby text with respect to the base text. 21 | typealias Position = CTRubyPosition 22 | 23 | /// Creates an immutable ruby annotation object. 24 | /// - parameter alignment: Specifies how the ruby text and the base text should be aligned relative to each 25 | /// other. 26 | /// - parameter overhang: Specifies how the ruby text can overhang adjacent characters. 27 | /// - parameter sizeFactor: Specifies the size of the annotation text as a percent of the size of the 28 | /// base text. 29 | /// - parameter text: An array of `String`s, indexed by `CTRubyPosition`. Supply `nil` for any 30 | /// unused positions. 31 | /// - returns: This function will return a reference to a `CTRubyAnnotation` object. 32 | /// 33 | /// Using this function is the easiest and most efficient way to 34 | /// create a ruby annotation object. 35 | static func create(alignment: Alignment, overhang: Overhang, sizeFactor: CGFloat, text: [String?]) -> CTRubyAnnotation { 36 | let bText = text.map({$0 as NSString?}) 37 | var cText = bText.map { (strVal) -> Unmanaged? in 38 | guard let strObj = strVal else { 39 | return nil 40 | } 41 | return Unmanaged.passUnretained(strObj) 42 | } 43 | return CTRubyAnnotationCreate(alignment, overhang, sizeFactor, &cText) 44 | } 45 | 46 | /// Creates an immutable ruby annotation object. 47 | /// - parameter alignment: Specifies how the ruby text and the base text should be aligned relative 48 | /// to each other. 49 | /// - parameter overhang: Specifies how the ruby text can overhang adjacent characters. 50 | /// - parameter sizeFactor: Specifies the size of the annotation text as a percent of the size of 51 | /// the base text. 52 | /// - parameter text: A tuple of `String`s, indexed by `CTRubyPosition`. Supply `nil` for 53 | /// any unused positions. 54 | /// - returns: This function will return a reference to a `CTRubyAnnotation` object. 55 | /// 56 | /// Using this function is the easiest and most efficient way to 57 | /// create a ruby annotation object. 58 | static func create(alignment: Alignment, overhang: Overhang, sizeFactor: CGFloat, text: (before: String?, after: String?, interCharacter: String?, inline: String?)) -> CTRubyAnnotation { 59 | let aText = [text.before, text.after, text.interCharacter, text.inline] 60 | return self.create(alignment: alignment, overhang: overhang, sizeFactor: sizeFactor, text: aText) 61 | } 62 | 63 | /// Creates an immutable ruby annotation object. 64 | /// - parameter attributes: 65 | /// A attribute dictionary to combine with the string specified above. If you don't specify 66 | /// `kCTFontAttributeName`, the font used by the Ruby annotation will be deduced from the base 67 | /// text, with a size factor specified by a `CFNumber` value keyed by 68 | /// `kCTRubyAnnotationSizeFactorAttributeName`. 69 | /// - parameter alignment: Specifies how the ruby text and the base text should be aligned relative to each 70 | /// other. 71 | /// - parameter overhang: Specifies how the ruby text can overhang adjacent characters. 72 | /// - parameter position: The position of the annotation text. 73 | /// - parameter string: A string without any formatting, its format will be derived from the attrs 74 | /// specified above. 75 | /// 76 | /// Using this function to create a ruby annotation object with more precise 77 | /// control of the annotation text. 78 | @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 79 | static func create(attributes: [String: Any], alignment: Alignment, overhang: Overhang, position: Position, string: String) -> CTRubyAnnotation { 80 | return CTRubyAnnotationCreateWithAttributes(alignment, overhang, position, string as NSString, attributes as NSDictionary) 81 | } 82 | 83 | /// Creates an immutable copy of a ruby annotation object. 84 | @inlinable func copy() -> CTRubyAnnotation { 85 | return CTRubyAnnotationCreateCopy(self) 86 | } 87 | 88 | /// the alignment value of the ruby annotation object. 89 | @inlinable var alignment: Alignment { 90 | return CTRubyAnnotationGetAlignment(self) 91 | } 92 | 93 | /// The overhang value of a ruby annotation object. 94 | @inlinable var overhang: Overhang { 95 | return CTRubyAnnotationGetOverhang(self) 96 | } 97 | 98 | /// The size factor of a ruby annotation object. 99 | @inlinable var sizeFactor: CGFloat { 100 | return CTRubyAnnotationGetSizeFactor(self) 101 | } 102 | 103 | /// Get the ruby text for a particular position in a ruby annotation. 104 | /// - parameter position: The position for which you want to get the ruby text. 105 | /// - returns: If the `position` is valid, then this 106 | /// function will return a `String` for the text. Otherwise it will return `nil`. 107 | func getText(for position: Position) -> String? { 108 | return CTRubyAnnotationGetTextForPosition(self, position) as String? 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTRunDelegateAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTRunDelegateAdditions.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 9/1/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreText 11 | 12 | // TODO: have Apple go over the API for CTRunDelegate, make sure all areas are supposed to be non-null. 13 | public extension CTRunDelegate { 14 | @inlinable static func create(callbacks: UnsafePointer, refCon: UnsafeMutableRawPointer?) -> CTRunDelegate? { 15 | return CTRunDelegateCreate(callbacks, refCon) 16 | } 17 | 18 | @inlinable var refCon: UnsafeMutableRawPointer? { 19 | return CTRunDelegateGetRefCon(self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTTextTabAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTTextTabAdditions.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 11/1/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreText 11 | 12 | public extension CTTextTab { 13 | /// These constants specify text alignment. 14 | typealias Alignment = CTTextAlignment 15 | 16 | /// Creates and initializes a new text tab. 17 | /// - parameter alignment: The tab's alignment. This is used to determine the position 18 | /// of text inside the tab column. This parameter must be set to a valid 19 | /// `CTTextAlignment` value or this function will return `nil`. 20 | /// - parameter location: The tab's ruler location, relative to the back margin. 21 | /// - parameter options: Options to pass in when the tab is created. Currently, the 22 | /// only option available is `kCTTabColumnTerminatorsAttributeName`. This parameter is 23 | /// optional and can be set to `nil` if not needed.
24 | /// Default is `nil`. 25 | /// - returns: The new CTTextTab. 26 | static func create(alignment: Alignment, location: Double, options: [String: Any]? = nil) -> CTTextTab { 27 | return CTTextTabCreate(alignment, location, options as NSDictionary?) 28 | } 29 | 30 | /// The tab's text alignment value. 31 | @inlinable var alignment: Alignment { 32 | return CTTextTabGetAlignment(self) 33 | } 34 | 35 | /// The tab's ruler location relative to the back margin. 36 | @inlinable var location: Double { 37 | return CTTextTabGetLocation(self) 38 | } 39 | 40 | /// The dictionary of attributes associated with the tab or `nil` if 41 | /// no dictionary is present. 42 | var options: [String: Any]? { 43 | return CTTextTabGetOptions(self) as? [String: Any] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CoreTextAdditions/CTTypesetterAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CTTypesetterAdditions.swift 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 9/13/22. 6 | // Copyright © 2022 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreText 11 | import FoundationAdditions 12 | 13 | public extension CTTypesetter { 14 | 15 | // MARK: Typesetter Creation 16 | 17 | /// Creates an immutable typesetter object using an attributed 18 | /// string and a dictionary of options. 19 | /// 20 | /// The resultant typesetter can be used to create lines, perform 21 | /// line breaking, and do other contextual analysis based on the 22 | /// characters in the string. 23 | /// - parameter string: The `CFAttributedStringRef` that you want to typeset. 24 | /// This parameter must be filled in with a valid `CFAttributedString`. 25 | /// - parameter options: A `CFDictionary` of typesetter options, or `nil` if there are none. 26 | /// - returns: This class method will return either a reference to a `CTTypesetter` 27 | /// or `nil` if layout cannot be performed due to an attributed 28 | /// string that would require unreasonable effort. 29 | /// 30 | /// - seeAlso: kCTTypesetterOptionAllowUnboundedLayout 31 | @inlinable static func create(with string: CFAttributedString, options: NSDictionary? = nil) -> CTTypesetter? { 32 | return CTTypesetterCreateWithAttributedStringAndOptions(string, options) 33 | } 34 | 35 | // MARK: - Typeset Line Breaking 36 | 37 | /// Suggests a contextual line break point based on the width 38 | /// provided. 39 | /// 40 | /// The line break can be triggered either by a hard break character 41 | /// in the stream or by filling the specified width with characters. 42 | /// 43 | /// - parameter startIndex: The starting point for the line break calculations. The break 44 | /// calculations will include the character starting at `startIndex`. 45 | /// - parameter width: The requested line break width. 46 | /// - parameter offset: The line position offset.
47 | /// Default is `0.0` 48 | /// - returns: The value returned is a count of the characters from `startIndex` 49 | /// that would cause the line break. This value returned can be used 50 | /// to construct a character range for `CTTypesetterCreateLine`. 51 | @inlinable func suggestLineBreak(startIndex: CFIndex, width: Double, offset: Double = 0.0) -> CFIndex { 52 | return CTTypesetterSuggestLineBreakWithOffset(self, startIndex, width, offset) 53 | } 54 | 55 | /// Suggests a cluster line break point based on the width provided. 56 | /// 57 | /// Suggests a typographic cluster line break point based on the width 58 | /// provided. This cluster break is similar to a character break, 59 | /// except that it will not break apart linguistic clusters. No other 60 | /// contextual analysis will be done. This can be used by the caller 61 | /// to implement a different line breaking scheme, such as 62 | /// hyphenation. Note that a typographic cluster break can also be 63 | /// triggered by a hard break character in the stream. 64 | /// 65 | /// - parameter startIndex: The starting point for the typographic cluster break 66 | /// calculations. The break calculations will include the character 67 | /// starting at `startIndex`. 68 | /// - parameter width: The requested typographic cluster break width. 69 | /// - parameter offset: The line position offset. 70 | /// - returns: The value returned is a count of the characters from `startIndex` 71 | /// that would cause the cluster break. This value returned can be 72 | /// used to construct a character range for `CTTypesetterCreateLine`. 73 | func suggestClusterBreak(startIndex: CFIndex, width: Double, offset: Double = 0.0) -> CFIndex { 74 | return CTTypesetterSuggestClusterBreakWithOffset(self, startIndex, width, offset) 75 | } 76 | 77 | // MARK: - Typeset Line Creation 78 | 79 | /// Creates an immutable line from the typesetter. 80 | /// 81 | /// The resultant line will consist of glyphs in the correct visual 82 | /// order, ready to draw. 83 | /// - parameter range: The string range which the line will be based on. If the `length` portion of 84 | /// `range` is set to *0*, then the typesetter will continue to add glyphs to the line until it runs out of characters in 85 | /// the string. The `location` and `length` of the range must be within the bounds of the string, otherwise the call will fail. 86 | /// - parameter offset: The line position offset.
87 | /// Default is `0.0` 88 | /// - returns: This method will return a reference to a `CTLine`. 89 | func line(with range: CFRange, offset: Double = 0) -> CTLine { 90 | return CTTypesetterCreateLineWithOffset(self, range, offset) 91 | } 92 | 93 | // MARK: - 94 | 95 | /// Typesetter options. 96 | enum Options: RawLosslessStringConvertibleCFString, Hashable, @unchecked Sendable { 97 | public typealias RawValue = CFString 98 | 99 | /// Allows layout requiring a potentially unbounded amount of work. 100 | /// 101 | /// Value must be a `CFBooleanRef`. Default is `false` for clients linked on or after macOS 10.14 or iOS 12. 102 | /// Proper Unicode layout of some text requires unreasonable effort; 103 | /// unless this option is set to `kCFBooleanTrue` such inputs will 104 | /// result in `CTTypesetterCreateWithAttributedStringAndOptions` 105 | /// returning `nil`. 106 | @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) 107 | case allowUnboundedLayout 108 | 109 | /// Specifies the embedding level. 110 | /// 111 | /// Value must be a `CFNumberRef`. Default is unset. Normally, 112 | /// typesetting applies the Unicode Bidirectional Algorithm as 113 | /// described in *UAX #9*. If present, this specifies the embedding 114 | /// level and any directional control characters are ignored. 115 | case forcedEmbeddingLevel 116 | 117 | public init?(rawValue: CFString) { 118 | if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) { 119 | if rawValue == kCTTypesetterOptionAllowUnboundedLayout { 120 | self = .allowUnboundedLayout 121 | return 122 | } 123 | } 124 | switch rawValue { 125 | case kCTTypesetterOptionForcedEmbeddingLevel: 126 | self = .forcedEmbeddingLevel 127 | 128 | default: 129 | return nil 130 | } 131 | } 132 | 133 | public var rawValue: CFString { 134 | if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) { 135 | if self == .allowUnboundedLayout { 136 | return kCTTypesetterOptionAllowUnboundedLayout 137 | } 138 | } 139 | switch self { 140 | case .allowUnboundedLayout: 141 | fatalError("We shouldn't be getting here!") 142 | 143 | case .forcedEmbeddingLevel: 144 | return kCTTypesetterOptionForcedEmbeddingLevel 145 | } 146 | } 147 | } 148 | } 149 | 150 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 9.0, *) 151 | public extension CTTypesetter { 152 | /// Creates an immutable typesetter object using an attributed 153 | /// string and a dictionary of options. 154 | /// 155 | /// The resultant typesetter can be used to create lines, perform 156 | /// line breaking, and do other contextual analysis based on the 157 | /// characters in the string. 158 | /// - parameter string: The `AttributedString` that you want to typeset. 159 | /// This parameter must be scoped with `CoreTextAttributes`. 160 | /// - parameter options: A `Dictionary` of typesetter options, or `nil` if there are none. 161 | /// - returns: This class method will return either a reference to a `CTTypesetter` 162 | /// or `nil` if layout cannot be performed due to an attributed 163 | /// string that would require unreasonable effort. 164 | /// 165 | /// - seeAlso: kCTTypesetterOptionAllowUnboundedLayout 166 | static func create(with string: AttributedString, options: [Options: Any]? = nil) throws -> CTTypesetter? { 167 | let preOpt = options?.map({ (key: Options, value: Any) -> (NSString, Any) in 168 | return (key.rawValue, value) 169 | }) 170 | var dict: NSDictionary? 171 | if let preOpt { 172 | let dict2 = Dictionary(uniqueKeysWithValues: preOpt) 173 | dict = dict2 as NSDictionary 174 | } else { 175 | dict = nil 176 | } 177 | let nsAttrStr = try NSAttributedString(string, including: AttributeScopes.CoreTextAttributes.self) 178 | return self.create(with: nsAttrStr, options:dict) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /CoreTextAdditions/CoreTextAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreTextAdditions.h 3 | // CoreTextAdditions 4 | // 5 | // Created by C.W. Betts on 11/4/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CoreTextAdditions. 12 | FOUNDATION_EXPORT double CoreTextAdditionsVersionNumber; 13 | 14 | //! Project version string for CoreTextAdditions. 15 | FOUNDATION_EXPORT const unsigned char CoreTextAdditionsVersionString[]; 16 | 17 | #import 18 | #import 19 | -------------------------------------------------------------------------------- /CoreTextAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CoreTextAdditionsTests/CoreTextAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreTextAdditionsTests.swift 3 | // CoreTextAdditionsTests 4 | // 5 | // Created by C.W. Betts on 11/4/17. 6 | // Copyright © 2017 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftAdditions 11 | @testable import CoreTextAdditions 12 | @testable import CoreTextAdditions.CTAFontManagerErrors 13 | 14 | class CoreTextAdditionsTests: XCTestCase { 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | super.tearDown() 24 | } 25 | 26 | func testFontNames() { 27 | let names = FontManager.availableFontFamilyNames 28 | print(names) 29 | } 30 | 31 | func testPSNames() { 32 | let names = FontManager.availablePostScriptNames 33 | print(names) 34 | } 35 | 36 | #if os(OSX) 37 | func testFontURLs() { 38 | let names = FontManager.availableFontURLs 39 | print(names) 40 | } 41 | #endif 42 | 43 | func testFontThing() { 44 | let wmfURL = URL(fileURLWithPath: "/System/Library/Fonts/Times.ttc") 45 | guard let fd = FontManager.fontDescriptors(from: wmfURL) else { 46 | XCTFail("") 47 | return 48 | } 49 | print(fd) 50 | for desc in fd { 51 | let aFont = CTFont(desc, size: 14) 52 | print(aFont) 53 | } 54 | } 55 | 56 | @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) 57 | func testAttributeScopes() throws { 58 | var attrStr = AttributedString("Color") 59 | attrStr.foregroundColor = CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1) 60 | 61 | let nsAttr = try NSAttributedString(attrStr, including: AttributeScopes.CoreTextAttributes.self) 62 | print(nsAttr) 63 | attrStr.foregroundColor = nil 64 | print(attrStr) 65 | let attrStr1 = try AttributedString(nsAttr, including: AttributeScopes.CoreTextAttributes.self) 66 | let nsAttr2 = NSMutableAttributedString(string: "Color") 67 | nsAttr2.addAttribute(NSAttributedString.Key.CoreText.foregroundColor, value: CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1), range: NSRange(location: 0, length: 5)) 68 | let attrStr2 = try AttributedString(nsAttr2, including: AttributeScopes.CoreTextAttributes.self) 69 | print(attrStr1) 70 | print(attrStr2) 71 | // XCTAssertNotEqual(attrStr1, attrStr) 72 | // XCTAssertEqual(attrStr1, attrStr2) 73 | } 74 | 75 | func testFeatures() { 76 | let aFont = CTFont("Times New Roman" as NSString, size: 0) 77 | let aFeat = aFont.features 78 | XCTAssertNotNil(aFeat) 79 | guard let aFeat else { 80 | return 81 | } 82 | print(aFeat) 83 | } 84 | 85 | func testTables() throws { 86 | let aFont = CTFont("Times New Roman" as NSString, size: 12) 87 | guard let tables = aFont.availableTables() else { 88 | XCTFail("Times New Roman should at least have tables...") 89 | return 90 | } 91 | for table in tables { 92 | let osVal = OSTypeToString(table) ?? "Unknown decode error!" 93 | if let tableDat = aFont.data(for: table) { 94 | print("Table “\(osVal)”: data is \(tableDat.count) bytes long!") 95 | } else { 96 | XCTFail("Table for \(osVal) was not found.") 97 | } 98 | } 99 | print(tables) 100 | } 101 | 102 | /// This tests "PostCrypt", a PostScript Type 1 outline font. 103 | /// 104 | /// As there's no reliable way to store an old, Mac-style PostScript outlines on git, 105 | /// let's use the PC's way of storing PostScript outlines 106 | func testNoTables() throws { 107 | guard let PCURL = Bundle(for: type(of: self)).url(forResource: "PostCrypt", withExtension: "pfb") else { 108 | throw XCTSkip("PostCrypt should at least be findable...") 109 | } 110 | 111 | guard let datProvid = CGDataProvider(url: PCURL as NSURL), 112 | let cgFont = CGFont(datProvid) else { 113 | throw XCTSkip("Creation of the PostScript CGFont failed...") 114 | } 115 | let graphFont = CTFont.create(graphicsFont: cgFont, size: 12) 116 | 117 | if let tables2 = graphFont.availableTables() { 118 | XCTFail("We got tables… \(tables2)") 119 | return 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /CoreTextAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CoreTextAdditionsTests/PostCrypt.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaddTheSane/SwiftMacTypes/36056103d15cbe85657236f734011084225426a6/CoreTextAdditionsTests/PostCrypt.pfb -------------------------------------------------------------------------------- /FoundationAdditions/CFBinaryHeap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CFBinaryHeap.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 8/2/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension CFBinaryHeap { 12 | /// The number of values currently in the binary heap. 13 | @inlinable var count: Int { 14 | return CFBinaryHeapGetCount(self) 15 | } 16 | 17 | /// Removes the minimum value from the binary heap. 18 | @inlinable func removeMinimum() { 19 | CFBinaryHeapRemoveMinimumValue(self) 20 | } 21 | 22 | /// Removes all the values from the binary heap, making it empty. 23 | @inlinable func removeAll() { 24 | CFBinaryHeapRemoveAllValues(self) 25 | } 26 | 27 | /// The minimum value is in the binary heap. If the heap contains several equal 28 | /// minimum values, any one may be returned. 29 | @inlinable var minimum: UnsafeRawPointer? { 30 | CFBinaryHeapGetMinimum(self) 31 | } 32 | 33 | /// Creates a new mutable binary heap with the values from the given binary heap. 34 | /// - parameter allocator: The `CFAllocator` which should be used to allocate 35 | /// memory for the binary heap and its storage for values. This 36 | /// parameter may be `nil` in which case the current default 37 | /// CFAllocator is used. If this reference is not a valid 38 | /// CFAllocator, the behavior is undefined. 39 | /// - parameter capacity: A hint about the number of values that will be held 40 | /// by the `CFBinaryHeap`. Pass `0` for no hint. The implementation may 41 | /// ignore this hint, or may use it to optimize various 42 | /// operations. A heap's actual capacity is only limited by 43 | /// address space and available memory constraints). 44 | /// This parameter must be greater than or equal 45 | /// to the count of the heap which is to be copied, or the 46 | /// behavior is undefined. If this parameter is negative, the 47 | /// behavior is undefined. 48 | /// - returns: A reference to the new mutable binary heap. 49 | /// 50 | /// The values from the binary heap are copied as pointers into the new binary heap (that is, the values 51 | /// themselves are copied, not that which the values point to, if anything). However, the values are also 52 | /// retained by the new binary heap. The count of the new binary will be the same as the given binary 53 | /// heap. The new binary heap uses the same callbacks as the binary heap to be copied. 54 | @inlinable func copy(allocator: CFAllocator? = kCFAllocatorDefault, capacity: CFIndex) -> CFBinaryHeap { 55 | return CFBinaryHeapCreateCopy(allocator, capacity, self) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FoundationAdditions/CFBitVector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CFBitVector.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 6/12/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension CFBitVector { 12 | /// The number of bit values. 13 | @inlinable var count: Int { 14 | return CFBitVectorGetCount(self) 15 | } 16 | 17 | /// Counts the number of times a certain bit value occurs within a range of bits in 18 | /// a bit vector. 19 | /// - parameter range: The range of bits to search. 20 | /// - parameter value: The bit value to count. 21 | /// - Returns: The number of occurrences of value in the specified range. 22 | @inlinable func countOfBit(in range: CFRange, _ value: CFBit) -> Int { 23 | return CFBitVectorGetCountOfBit(self, range, value) 24 | } 25 | 26 | /// Returns whether a bit vector contains a particular bit value. 27 | /// - parameter range: The range of bits to search. 28 | /// - parameter value: The bit value for which to search. 29 | /// - returns: `true` if the specified range of bits contains `value`, otherwise `false`. 30 | @inlinable func containsBit(in range: CFRange, _ value: CFBit) -> Bool { 31 | return CFBitVectorContainsBit(self, range, value) 32 | } 33 | 34 | /// Returns the bit value at a given index in a bit vector. 35 | /// - parameter idx: The index of the bit value in bv to return. 36 | /// - returns: The bit value at index `idx`. 37 | @inlinable func bit(at idx: Int) -> CFBit { 38 | return CFBitVectorGetBitAtIndex(self, idx) 39 | } 40 | 41 | /// Returns the bit values in a range of indices in a bit vector. 42 | /// - parameter range: The range of bit values to return. 43 | /// - parameter bytes: On return, contains the requested bit values. This argument 44 | /// must point to enough memory to hold the number of bits requested. The requested 45 | /// bits are left-aligned with the first requested bit stored in the left-most, or 46 | /// most-significant, bit of the byte stream. 47 | @inlinable func getBits(in range: CFRange, _ bytes: UnsafeMutablePointer) { 48 | CFBitVectorGetBits(self, range, bytes) 49 | } 50 | 51 | /// Locates the first occurrence of a certain bit value within a range of bits in a 52 | /// bit vector. 53 | /// - parameter range: The range of bits to search. 54 | /// - parameter value: The bit value for which to search. 55 | /// - returns: The index of the first occurrence of value in the specified range, 56 | /// or `nil` if value is not present. 57 | func firstIndex(in range: CFRange, of value: CFBit) -> Int? { 58 | let retVal = CFBitVectorGetFirstIndexOfBit(self, range, value) 59 | if retVal == kCFNotFound { 60 | return nil 61 | } 62 | return retVal 63 | } 64 | 65 | /// Locates the last occurrence of a certain bit value within a range of bits in a 66 | /// bit vector. 67 | /// - parameter range: The range of bits to search. 68 | /// - parameter value: The bit value for which to search. 69 | /// - returns: The index of the last occurrence of value in the specified range, 70 | /// or `nil` if value is not present. 71 | func lastIndex(in range: CFRange, of value: CFBit) -> Int? { 72 | let retVal = CFBitVectorGetLastIndexOfBit(self, range, value) 73 | if retVal == kCFNotFound { 74 | return nil 75 | } 76 | return retVal 77 | } 78 | 79 | /// Creates an immutable bit vector that is a copy of another bit vector. 80 | /// - parameter allocator: The allocator to use to allocate memory for the new bit vector. Pass `nil` or kCFAllocatorDefault to use the current default allocator. 81 | /// - returns: A new bit vector holding the same bit values. 82 | @inlinable func copy(allocator: CFAllocator? = kCFAllocatorDefault) -> CFBitVector { 83 | return CFBitVectorCreateCopy(allocator, self) 84 | } 85 | 86 | /// Creates a new mutable bit vector from a pre-existing bit vector. 87 | /// - parameter allocator: The allocator to use to allocate memory for the new bit vector. Pass `nil` or kCFAllocatorDefault to use the current default allocator. 88 | /// - parameter capacity: The maximum number of values that can be contained by the new bit vector. The bit vector starts with the same number of values as bv and can grow to this number of values (it can have less). 89 | /// Pass `0` to specify that the maximum capacity is not limited. If non-`0`, `capacity` must be large enough to hold all bit values. 90 | /// - returns: A new bit vector holding the same bit values. 91 | @inlinable func mutableCopy(allocator: CFAllocator? = kCFAllocatorDefault, capacity: Int) -> CFMutableBitVector { 92 | return CFBitVectorCreateMutableCopy(allocator, capacity, self) 93 | } 94 | } 95 | 96 | public extension CFMutableBitVector { 97 | // override var count: Int { 98 | // get { 99 | // super.count 100 | // } 101 | // set { 102 | // CFBitVectorSetCount(self, newValue) 103 | // } 104 | // } 105 | 106 | /// Changes the size of a mutable bit vector. 107 | /// 108 | /// If this vector was created with a fixed capacity, you cannot increase its 109 | /// size beyond that capacity. 110 | /// - parameter count: The new size for this vector. If `count` is greater than 111 | /// the current size, the additional bit values are set to `0`. 112 | @inlinable func setCount(_ count: Int) { 113 | CFBitVectorSetCount(self, count) 114 | } 115 | 116 | /// Flips a bit value in a bit vector. 117 | /// - parameter idx: The index of the bit value to flip. The index must be in the 118 | /// range `0…N-1`, where `N` is the count of the vector. 119 | @inlinable func flipBit(at idx: Int) { 120 | CFBitVectorFlipBitAtIndex(self, idx) 121 | } 122 | 123 | /// Flips a range of bit values in a bit vector. 124 | /// - parameter range: The range of bit values in bv to flip. The range must 125 | /// not exceed `0…N-1`, where `N` is the count of the vector. 126 | @inlinable func flipBits(in range: CFRange) { 127 | CFBitVectorFlipBits(self, range) 128 | } 129 | 130 | /// Sets the value of a particular bit in a bit vector. 131 | /// - parameter idx: The index of the bit value to set. The index must be in 132 | /// the range `0…N-1`, where `N` is the count of the vector. 133 | /// - parameter value: The bit value to which to set the bit at index idx. 134 | @inlinable func setBit(at idx: Int, to value: CFBit) { 135 | CFBitVectorSetBitAtIndex(self, idx, value) 136 | } 137 | 138 | /// Sets a range of bits in a bit vector to a particular value. 139 | /// - parameter range: The range of bits to set. The range must not exceed 140 | /// `0…N-1`, where `N` is the count of the vector. 141 | /// - parameter value: The bit value to which to set the range of bits. 142 | @inlinable func setBits(in range: CFRange, to value: CFBit) { 143 | CFBitVectorSetBits(self, range, value) 144 | } 145 | 146 | /// Sets all bits in a bit vector to a particular value. 147 | /// - parameter value: The bit value to which to set all bits. 148 | @inlinable func setAllBits(to value: CFBit) { 149 | CFBitVectorSetAllBits(self, value) 150 | } 151 | } 152 | 153 | extension CFBitVector: @retroactive Sequence {} 154 | extension CFBitVector: @retroactive Collection { 155 | public typealias Element = CFBit 156 | 157 | public func index(after i: CFIndex) -> CFIndex { 158 | return i + 1 159 | } 160 | 161 | public subscript(position: CFIndex) -> CFBit { 162 | return CFBitVectorGetBitAtIndex(self, position) 163 | } 164 | 165 | @inlinable public var startIndex: CFIndex { 166 | return 0 167 | } 168 | 169 | @inlinable public var endIndex: CFIndex { 170 | return CFBitVectorGetCount(self) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /FoundationAdditions/CFMessagePortAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CFMessagePortAdditions.swift 3 | // FoundationAdditions 4 | // 5 | // Created by C.W. Betts on 9/30/24. 6 | // Copyright © 2024 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #if os(OSX) || os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) || os(visionOS) 10 | 11 | import Foundation 12 | import CoreFoundation 13 | 14 | // TODO: documentation 15 | public extension CFMessagePort { 16 | /// A Boolean value that indicates whether this object represents a remote port. 17 | @inlinable var isRemote: Bool { 18 | return CFMessagePortIsRemote(self) 19 | } 20 | 21 | /// The name with which a `CFMessagePort` object is registered. 22 | /// 23 | /// The registered name of the port, `nil` if unnamed. 24 | var name: String? { 25 | get { 26 | return CFMessagePortGetName(self) as String? 27 | } 28 | } 29 | 30 | /// Sets the name of a local `CFMessagePort` object. 31 | /// - parameter name: The new name for ms. 32 | /// - returns: `true` if the name change succeeds, otherwise `false`. 33 | func setName(_ name: String?) -> Bool { 34 | return CFMessagePortSetName(self, name as CFString?) 35 | } 36 | 37 | /// Invalidates this `CFMessagePort` object, stopping it from receiving or sending any more messages. 38 | /// 39 | /// Invalidating a message port prevents the port from ever sending or receiving any more messages; the message 40 | /// port is not deallocated, though. If the port has not already been invalidated, the port’s invalidation callback function 41 | /// is invoked, if one has been set with `CFMessagePortSetInvalidationCallBack(_:_:)` or the 42 | /// `invalidationCallBack` variable. The `CFMessagePortContext` info 43 | /// information for this object is also released, if a `release` callback was specified in the port’s context structure. Finally, if a run 44 | /// loop source was created for the message port, the run loop source is also invalidated. 45 | @inlinable func invalidate() { 46 | CFMessagePortInvalidate(self) 47 | } 48 | 49 | /// A Boolean value that indicates whether the current `CFMessagePort` object is valid and able to send or receive messages. 50 | @inlinable var isValid: Bool { 51 | return CFMessagePortIsValid(self) 52 | } 53 | 54 | /// The invalidation callback function of this `CFMessagePort` object. 55 | /// 56 | /// When setting, if the message port is already invalid, the invalidation callback is invoked immediately. 57 | @inlinable var invalidationCallBack: CFMessagePortInvalidationCallBack? { 58 | get { 59 | return CFMessagePortGetInvalidationCallBack(self) 60 | } 61 | set { 62 | CFMessagePortSetInvalidationCallBack(self, newValue) 63 | } 64 | } 65 | 66 | /// `nil` replyMode argument means no return value expected, don't wait for it. 67 | func sendRequest(messageID: Int32, data: CFData, sendTimeout: TimeInterval, receiveTimeout: TimeInterval, replyMode: String?, returnData: AutoreleasingUnsafeMutablePointer?) -> Int32 { 68 | let toRet: Int32 69 | if let returnData { 70 | var theDat: Unmanaged? = nil 71 | toRet = CFMessagePortSendRequest(self, messageID, data, sendTimeout, receiveTimeout, replyMode as CFString?, &theDat) 72 | returnData.pointee = theDat?.takeRetainedValue() 73 | } else { 74 | toRet = CFMessagePortSendRequest(self, messageID, data, sendTimeout, receiveTimeout, replyMode as CFString?, nil) 75 | } 76 | 77 | return toRet 78 | } 79 | 80 | @inlinable func createRunLoopSource(allocator: CFAllocator = kCFAllocatorDefault, order: CFIndex = 0) -> CFRunLoopSource { 81 | return CFMessagePortCreateRunLoopSource(allocator, self, order) 82 | } 83 | 84 | /// Schedules callbacks for the specified message port on the specified dispatch queue. 85 | /// - parameter queue: The libdispatch queue. 86 | @inlinable func setDispatchQueue(_ queue: DispatchQueue) { 87 | CFMessagePortSetDispatchQueue(self, queue) 88 | } 89 | 90 | var context: CFMessagePortContext { 91 | var context: CFMessagePortContext = .init() 92 | CFMessagePortGetContext(self, &context) 93 | return context 94 | } 95 | 96 | /// Returns a local `CFMessagePort` object. 97 | /// - parameter allocator: The allocator to use to allocate memory for the new object. Pass `kCFAllocatorDefault` to use the current default allocator. Default is `kCFAllocatorDefault`. 98 | /// - parameter name: The name with which to register the port. name can be `nil`. 99 | /// - parameter callout: The callback function invoked when a message is received on the message port. 100 | /// - parameter context: A structure holding contextual information for the message port. The function copies the information out of the structure, so the memory pointed to by context does not need to persist beyond the function call. 101 | /// - parameter shouldFreeInfo: A flag set by the function to indicate whether the info member of context should be freed. The flag is set to `true` on failure or if a local port named `name` already exists, `false` otherwise. `shouldFreeInfo` can be `nil`. 102 | /// - returns: The new `CFMessagePort` object, or `nil` on failure. If a local port is already named `name`, the function returns that port instead of creating a new object; the `context` and `callout` parameters are ignored in this case. 103 | @inlinable static func createLocal(allocator: CFAllocator = kCFAllocatorDefault, name: String?, callout: CFMessagePortCallBack, context: UnsafeMutablePointer, shouldFreeInfo: UnsafeMutablePointer?) -> CFMessagePort? { 104 | return CFMessagePortCreateLocal(allocator, name as CFString?, callout, context, shouldFreeInfo) 105 | } 106 | 107 | /// Returns a `CFMessagePort` object connected to a remote port. 108 | /// - parameter allocator: The allocator to use to allocate memory for the new object. Pass `kCFAllocatorDefault` to use the current default allocator. Default is `kCFAllocatorDefault`. 109 | /// - parameter name: The name of the remote message port to which to connect. 110 | /// - returns: The new `CFMessagePort` object, or `nil` on failure. 111 | /// If a message port has already been created for the remote port, the pre-existing object is returned. 112 | @inlinable static func createRemote(allocator: CFAllocator = kCFAllocatorDefault, name: String) -> CFMessagePort? { 113 | return CFMessagePortCreateRemote(allocator, name as CFString) 114 | } 115 | } 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /FoundationAdditions/CocoaComparable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaComparable.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 7/1/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Protocol that can be used to mark Objective-C classes as `Comparable`. 12 | /// Useful if the class already have a `compare(_:)` function. 13 | public protocol CocoaComparable: NSObjectProtocol, Comparable { 14 | func compare(_ rhs: Self) -> ComparisonResult 15 | } 16 | 17 | public extension CocoaComparable { 18 | static func <(lhs: Self, rhs: Self) -> Bool { 19 | return lhs.compare(rhs) == .orderedAscending 20 | } 21 | 22 | static func >(lhs: Self, rhs: Self) -> Bool { 23 | return lhs.compare(rhs) == .orderedDescending 24 | } 25 | 26 | static func <=(lhs: Self, rhs: Self) -> Bool { 27 | let cmpResult = lhs.compare(rhs) 28 | return cmpResult == .orderedAscending || cmpResult == .orderedSame 29 | } 30 | 31 | static func >=(lhs: Self, rhs: Self) -> Bool { 32 | let cmpResult = lhs.compare(rhs) 33 | return cmpResult == .orderedDescending || cmpResult == .orderedSame 34 | } 35 | 36 | //static public func ==(lhs: Self, rhs: Self) -> Bool { 37 | // return lhs.compare(rhs) == .orderedSame 38 | //} 39 | } 40 | 41 | extension NSNumber: @retroactive Comparable {} 42 | extension NSNumber: CocoaComparable { 43 | } 44 | -------------------------------------------------------------------------------- /FoundationAdditions/Endianness.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Endianness.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 11/5/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Swift 10 | 11 | @available(swift, introduced: 1.2, deprecated: 5.5, obsoleted: 6.0, message: "Use `_endian(little)` and `_endian(big)` 'macros' instead.") 12 | public enum ByteOrder { 13 | case little 14 | case big 15 | case unknown 16 | 17 | /// The current byte-order of the machine. 18 | @available(swift, introduced: 1.2, deprecated: 5.5, obsoleted: 6.0, message: "Use `_endian(little)` and `_endian(big)` 'macros' instead.") 19 | @inlinable public static var current: ByteOrder { 20 | #if _endian(little) 21 | return .little 22 | #elseif _endian(big) 23 | return .big 24 | #else 25 | return .unknown 26 | #endif 27 | } 28 | 29 | /// Is the machine's byte-order little-endian? 30 | @inlinable public static var isLittle: Bool { 31 | #if _endian(little) 32 | return true 33 | #else 34 | return false 35 | #endif 36 | } 37 | 38 | /// Is the machine's byte-order big-endian? 39 | @inlinable public static var isBig: Bool { 40 | #if _endian(big) 41 | return true 42 | #else 43 | return false 44 | #endif 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FoundationAdditions/FoundationAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FoundationAdditions.h 3 | // FoundationAdditions 4 | // 5 | // Created by C.W. Betts on 8/7/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FoundationAdditions. 12 | FOUNDATION_EXPORT double FoundationAdditionsVersionNumber; 13 | 14 | //! Project version string for FoundationAdditions. 15 | FOUNDATION_EXPORT const unsigned char FoundationAdditionsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /FoundationAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2021 C.W. Betts. All rights reserved. 23 | 24 | 25 | -------------------------------------------------------------------------------- /FoundationAdditionsTests/FoundationAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FoundationAdditionsTests.swift 3 | // FoundationAdditionsTests 4 | // 5 | // Created by C.W. Betts on 8/7/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import FoundationAdditions 11 | 12 | class FoundationAdditionsTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() throws { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() throws { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /FoundationAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | version 1.0.0, November 9th, 2014 2 | 3 | Copyright (C) 2014 C.W. "Madd the Sane" Betts 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | C.W. Betts 22 | computers57@hotmail.com 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftAdditions", 8 | platforms: [.macOS(.v11), .iOS(.v14), .watchOS(.v7), .tvOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 11 | .library( 12 | name: "FoundationAdditions", 13 | targets: ["FoundationAdditions"]), 14 | .library( 15 | name: "SwiftAdditions", 16 | targets: ["SwiftAdditions"]), 17 | .library( 18 | name: "SwiftAudioAdditions", 19 | targets: ["SwiftAudioAdditions"]), 20 | .library( 21 | name: "CoreTextAdditions", 22 | targets: ["CoreTextAdditions"]), 23 | .library( 24 | name: "TISAdditions", 25 | targets: ["TISAdditions"]), 26 | .library( 27 | name: "UTTypeOSTypes", 28 | targets: ["UTTypeOSTypes"]), 29 | ], 30 | dependencies: [ 31 | // Dependencies declare other packages that this package depends on. 32 | // .package(url: /* package url */, from: "1.0.0"), 33 | ], 34 | targets: [ 35 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 36 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 37 | .target( 38 | name: "SwiftAdditions", 39 | dependencies: ["FoundationAdditions"], 40 | path: "SwiftAdditions", 41 | exclude: ["SAMacError.m"]), 42 | .testTarget( 43 | name: "SwiftAdditionsTests", 44 | dependencies: ["SwiftAdditions"], 45 | path: "SwiftAdditionsTests"), 46 | .target( 47 | name: "SwiftAudioAdditions", 48 | dependencies: ["FoundationAdditions", "SwiftAdditions"], 49 | path: "SwiftAudioAdditions", 50 | exclude: ["SAAError.m"]), 51 | .testTarget( 52 | name: "SwiftAudioAdditionsTests", 53 | dependencies: ["SwiftAdditions", "SwiftAudioAdditions"], 54 | path: "SwiftAudioAdditionsTests"), 55 | .target( 56 | name: "CoreTextAdditions", 57 | dependencies: ["FoundationAdditions", "CTAdditionsSwiftHelpers"], 58 | path: "CoreTextAdditions"), 59 | .target( 60 | name: "CTAdditionsSwiftHelpers", 61 | path: "CTAdditionsSwiftHelpers"), 62 | .testTarget( 63 | name: "CoreTextAdditionsTests", 64 | dependencies: ["SwiftAdditions", "FoundationAdditions", "CoreTextAdditions"], 65 | path: "CoreTextAdditionsTests"), 66 | .target( 67 | name: "FoundationAdditions", 68 | dependencies: [], 69 | path: "FoundationAdditions"), 70 | .testTarget( 71 | name: "FoundationAdditionsTests", 72 | dependencies: ["SwiftAdditions", "FoundationAdditions"], 73 | path: "FoundationAdditionsTests"), 74 | .target( 75 | name: "TISAdditions", 76 | dependencies: ["SwiftAdditions", "FoundationAdditions"], 77 | path: "TISAdditions"), 78 | .testTarget( 79 | name: "TISAdditionsTests", 80 | dependencies: ["SwiftAdditions", "FoundationAdditions", "TISAdditions"], 81 | path: "TISAdditionsTests"), 82 | .target( 83 | name: "UTTypeOSTypes", 84 | dependencies: [], 85 | path: "UTTypeOSTypes"), 86 | .testTarget( 87 | name: "UTTypeOSTypesTests", 88 | dependencies: ["UTTypeOSTypes"], 89 | path: "UTTypeOSTypesTests"), 90 | ] 91 | ) 92 | -------------------------------------------------------------------------------- /QuartzAdditions/CATransform3DAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CATransform3DAdditions.swift 3 | // CoreAnimationTextSwift 4 | // 5 | // Created by C.W. Betts on 10/20/17. 6 | // 7 | 8 | import Foundation 9 | import QuartzCore.CATransform3D 10 | 11 | public extension CATransform3D { 12 | /// Creates a transform with the same effect as affine transform `m`. 13 | @inlinable 14 | init(affineTransform m: CGAffineTransform) { 15 | self = CATransform3DMakeAffineTransform(m) 16 | } 17 | 18 | /// The identity transform: `[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]`. 19 | @inlinable 20 | static var identity: CATransform3D { 21 | return CATransform3DIdentity 22 | } 23 | 24 | /// Is `true` if this transform is the identity transformation. 25 | @inlinable 26 | var isIdentity: Bool { 27 | return CATransform3DIsIdentity(self) 28 | } 29 | 30 | /// Is `true` if this transform can be represented exactly by an affine transform. 31 | @inlinable 32 | var isAffine: Bool { 33 | return CATransform3DIsAffine(self) 34 | } 35 | 36 | /// The affine transform represented by this transform. 37 | /// 38 | /// If this transform cannot be 39 | /// represented exactly by an affine transform, the value is 40 | /// `nil`. 41 | @inlinable 42 | var affine: CGAffineTransform? { 43 | guard isAffine else { 44 | return nil 45 | } 46 | return CATransform3DGetAffineTransform(self) 47 | } 48 | 49 | /// Returns a transform that translates by `(tx, ty, tz)`: 50 | /// **t' = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]**. 51 | @inlinable 52 | init(translateTx tx: CGFloat, ty: CGFloat, tz: CGFloat) { 53 | self = CATransform3DMakeTranslation(tx, ty, tz) 54 | } 55 | 56 | /// Creates a transform that scales by `(sx, sy, sz)`. 57 | /// 58 | /// `self = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]`. 59 | @inlinable 60 | init(scaleSx sx: CGFloat, sy: CGFloat, sz: CGFloat) { 61 | self = CATransform3DMakeScale(sx, sy, sz) 62 | } 63 | 64 | /// Creates a transform that rotates by `angle` radians about the vector 65 | /// `(x, y, z)`. 66 | /// 67 | /// If the vector has length zero, the identity transform is 68 | /// created. 69 | @inlinable 70 | init(rotateAngle angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) { 71 | self = CATransform3DMakeRotation(angle, x, y, z) 72 | } 73 | 74 | /// Translate this transform by `(tx, ty, tz)`: 75 | /// **self = translate(tx, ty, tz) * self**. 76 | @inlinable 77 | mutating func translate(tx: CGFloat, ty: CGFloat, tz: CGFloat) { 78 | self = CATransform3DTranslate(self, tx, ty, tz) 79 | } 80 | 81 | /// Translate this transform by `(tx, ty, tz)` and return the result: 82 | /// **self' = translate(tx, ty, tz) * self**. 83 | @inlinable func translated(tx: CGFloat, ty: CGFloat, tz: CGFloat) -> CATransform3D { 84 | return CATransform3DTranslate(self, tx, ty, tz) 85 | } 86 | 87 | /// Scales this transform by `(sx, sy, sz)`: 88 | /// **self = scale(sx, sy, sz) * self**. 89 | @inlinable 90 | mutating func scale(sx: CGFloat, sy: CGFloat, sz: CGFloat) { 91 | self = CATransform3DScale(self, sx, sy, sz) 92 | } 93 | 94 | /// Scales this transform by `(sx, sy, sz)` and return the result: 95 | /// **self' = scale(sx, sy, sz) * self**. 96 | @inlinable 97 | func scaled(sx: CGFloat, sy: CGFloat, sz: CGFloat) -> CATransform3D { 98 | return CATransform3DScale(self, sx, sy, sz) 99 | } 100 | 101 | 102 | /// Rotates this transform by `angle` radians about the vector `(x, y, z)`. 103 | /// 104 | /// If the vector has zero length the behavior is undefined: 105 | /// **self = rotation(angle, x, y, z) * self**. 106 | @inlinable 107 | mutating func rotate(angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) { 108 | self = CATransform3DRotate(self, angle, x, y, z) 109 | } 110 | 111 | /// Rotate this transform by `angle` radians about the vector `(x, y, z)` and 112 | /// return the result. 113 | /// 114 | /// If the vector has zero length the behavior is undefined: 115 | /// **self' = rotation(angle, x, y, z) * self**. 116 | @inlinable 117 | func rotated(angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) -> CATransform3D { 118 | return CATransform3DRotate(self, angle, x, y, z) 119 | } 120 | 121 | 122 | /// Inverts this transform. 123 | /// 124 | /// Does nothing if there's no inverse. 125 | @inlinable 126 | mutating func invert() { 127 | self = CATransform3DInvert(self) 128 | } 129 | 130 | /// Inverts this transform and return the result. 131 | /// 132 | /// Returns `self` if there's no inverse. 133 | @inlinable 134 | var inverted: CATransform3D { 135 | return CATransform3DInvert(self) 136 | } 137 | } 138 | 139 | extension CATransform3D: @retroactive Equatable { 140 | /// Concatenate `rhs` to `lhs` and sets `lhs` to the result: **lhs = lhs * rhs**. 141 | @inlinable public static func +=(lhs: inout CATransform3D, rhs: CATransform3D) { 142 | lhs = CATransform3DConcat(lhs, rhs) 143 | } 144 | 145 | /// Concatenate `rhs` to `lhs` and return the result: **t' = lhs * rhs**. 146 | @inlinable public static func +(lhs: CATransform3D, rhs: CATransform3D) -> CATransform3D { 147 | return CATransform3DConcat(lhs, rhs) 148 | } 149 | 150 | @inlinable public static func ==(lhs: CATransform3D, rhs: CATransform3D) -> Bool { 151 | return CATransform3DEqualToTransform(lhs, rhs) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /QuartzAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /QuartzAdditions/QuartzAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // QuartzAdditions.h 3 | // QuartzAdditions 4 | // 5 | // Created by C.W. Betts on 10/21/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for QuartzAdditions. 12 | FOUNDATION_EXPORT double QuartzAdditionsVersionNumber; 13 | 14 | //! Project version string for QuartzAdditions. 15 | FOUNDATION_EXPORT const unsigned char QuartzAdditionsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /QuartzAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QuartzAdditionsTests/QuartzAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuartzAdditionsTests.swift 3 | // QuartzAdditionsTests 4 | // 5 | // Created by C.W. Betts on 10/21/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import QuartzAdditions 11 | 12 | class QuartzAdditionsTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwiftAdditions 2 | ============= 3 | 4 | Swift additions for common Mac OS X data types, including old data types used by the Classic Mac OS (`OSType`, Pascal strings). 5 | 6 | License 7 | == 8 | 9 | SwiftAdditions uses the [zlib License](http://www.zlib.net/zlib_license.html). 10 | -------------------------------------------------------------------------------- /SIMDAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2020 C.W. Betts. All rights reserved. 23 | 24 | 25 | -------------------------------------------------------------------------------- /SIMDAdditions/SIMD2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SIMD2.swift 3 | // SIMDAdditions 4 | // 5 | // Created by C.W. Betts on 9/24/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import simd 11 | 12 | public extension SIMD2 where Scalar: Numeric { 13 | @inlinable func allZero() -> Bool { 14 | return x == 0 && y == 0 15 | } 16 | 17 | @inlinable static func ==(left: SIMD2, right: SIMD2) -> simd_int2 { 18 | return simd_int2(left.x == right.x ? -1: 0, left.y == right.y ? -1: 0) 19 | } 20 | 21 | @inlinable static func !=(left: SIMD2, right: SIMD2) -> simd_int2 { 22 | return simd_int2(left.x != right.x ? -1: 0, left.y != right.y ? -1: 0) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /SIMDAdditions/SIMD3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SIMD3.swift 3 | // SIMDAdditions 4 | // 5 | // Created by C.W. Betts on 9/24/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import simd 11 | 12 | public extension SIMD3 where Scalar: Numeric { 13 | @inlinable func allZero() -> Bool { 14 | return x == 0 && y == 0 && z == 0 15 | } 16 | 17 | @inlinable static func ==(left: SIMD3, right: SIMD3) -> simd_int3 { 18 | return simd_int3(left.x == right.x ? -1: 0, left.y == right.y ? -1: 0, left.z == right.z ? -1: 0) 19 | } 20 | @inlinable static func !=(left: SIMD3, right: SIMD3) -> simd_int3 { 21 | return simd_int3(left.x != right.x ? -1: 0, left.y != right.y ? -1: 0, left.z != right.z ? -1: 0) 22 | } 23 | } 24 | 25 | public extension SIMD3 { 26 | @inlinable var xy: SIMD2 { 27 | get { 28 | return SIMD2(x, y) 29 | } 30 | set { 31 | x = newValue.x 32 | y = newValue.y 33 | } 34 | } 35 | 36 | @inlinable var yz: SIMD2 { 37 | get { 38 | return SIMD2(y, z) 39 | } 40 | set { 41 | y = newValue.x 42 | z = newValue.y 43 | } 44 | } 45 | 46 | @inlinable var xz: SIMD2 { 47 | get { 48 | return SIMD2(x, z) 49 | } 50 | set { 51 | x = newValue.x 52 | z = newValue.y 53 | } 54 | } 55 | 56 | @inlinable var zyx: SIMD3 { 57 | get { 58 | return SIMD3(z, y, x) 59 | } 60 | set { 61 | x = newValue.z 62 | y = newValue.y 63 | z = newValue.x 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SIMDAdditions/SIMD4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SIMD4.swift 3 | // SIMDAdditions 4 | // 5 | // Created by C.W. Betts on 9/24/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import simd 11 | 12 | public extension SIMD4 where Scalar: Numeric { 13 | @inlinable func allZero() -> Bool { 14 | return x == 0 && y == 0 && z == 0 && w == 0 15 | } 16 | 17 | @inlinable static func ==(left: SIMD4, right: SIMD4) -> simd_int4 { 18 | return simd_int4(left.x == right.x ? -1: 0, left.y == right.y ? -1: 0, left.z == right.z ? -1: 0, left.w == right.w ? -1: 0) 19 | } 20 | 21 | @inlinable static func !=(left: SIMD4, right: SIMD4) -> simd_int4 { 22 | return simd_int4(left.x != right.x ? -1: 0, left.y != right.y ? -1: 0, left.z != right.z ? -1: 0, left.w != right.w ? -1: 0) 23 | } 24 | } 25 | 26 | public extension SIMD4 { 27 | @inlinable var xyz: SIMD3 { 28 | get { 29 | return SIMD3(x, y, z) 30 | } 31 | set { 32 | x = newValue.x 33 | y = newValue.y 34 | z = newValue.z 35 | } 36 | } 37 | 38 | @inlinable var xyw: SIMD3 { 39 | get { 40 | return SIMD3(x, y, w) 41 | } 42 | set { 43 | x = newValue.x 44 | y = newValue.y 45 | w = newValue.z 46 | } 47 | } 48 | 49 | @inlinable var wzyx: SIMD4 { 50 | get { 51 | return SIMD4(w, z, y, x) 52 | } 53 | set { 54 | w = newValue.x 55 | z = newValue.y 56 | y = newValue.z 57 | x = newValue.w 58 | } 59 | } 60 | 61 | @inlinable var xzyz: SIMD4 { 62 | get { 63 | return SIMD4(x, z, y, z) 64 | } 65 | } 66 | 67 | @inlinable var xy: SIMD2 { 68 | get { 69 | return SIMD2(x, y) 70 | } 71 | set { 72 | x = newValue.x 73 | y = newValue.y 74 | } 75 | } 76 | 77 | @inlinable var yz: SIMD2 { 78 | get { 79 | return SIMD2(y, z) 80 | } 81 | set { 82 | y = newValue.x 83 | z = newValue.y 84 | } 85 | } 86 | 87 | @inlinable var xz: SIMD2 { 88 | get { 89 | return SIMD2(x, z) 90 | } 91 | set { 92 | x = newValue.x 93 | z = newValue.y 94 | } 95 | } 96 | 97 | @inlinable var zyx: SIMD3 { 98 | get { 99 | return SIMD3(z, y, x) 100 | } 101 | set { 102 | x = newValue.z 103 | y = newValue.y 104 | z = newValue.x 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /SIMDAdditions/SIMDAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIMDAdditions.h 3 | // SIMDAdditions 4 | // 5 | // Created by C.W. Betts on 9/24/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SIMDAdditions. 12 | FOUNDATION_EXPORT double SIMDAdditionsVersionNumber; 13 | 14 | //! Project version string for SIMDAdditions. 15 | FOUNDATION_EXPORT const unsigned char SIMDAdditionsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SIMDAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SIMDAdditionsTests/SIMDAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SIMDAdditionsTests.swift 3 | // SIMDAdditionsTests 4 | // 5 | // Created by C.W. Betts on 9/24/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SIMDAdditions 11 | 12 | class SIMDAdditionsTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /SwiftAdditions.xcodeproj/xcshareddata/xcschemes/All Additions-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /SwiftAdditions.xcodeproj/xcshareddata/xcschemes/ColorSync.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SwiftAdditions.xcodeproj/xcshareddata/xcschemes/CoreTextAdditions.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SwiftAdditions.xcodeproj/xcshareddata/xcschemes/FoundationAdditions.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /SwiftAdditions.xcodeproj/xcshareddata/xcschemes/TISAdditions.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /SwiftAdditions/AppKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppKit.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 2/1/15. 6 | // Copyright (c) 2015 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | 12 | // MARK: CGWindowLevel values 13 | @inlinable public var kCGBaseWindowLevel: CGWindowLevel { 14 | return CGWindowLevelForKey(CGWindowLevelKey.baseWindow) 15 | } 16 | 17 | @inlinable public var kCGMinimumWindowLevel: CGWindowLevel { 18 | return CGWindowLevelForKey(CGWindowLevelKey.minimumWindow) 19 | } 20 | 21 | @inlinable public var kCGDesktopWindowLevel: CGWindowLevel { 22 | return CGWindowLevelForKey(CGWindowLevelKey.desktopWindow) 23 | } 24 | 25 | @inlinable public var kCGMaximumWindowLevel: CGWindowLevel { 26 | return CGWindowLevelForKey(CGWindowLevelKey.maximumWindow) 27 | } 28 | 29 | @inlinable public var kCGDesktopIconWindowLevel: CGWindowLevel { 30 | return CGWindowLevelForKey(CGWindowLevelKey.desktopIconWindow) 31 | } 32 | 33 | @inlinable public var kCGCursorWindowLevel: CGWindowLevel { 34 | return CGWindowLevelForKey(CGWindowLevelKey.cursorWindow) 35 | } 36 | 37 | public extension AffineTransform { 38 | init(cgTransform cgAff: CGAffineTransform) { 39 | self.init(m11: cgAff.a, m12: cgAff.b, m21: cgAff.c, m22: cgAff.d, tX: cgAff.tx, tY: cgAff.ty) 40 | } 41 | 42 | var cgTransform: CGAffineTransform { 43 | return CGAffineTransform(a: m11, b: m12, c: m21, d: m22, tx: tX, ty: tY) 44 | } 45 | } 46 | 47 | public extension NSAffineTransform { 48 | convenience init(cgTransform: CGAffineTransform) { 49 | self.init() 50 | transformStruct = NSAffineTransformStruct(m11: cgTransform.a, m12: cgTransform.b, m21: cgTransform.c, m22: cgTransform.d, tX: cgTransform.tx, tY: cgTransform.ty) 51 | } 52 | 53 | var cgTransform: CGAffineTransform { 54 | let theStruct = transformStruct 55 | return CGAffineTransform(a: theStruct.m11, b: theStruct.m12, c: theStruct.m21, d: theStruct.m22, tx: theStruct.tX, ty: theStruct.tY) 56 | } 57 | } 58 | 59 | public extension NSBitmapImageRep { 60 | /// Returns a buffer of all available compression types that can be used when writing 61 | /// a TIFF image. 62 | /// 63 | /// This is an `UnsafeBufferPointer` of `NSBitmapImageRep.TIFFCompression` constants. 64 | /// This buffer belongs to the `NSBitmapImageRep` class; it shouldn’t be freed or 65 | /// altered. See `NSBitmapImageRep.TIFFCompression` for the supported TIFF 66 | /// compression types. 67 | /// 68 | /// Note that not all compression types can be used for all images: 69 | /// `NSBitmapImageRep.TIFFCompression.next` can be used only to retrieve image data. 70 | /// Because future releases may include other compression types, always use this 71 | /// method to get the available compression types—for example, when you implement a 72 | /// user interface for selecting compression types. 73 | static var tiffCompressionTypes: UnsafeBufferPointer { 74 | var compPtr: UnsafePointer? = nil 75 | var count = 0 76 | getTIFFCompressionTypes(&compPtr, count: &count) 77 | 78 | let bufPtr = UnsafeBufferPointer(start: compPtr, count: count) 79 | return bufPtr 80 | } 81 | } 82 | 83 | public extension NSBitmapImageRep.Format { 84 | /// The native 32-bit byte order format. 85 | @inlinable static var thirtyTwoBitNativeEndian: NSBitmapImageRep.Format { 86 | #if _endian(little) 87 | return .thirtyTwoBitLittleEndian 88 | #elseif _endian(big) 89 | return .thirtyTwoBitBigEndian 90 | #else 91 | fatalError("Unknown endianness") 92 | #endif 93 | } 94 | 95 | /// The native 16-bit byte order format. 96 | @inlinable static var sixteenBitNativeEndian: NSBitmapImageRep.Format { 97 | #if _endian(little) 98 | return .sixteenBitLittleEndian 99 | #elseif _endian(big) 100 | return .sixteenBitBigEndian 101 | #else 102 | fatalError("Unknown endianness") 103 | #endif 104 | } 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /SwiftAdditions/AppleScriptFoundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleScriptFoundation.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 2/4/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(OSX) 12 | private func getError(dict: NSDictionary?) -> Error { 13 | if var dict = dict as? [String : Any] { 14 | let errNum = dict[NSAppleScript.errorNumber] as? Int ?? errOSAScriptError 15 | 16 | dict[NSLocalizedFailureReasonErrorKey] = dict[NSAppleScript.errorMessage] 17 | dict[NSDebugDescriptionErrorKey] = dict[NSAppleScript.errorMessage] 18 | dict[NSLocalizedDescriptionKey] = dict[NSAppleScript.errorBriefMessage] 19 | return NSError(domain: NSOSStatusErrorDomain, code: errNum, userInfo: dict) 20 | } else { 21 | return SAMacError(.osaScriptError) 22 | } 23 | } 24 | 25 | public extension NSAppleScript { 26 | /// Creates a newly allocated script instance from the source identified by the passed URL. 27 | /// - parameter url: A URL that locates a script, in either text or compiled form. 28 | /// - throws: an `NSError` in the `NSOSStatusErrorDomain` domain 29 | /// if unsuccessful. If you need to get the dictionary that would 30 | /// have been returned by `compileAndReturnError(_:)`, the values 31 | /// are stored in the `NSError`'s `userInfo`. 32 | @nonobjc static func appleScript(contentsOf url: URL) throws -> NSAppleScript { 33 | var errDict: NSDictionary? 34 | if let hi = NSAppleScript(contentsOf: url, error: &errDict) { 35 | return hi 36 | } 37 | throw getError(dict: errDict) 38 | } 39 | 40 | /// Compile the script, if it is not already compiled. 41 | /// - throws: an `NSError` in the `NSOSStatusErrorDomain` domain 42 | /// if unsuccessful. If you need to get the dictionary that would 43 | /// have been returned by `compileAndReturnError(_:)`, the values 44 | /// are stored in the `NSError`'s `userInfo`. 45 | @nonobjc func compile() throws { 46 | var errDict: NSDictionary? 47 | if !compileAndReturnError(&errDict) { 48 | throw getError(dict: errDict) 49 | } 50 | } 51 | 52 | /// Execute the script, compiling it first if it is not already compiled. 53 | /// - returns: the result of executing the script. 54 | /// - throws: an `NSError` in the `NSOSStatusErrorDomain` domain on 55 | /// failure. If you need to get the dictionary that would have 56 | /// been returned by `executeAndReturnError(_:)`, the values are stored 57 | /// in the `NSError`'s `userInfo`. 58 | @nonobjc func execute() throws -> NSAppleEventDescriptor { 59 | var errDict: NSDictionary? 60 | if let descriptor = executeAndReturnError(&errDict) as NSAppleEventDescriptor? { 61 | return descriptor 62 | } else { 63 | throw getError(dict: errDict) 64 | } 65 | } 66 | 67 | /// Execute an Apple event in the context of the script, compiling 68 | /// the script first if it is not already compiled. 69 | /// - parameter event: The Apple event to execute. 70 | /// - returns: the result of executing the event. 71 | /// - throws: an `NSError` in the `NSOSStatusErrorDomain` domain 72 | /// if an error occurs. If you need to get the dictionary that would 73 | /// have been returned by `executeAppleEvent(_:error:)`, the values 74 | /// are stored in the `NSError`'s `userInfo`. 75 | @nonobjc func execute(event: NSAppleEventDescriptor) throws -> NSAppleEventDescriptor { 76 | var errDict: NSDictionary? 77 | if let descriptor = executeAppleEvent(event, error: &errDict) as NSAppleEventDescriptor? { 78 | return descriptor 79 | } else { 80 | throw getError(dict: errDict) 81 | } 82 | } 83 | } 84 | #endif 85 | -------------------------------------------------------------------------------- /SwiftAdditions/CGColorSpace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGColorSpace.swift 3 | // QuartzAdditions 4 | // 5 | // Created by C.W. Betts on 7/24/22. 6 | // Copyright © 2022 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics.CGColorSpace 11 | 12 | public extension CGColorSpace { 13 | //CGColorSpaceUsesITUR_2100TF 14 | /// Return true if color space uses transfer functions defined in **ITU Rec.2100**. 15 | @available(macOS 11.0, iOS 14.0, tvOS 14.0, *) 16 | @inlinable var usesITUR_2100TF: Bool { 17 | return CGColorSpaceUsesITUR_2100TF(self) 18 | } 19 | 20 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, *) 21 | @inlinable var isPQBased: Bool { 22 | return CGColorSpaceIsPQBased(self) 23 | } 24 | 25 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, *) 26 | @inlinable var isHLGBased: Bool { 27 | return CGColorSpaceIsHLGBased(self) 28 | } 29 | 30 | @available(macOS 10.12, iOS 10.0, tvOS 10.0, *) 31 | @inlinable var usesExtendedRange: Bool { 32 | return CGColorSpaceUsesExtendedRange(self) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SwiftAdditions/Characters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Characters.swift 3 | // BlastApp 4 | // 5 | // Created by C.W. Betts on 9/22/15. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Based off of the ASCII code tables 12 | public enum ASCIICharacter: Int8, Comparable, Hashable, Sendable { 13 | // MARK: non-visible characters 14 | // TODO: add info for each value. 15 | case nullCharacter = 0 16 | case startOfHeader 17 | case startOfText 18 | case endOfText 19 | case endOfTransmission 20 | case enquiry 21 | case acknowledgement 22 | case bell 23 | case backspace 24 | case horizontalTab 25 | /// new-line in Unix files, commonly represented as `'\n'` 26 | /// in C-like languages. 27 | /// 28 | /// Hex code: `0x0A` 29 | case lineFeed = 0x0A 30 | case verticalTab 31 | case formFeed 32 | case carriageReturn 33 | case shiftOut 34 | case shiftIn 35 | case dataLineEscape 36 | case deviceControl1 37 | case deviceControl2 38 | case deviceControl3 39 | case deviceControl4 40 | case negativeAcknowledgement 41 | case synchronousIdle 42 | case endOfTransmitBlock = 0x17 43 | case cancel 44 | case endOfMedium 45 | case substitute 46 | case escape 47 | case fileSeperator 48 | case groupSeperator 49 | case recordSeperator 50 | case unitSeperator 51 | 52 | // MARK: visible characters 53 | /// 0x20 54 | case space = 0x20 55 | case exclamationMark = 0x21 56 | case doubleQuote 57 | case numberSign 58 | case dollarSign 59 | case percentSign 60 | case apersand 61 | case singleQuote 62 | case openParenthesis 63 | case closeParenthesis 64 | case asterisk 65 | case plusSign 66 | case comma 67 | case hyphen 68 | case period 69 | case slash 70 | case numberZero = 0x30 71 | case numberOne 72 | case numberTwo 73 | case numberThree 74 | case numberFour 75 | case numberFive 76 | case numberSix 77 | case numberSeven 78 | case numberEight 79 | case numberNine 80 | case colon = 0x3A 81 | case semicolon 82 | case lessThan 83 | case equals 84 | case greaterThan 85 | case questionMark 86 | case atSymbol 87 | 88 | case letterUppercaseA = 0x41 89 | case letterUppercaseB 90 | case letterUppercaseC 91 | case letterUppercaseD 92 | case letterUppercaseE 93 | case letterUppercaseF 94 | case letterUppercaseG 95 | case letterUppercaseH 96 | case letterUppercaseI 97 | case letterUppercaseJ 98 | case letterUppercaseK 99 | case letterUppercaseL 100 | case letterUppercaseM 101 | case letterUppercaseN 102 | case letterUppercaseO 103 | case letterUppercaseP 104 | case letterUppercaseQ 105 | case letterUppercaseR 106 | case letterUppercaseS 107 | case letterUppercaseT 108 | case letterUppercaseU 109 | case letterUppercaseV 110 | case letterUppercaseW 111 | case letterUppercaseX 112 | case letterUppercaseY 113 | case letterUppercaseZ 114 | 115 | case openingBracket 116 | case backSlashCharacter 117 | case closingBracket 118 | case caretCharacter 119 | case underscoreCharacter 120 | case graveAccent 121 | 122 | case letterLowercaseA = 0x61 123 | case letterLowercaseB 124 | case letterLowercaseC 125 | case letterLowercaseD 126 | case letterLowercaseE 127 | case letterLowercaseF 128 | case letterLowercaseG 129 | case letterLowercaseH 130 | case letterLowercaseI 131 | case letterLowercaseJ 132 | case letterLowercaseK 133 | case letterLowercaseL 134 | case letterLowercaseM 135 | case letterLowercaseN 136 | case letterLowercaseO 137 | case letterLowercaseP 138 | case letterLowercaseQ 139 | case letterLowercaseR 140 | case letterLowercaseS 141 | case letterLowercaseT 142 | case letterLowercaseU 143 | case letterLowercaseV 144 | case letterLowercaseW 145 | case letterLowercaseX 146 | case letterLowercaseY 147 | case letterLowercaseZ 148 | 149 | case openingBrace = 0x7B 150 | case verticalBar 151 | case closingBrace 152 | case tildeCharacter 153 | case deleteCharacter 154 | 155 | /// Value is not valid ASCII 156 | case invalid = -1 157 | 158 | static public func <(lhs: ASCIICharacter, rhs: ASCIICharacter) -> Bool { 159 | return lhs.rawValue < rhs.rawValue 160 | } 161 | } 162 | 163 | public extension ASCIICharacter { 164 | /// Takes a Swift `Character` and returns an ASCII character/code. 165 | /// Returns `nil` if the value can't be represented in ASCII. 166 | init?(swiftCharacter: Character) { 167 | let srrChar = swiftCharacter.unicodeScalars 168 | guard srrChar.count == 1, 169 | let ourChar = srrChar.last, 170 | ourChar.isASCII else { 171 | return nil 172 | } 173 | 174 | self = ASCIICharacter(rawValue: Int8(ourChar.value))! 175 | } 176 | 177 | /// Takes a C-style char value and maps it to the ASCII table.
178 | /// Returns `nil` if the value can't be represented as ASCII. 179 | init?(cCharacter: Int8) { 180 | guard let aChar = ASCIICharacter(rawValue: cCharacter) else { 181 | return nil 182 | } 183 | self = aChar 184 | } 185 | 186 | /// Takes a C-style char value and maps it to the ASCII table. 187 | /// Returns `nil` if the value can't be represented as ASCII. 188 | init?(cCharacter cch: UInt8) { 189 | self.init(cCharacter: Int8(bitPattern: cch)) 190 | } 191 | 192 | 193 | /// Returns a Swift `Character` representing the current enum value. 194 | /// Returns a blank replacement character (**0xFFFD**) if not a valid ASCII value. 195 | var characterValue: Character { 196 | let numVal = self.rawValue 197 | guard numVal >= 0 else { 198 | return "\u{FFFD}" 199 | } 200 | 201 | return Character(Unicode.Scalar(UInt8(numVal))) 202 | } 203 | } 204 | 205 | public extension String { 206 | /// Creates a string from a sequence of `ASCIICharacter`s. 207 | @inlinable init(asciiCharacters: A) where A.Element == ASCIICharacter { 208 | let asciiCharMap = asciiCharacters.map { (cha) -> Character in 209 | return cha.characterValue 210 | } 211 | self = String(asciiCharMap) 212 | } 213 | 214 | /// Converts the string to an array of `ASCIICharacter`s. 215 | /// - parameter encodeInvalid: If `true`, any character that can't be represented as 216 | /// an ASCII character is instead replaced with `ASCIICharacter.invalid` 217 | /// instead of stopping and returning `nil`. 218 | /// - returns: An array of `ASCIICharacter`s, or `nil` if there is a non-ASCII 219 | /// character and `encodeInvalid` is `false`. 220 | func toASCIICharacters(encodeInvalid: Bool = false) -> [ASCIICharacter]? { 221 | if encodeInvalid { 222 | return self.map({ (aChar) -> ASCIICharacter in 223 | return ASCIICharacter(swiftCharacter: aChar) ?? .invalid 224 | }) 225 | } 226 | guard var asciis = self.cString(using: String.Encoding.ascii) else { 227 | return nil 228 | } 229 | 230 | // Remove null termination. 231 | asciis.removeLast() 232 | return asciis.map({ (aChar) -> ASCIICharacter in 233 | return ASCIICharacter(cCharacter: aChar)! 234 | }) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /SwiftAdditions/CoreGraphics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreGraphics.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 11/3/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import FoundationAdditions 12 | 13 | public extension CGBitmapInfo { 14 | /// The alpha info of the current `CGBitmapInfo`. 15 | var alphaInfo: CGImageAlphaInfo { 16 | get { 17 | let tmpInfo = (self.rawValue & CGBitmapInfo.alphaInfoMask.rawValue) 18 | return CGImageAlphaInfo(rawValue: tmpInfo) ?? .none 19 | } 20 | set { 21 | let aRaw = newValue.rawValue 22 | 23 | //Clear the alpha info 24 | self.remove(CGBitmapInfo.alphaInfoMask) 25 | 26 | let toMerge = CGBitmapInfo(rawValue: aRaw) 27 | insert(toMerge) 28 | } 29 | } 30 | 31 | /// Inits a `CGBitmapInfo` value from a `CGImageAlphaInfo`. 32 | init(alphaInfo: CGImageAlphaInfo) { 33 | let ordValue = alphaInfo.rawValue 34 | self = CGBitmapInfo(rawValue: ordValue) 35 | } 36 | 37 | /// The native 16-bit byte order format. 38 | @inlinable static var byteOrder16Host: CGBitmapInfo { 39 | #if _endian(little) 40 | return .byteOrder16Little 41 | #elseif _endian(big) 42 | return .byteOrder16Big 43 | #else 44 | fatalError("Unknown endianness") 45 | #endif 46 | } 47 | 48 | /// The native 32-bit byte order format. 49 | @inlinable static var byteOrder32Host: CGBitmapInfo { 50 | #if _endian(little) 51 | return .byteOrder32Little 52 | #elseif _endian(big) 53 | return .byteOrder32Big 54 | #else 55 | fatalError("Unknown endianness") 56 | #endif 57 | } 58 | } 59 | 60 | extension CGFont: @retroactive CFTypeProtocol {} 61 | extension CGImage: @retroactive CFTypeProtocol {} 62 | extension CGLayer: @retroactive CFTypeProtocol {} 63 | extension CGPath: @retroactive CFTypeProtocol {} 64 | extension CGPattern: @retroactive CFTypeProtocol {} 65 | extension CGShading: @retroactive CFTypeProtocol {} 66 | extension CGColor: @retroactive CFTypeProtocol {} 67 | extension CGColorConversionInfo: @retroactive CFTypeProtocol {} 68 | extension CGColorSpace: @retroactive CFTypeProtocol {} 69 | extension CGContext: @retroactive CFTypeProtocol {} 70 | extension CGDataConsumer: @retroactive CFTypeProtocol {} 71 | extension CGDataProvider: @retroactive CFTypeProtocol {} 72 | extension CGFunction: @retroactive CFTypeProtocol {} 73 | extension CGGradient: @retroactive CFTypeProtocol {} 74 | extension CGPDFPage: @retroactive CFTypeProtocol {} 75 | extension CGPDFDocument: @retroactive CFTypeProtocol {} 76 | 77 | #if os(OSX) 78 | extension CGPSConverter: @retroactive CFTypeProtocol {} 79 | extension CGEventSource: @retroactive CFTypeProtocol {} 80 | extension CGDisplayMode: @retroactive CFTypeProtocol {} 81 | extension CGDisplayStream: @retroactive CFTypeProtocol {} 82 | extension CGEvent: @retroactive CFTypeProtocol {} 83 | #endif 84 | -------------------------------------------------------------------------------- /SwiftAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleSignature 6 | ???? 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftAdditions/SAMacError.h: -------------------------------------------------------------------------------- 1 | // 2 | // SAMacError.h 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 10/10/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #ifndef SAMacError_h 10 | #define SAMacError_h 11 | 12 | #import 13 | #import 14 | #if TARGET_OS_OSX 15 | #include 16 | #else 17 | #define paramErr -50 18 | #define unimpErr -4 19 | #define fnfErr -43 20 | #define permErr -54 21 | #define tmfoErr -42 22 | #define memFullErr -108 23 | #define fnOpnErr -38 24 | #define eofErr -39 25 | #define posErr -40 26 | #define nsvErr -35 27 | #define fLckdErr -45 28 | #define userCanceledErr -128 29 | #define dirNFErr -120 30 | #endif 31 | 32 | #ifndef MTS_ERROR_ENUM 33 | #define __MTS_ERROR_ENUM_GET_MACRO(_0, _1, _2, NAME, ...) NAME 34 | #if ((__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))) && __has_attribute(ns_error_domain) 35 | #define __MTS_NAMED_ERROR_ENUM(_type, _domain, _name) enum _name : _type _name; enum __attribute__((ns_error_domain(_domain))) _name : _type 36 | #define __MTS_ANON_ERROR_ENUM(_type, _domain) enum __attribute__((ns_error_domain(_domain))) : _type 37 | #else 38 | #define __MTS_NAMED_ERROR_ENUM(_type, _domain, _name) NS_ENUM(_type, _name) 39 | #define __MTS_ANON_ERROR_ENUM(_type, _domain) NS_ENUM(_type) 40 | #endif 41 | 42 | #define MTS_ERROR_ENUM(...) __MTS_ERROR_ENUM_GET_MACRO(__VA_ARGS__, __MTS_NAMED_ERROR_ENUM, __MTS_ANON_ERROR_ENUM)(__VA_ARGS__) 43 | #endif 44 | 45 | //! Common Carbon error codes 46 | //! 47 | //! This list is in no way, shape, or form exhaustive! A lot of the other 48 | //! errors make no sense under Mac OS X but were needed for pre-OS X systems. 49 | typedef MTS_ERROR_ENUM(OSStatus, NSOSStatusErrorDomain, SAMacError) { 50 | /*! error in user parameter list */ 51 | SAMacErrorParameter = paramErr, 52 | /*! unimplemented core routine */ 53 | SAMacErrorUnimplemented = unimpErr, 54 | /*! File not found */ 55 | SAMacErrorFileNotFound = fnfErr, 56 | /*! permissions error (on file open) */ 57 | SAMacErrorFilePermission = permErr, 58 | /*! too many files open */ 59 | SAMacErrorTooManyFilesOpen = tmfoErr, 60 | /*! Not enough room in heap zone */ 61 | SAMacErrorMemoryFull = memFullErr, 62 | /*! File not open */ 63 | SAMacErrorFileNotOpen = fnOpnErr, 64 | /*! End of file */ 65 | SAMacErrorEndOfFile = eofErr, 66 | /*! tried to position to before start of file (r/w) */ 67 | SAMacErrorFilePosition = posErr, 68 | /*! no such volume */ 69 | SAMacErrorNoSuchVolume = nsvErr, 70 | /*! file is locked */ 71 | SAMacErrorFileLocked = fLckdErr, 72 | /*! User cancelled action */ 73 | SAMacErrorUserCancelled = userCanceledErr, 74 | /*! Directory not found */ 75 | SAMacErrorDirectoryNotFound = dirNFErr, 76 | 77 | #if TARGET_OS_OSX 78 | SAMacErrorOSASystemError = -1750, 79 | SAMacErrorOSAInvalidID = -1751, 80 | SAMacErrorOSABadStorageType = -1752, 81 | SAMacErrorOSAScriptError = -1753, 82 | SAMacErrorOSABadSelector = -1754, 83 | SAMacErrorOSASourceNotAvailable = -1756, 84 | SAMacErrorOSANoSuchDialect = -1757, 85 | SAMacErrorOSADataFormatObsolete = -1758, 86 | SAMacErrorOSADataFormatTooNew = -1759, 87 | SAMacErrorOSACorruptData = -1702, 88 | SAMacErrorOSARecordingIsAlreadyOn = -1732, 89 | 90 | /*! Parameters are from 2 different components */ 91 | SAMacErrorOSAComponentMismatch = -1761, 92 | /*! Can't connect to scripting system with that ID */ 93 | SAMacErrorOSACantOpenComponent = -1762, 94 | /*! Can't store memory pointers in a saved script */ 95 | SAMacErrorOSACantStorePointers = -1763 96 | #endif 97 | }; 98 | 99 | #endif /* SAMacError_h */ 100 | -------------------------------------------------------------------------------- /SwiftAdditions/SAMacError.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAMacError.c 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 10/10/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #include "SAMacError.h" 10 | -------------------------------------------------------------------------------- /SwiftAdditions/SAMacError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SAMacError.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 10/10/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if SWIFT_PACKAGE 12 | 13 | /// Common Carbon error codes 14 | /// 15 | /// This list is in no way, shape, or form exhaustive! A lot of the other 16 | /// errors make no sense under Mac OS X but were needed for pre-OS X systems. 17 | public struct SAMacError: Error, _BridgedStoredNSError { 18 | public let _nsError: NSError 19 | 20 | public init(_nsError: NSError) { 21 | precondition(_nsError.domain == NSOSStatusErrorDomain) 22 | self._nsError = _nsError 23 | } 24 | 25 | public static var errorDomain: String { return NSOSStatusErrorDomain } 26 | 27 | /// Common Carbon error codes 28 | /// 29 | /// This list is in no way, shape, or form exhaustive! A lot of the other 30 | /// errors make no sense under Mac OS X but were needed for pre-OS X systems. 31 | public enum Code: OSStatus, _ErrorCodeProtocol, Hashable, @unchecked Sendable, Equatable { 32 | public typealias _ErrorType = SAMacError 33 | 34 | /// error in user parameter list 35 | case parameter = -50 36 | 37 | /// unimplemented core routine 38 | case unimplemented = -4 39 | 40 | /// File not found 41 | case fileNotFound = -43 42 | 43 | /// permissions error (on file open) 44 | case filePermission = -54 45 | 46 | /// too many files open 47 | case tooManyFilesOpen = -42 48 | 49 | /// Not enough room in heap zone 50 | case memoryFull = -108 51 | 52 | /// File not open 53 | case fileNotOpen = -38 54 | 55 | /// End of file 56 | case endOfFile = -39 57 | 58 | /// tried to position to before start of file (r/w) 59 | case filePosition = -40 60 | 61 | /// no such volume 62 | case noSuchVolume = -35 63 | 64 | /// file is locked 65 | case fileLocked = -45 66 | 67 | /// User cancelled action 68 | case userCancelled = -128 69 | 70 | /// Directory not found 71 | case directoryNotFound = -120 72 | 73 | #if os(OSX) 74 | case osaSystemError = -1750 75 | case osaInvalidID = -1751 76 | case osaBadStorageType = -1752 77 | case osaScriptError = -1753 78 | case osaBadSelector = -1754 79 | case osaSourceNotAvailable = -1755 80 | case osaNoSuchDialect = -1756 81 | case osaDataFormatObsolete = -1758 82 | case osaDataFormatTooNew = -1759 83 | case osaCorruptData = -1702 84 | case osaRecordingIsAlreadyOn = -1732 85 | 86 | /// Parameters are from 2 different components 87 | case osaComponentMismatch = -1761 88 | 89 | /// Can't connect to scripting system with that ID 90 | case osaCantOpenComponent = -1762 91 | 92 | /// Can't store memory pointers in a saved script 93 | case osaCantStorePointers = -1763 94 | #endif 95 | } 96 | 97 | /// error in user parameter list 98 | public static var parameter: SAMacError.Code { return .parameter } 99 | 100 | /// unimplemented core routine 101 | public static var unimplemented: SAMacError.Code { return .unimplemented } 102 | 103 | /// File not found 104 | public static var fileNotFound: SAMacError.Code { return .fileNotFound } 105 | 106 | /// permissions error (on file open) 107 | public static var filePermission: SAMacError.Code { return .filePermission } 108 | 109 | /// too many files open 110 | public static var tooManyFilesOpen: SAMacError.Code { return .tooManyFilesOpen } 111 | 112 | /// Not enough room in heap zone 113 | public static var memoryFull: SAMacError.Code { return .memoryFull } 114 | 115 | /// File not open 116 | public static var fileNotOpen: SAMacError.Code { return .fileNotOpen } 117 | 118 | /// End of file 119 | public static var endOfFile: SAMacError.Code { return .endOfFile } 120 | 121 | /// tried to position to before start of file (r/w) 122 | public static var filePosition: SAMacError.Code { return .filePosition } 123 | 124 | /// no such volume 125 | public static var noSuchVolume: SAMacError.Code { return .noSuchVolume } 126 | 127 | /// file is locked 128 | public static var fileLocked: SAMacError.Code { return .fileLocked } 129 | 130 | /// User cancelled action 131 | public static var userCancelled: SAMacError.Code { return .userCancelled } 132 | 133 | /// Directory not found 134 | public static var directoryNotFound: SAMacError.Code { return .directoryNotFound } 135 | 136 | #if os(OSX) 137 | public static var osaSystemError: SAMacError.Code { return .osaSystemError } 138 | 139 | public static var osaInvalidID: SAMacError.Code { return .osaInvalidID } 140 | 141 | public static var osaBadStorageType: SAMacError.Code { return .osaBadStorageType } 142 | 143 | public static var osaScriptError: SAMacError.Code { return .osaScriptError } 144 | 145 | public static var osaBadSelector: SAMacError.Code { return .osaBadSelector } 146 | 147 | public static var osaSourceNotAvailable: SAMacError.Code { return .osaSourceNotAvailable } 148 | 149 | public static var osaNoSuchDialect: SAMacError.Code { return .osaNoSuchDialect } 150 | 151 | public static var osaDataFormatObsolete: SAMacError.Code { return .osaDataFormatObsolete } 152 | 153 | public static var osaDataFormatTooNew: SAMacError.Code { return .osaDataFormatTooNew } 154 | 155 | public static var osaCorruptData: SAMacError.Code { return .osaCorruptData } 156 | 157 | public static var osaRecordingIsAlreadyOn: SAMacError.Code { return .osaRecordingIsAlreadyOn } 158 | 159 | /// Parameters are from 2 different components 160 | public static var osaComponentMismatch: SAMacError.Code { return .osaComponentMismatch } 161 | 162 | /// Can't connect to scripting system with that ID 163 | public static var osaCantOpenComponent: SAMacError.Code { return .osaCantOpenComponent } 164 | 165 | /// Can't store memory pointers in a saved script 166 | public static var osaCantStorePointers: SAMacError.Code { return .osaCantStorePointers } 167 | #endif 168 | } 169 | #endif 170 | 171 | public extension SAMacError.Code { 172 | /// is `nil` if value cannot fit into `OSErr`. 173 | var toOSErr: OSErr? { 174 | return OSErr(exactly: rawValue) 175 | } 176 | 177 | init?(osErrValue val: OSErr) { 178 | self.init(rawValue: OSStatus(val)) 179 | } 180 | } 181 | 182 | public extension SAMacError { 183 | /// This creates an error based on the `status` and anything passed into the `userInfo` dictionary. 184 | /// 185 | /// `SAMacError` is not exhaustive: not every Mac OS 9/Carbon error is used! 186 | /// Catching `SAMacError` may also catch error values that aren't included in 187 | /// the `SAMacError.Code` enum. 188 | /// - parameter userInfo: Additional user info dictionary. Optional, default value is an 189 | /// empty dictionary. 190 | /// - parameter status: The `OSStatus` to create an error in the `NSOSStatusErrorDomain` error domain. 191 | /// - returns: An object conforming to the `Error` protocol, either `SAMacError` or `NSError`. 192 | static func osStatus(_ status: OSStatus, userInfo: [String: Any] = [:]) -> Error { 193 | if let saCode = SAMacError.Code(rawValue: status) { 194 | return SAMacError(saCode, userInfo: userInfo) 195 | } 196 | return NSError(domain: NSOSStatusErrorDomain, code: Int(status), userInfo: userInfo) 197 | } 198 | 199 | /// This creates an error based on the `status` and anything passed into the `userInfo` dictionary. 200 | /// 201 | /// `SAMacError` is not exhaustive: not every Mac OS 9/Carbon error is used! 202 | /// Catching `SAMacError` may also catch error values that aren't included in 203 | /// the `SAMacError.Code` enum. 204 | /// 205 | /// `OSErr`s are returned by older APIs. These APIs may be deprecated and not available to 206 | /// Swift. 207 | /// - parameter userInfo: Additional user info dictionary. Optional, default value is a 208 | /// blank dictionary. 209 | /// - parameter status: The `OSErr` to create an error in the `NSOSStatusErrorDomain` error domain. 210 | /// - returns: An object conforming to the `Error` protocol, either `SAMacError` or `NSError`. 211 | static func osErr(_ status: OSErr, userInfo: [String: Any] = [:]) -> Error { 212 | if let saCode = SAMacError.Code(osErrValue: status) { 213 | return SAMacError(saCode, userInfo: userInfo) 214 | } 215 | return NSError(domain: NSOSStatusErrorDomain, code: Int(status), userInfo: userInfo) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /SwiftAdditions/SwiftAdditions.h: -------------------------------------------------------------------------------- 1 | /* SwiftAdditions.h -- interface of the 'SwiftAdditions' general purpose framework 2 | version 1.0.0, November 9th, 2014 3 | 4 | Copyright (C) 2014 C.W. "Madd the Sane" Betts 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, 11 | including commercial applications, and to alter it and redistribute it 12 | freely, subject to the following restrictions: 13 | 14 | 1. The origin of this software must not be misrepresented; you must not 15 | claim that you wrote the original software. If you use this software 16 | in a product, an acknowledgment in the product documentation would be 17 | appreciated but is not required. 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 3. This notice may not be removed or altered from any source distribution. 21 | 22 | C.W. Betts 23 | computers57@hotmail.com 24 | 25 | */ 26 | 27 | #import 28 | 29 | //! Project version number for SwiftAdditions. 30 | FOUNDATION_EXPORT double SwiftAdditionsVersionNumber; 31 | 32 | //! Project version string for SwiftAdditions. 33 | FOUNDATION_EXPORT const unsigned char SwiftAdditionsVersionString[]; 34 | 35 | #import 36 | 37 | -------------------------------------------------------------------------------- /SwiftAdditionsTests/CharacterAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterAdditionsTests.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 6/7/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SwiftAdditions 12 | 13 | class CharacterAdditionsTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testValidASCIIToChars() { 26 | let hi = "Hello".toASCIICharacters() 27 | XCTAssertNotNil(hi) 28 | } 29 | 30 | func testInvalidASCIIToChars() { 31 | let hi = "Héllo".toASCIICharacters(encodeInvalid: true) 32 | XCTAssertNotNil(hi) 33 | if let hi = hi { 34 | let hi2: [ASCIICharacter] = [.letterUppercaseH, .invalid, .letterLowercaseL, .letterLowercaseL, .letterLowercaseO] 35 | XCTAssertEqual(hi, hi2) 36 | } 37 | } 38 | 39 | func testToAsciiChars() { 40 | let hiStr = "Hello" 41 | if let hi1 = hiStr.toASCIICharacters(encodeInvalid: false), 42 | let hi2 = hiStr.toASCIICharacters(encodeInvalid: true) { 43 | XCTAssertEqual(hi1, hi2) 44 | } else { 45 | XCTFail("hi1 and/or hi2 returned nil") 46 | } 47 | 48 | } 49 | 50 | func testValidASCIIFromChars() { 51 | //let hi = [0x22, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x22] 52 | let hi: [ASCIICharacter] = [.doubleQuote, .letterUppercaseH, .letterLowercaseE, .letterLowercaseL, .letterLowercaseL, .letterLowercaseO, .doubleQuote] 53 | let hi2 = String(asciiCharacters: hi) 54 | XCTAssertEqual("\"Hello\"", hi2) 55 | } 56 | 57 | func testInvalidASCIIFromChars() { 58 | //let hi = [0x22, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x22] 59 | let hi: [ASCIICharacter] = [.doubleQuote, .letterUppercaseH, .invalid, .letterLowercaseL, .letterLowercaseL, .letterLowercaseO, .invalid, .invalid, .doubleQuote] 60 | let hi2 = String(asciiCharacters: hi) 61 | XCTAssertEqual("\"H\u{FFFD}llo\u{FFFD}\u{FFFD}\"", hi2) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftAdditionsTests/MacAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacAdditionsTests.swift 3 | // SwiftAdditionsTests 4 | // 5 | // Created by C.W. Betts on 4/4/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SwiftAdditions 12 | import CoreFoundation.CFStringEncodingExt 13 | 14 | class MacAdditionsTests: XCTestCase { 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | super.tearDown() 24 | } 25 | 26 | func testPascalStrings() { 27 | let pStr255: String.PStr255 = (2, 72, 105, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 0, 0, 0) 53 | /// A pascal string that is 64 bytes long, containing at least 63 characters. 54 | let pStr63: String.PStr63 = (2, 72, 105, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 0, 0, 0) 61 | /// A pascal string that is 33 bytes long, containing at least 32 characters. 62 | let pStr32: String.PStr32 = (2, 72, 105, 0, 0, 0, 0, 63 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65 | 0, 0, 0, 0, 0, 0) 66 | /// A pascal string that is 32 bytes long, containing at least 31 characters. 67 | let pStr31: String.PStr31 = (2, 72, 105, 0, 0, 0, 0, 68 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70 | 0, 0, 0, 0, 0) 71 | /// A pascal string that is 28 bytes long, containing at least 27 characters. 72 | let pStr27: String.PStr27 = (2, 72, 105, 0, 0, 0, 0, 73 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75 | 0) 76 | /// A pascal string that is 16 bytes long, containing at least 15 characters. 77 | let pStr15: String.PStr15 = (4, 72, 105, 88, 32, 0, 0, 78 | 0, 0, 0, 0, 0, 0, 0, 0, 0) 79 | /// A pascal string that is 34 bytes long, containing at least 32 characters. 80 | /// 81 | /// The last byte is unused as it was used for padding over a network. 82 | let pStr32Field: String.PStr32Field = (2, 72, 105, 0, 0, 0, 83 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85 | 0, 0, 0, 0, 0, 0, 0, 0) 86 | 87 | var aStr = String(pascalString: pStr255) 88 | XCTAssertNotNil(aStr, "Pascal String '\(pStr255)' invalid") 89 | aStr = String(pascalString: pStr63) 90 | XCTAssertNotNil(aStr, "Pascal String '\(pStr63)' invalid") 91 | aStr = String(pascalString: pStr32) 92 | XCTAssertNotNil(aStr, "Pascal String '\(pStr32)' invalid") 93 | aStr = String(pascalString: pStr31) 94 | XCTAssertNotNil(aStr, "Pascal String '\(pStr31)' invalid") 95 | aStr = String(pascalString: pStr27) 96 | XCTAssertNotNil(aStr, "Pascal String '\(pStr27)' invalid") 97 | aStr = String(pascalString: pStr15) 98 | XCTAssertNotNil(aStr, "Pascal String '\(pStr15)' invalid") 99 | aStr = String(pascalString: pStr32Field) 100 | XCTAssertNotNil(aStr, "Pascal String '\(pStr32Field)' invalid") 101 | } 102 | 103 | func testInvalidPascalStrings() { 104 | let pStr15: String.PStr15 = (255, 72, 105, 0, 0, 0, 0, 105 | 0, 0, 0, 0, 0, 0, 0, 0, 0) 106 | let pStr27: String.PStr27 = (28, 72, 105, 0, 0, 0, 0, 107 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109 | 0) 110 | var aStr = String(pascalString: pStr15) 111 | XCTAssertNil(aStr) 112 | aStr = String(pascalString: pStr27) 113 | XCTAssertNil(aStr) 114 | } 115 | 116 | /* 117 | TODO: Test encodings that Cocoa can decode: 118 | 0 119 | 1 120 | 2 121 | 3 122 | 4 123 | 5 124 | 6 125 | 7 126 | 9 127 | 10 128 | 11 129 | 21 130 | 25 131 | 26 132 | 29 133 | 33 134 | 34 135 | 35 136 | 36 137 | 37 138 | 38 139 | 39 140 | 40 141 | 140 142 | 152 143 | 236 144 | 145 | CFStringBuiltInEncodings.macRoman 146 | CFStringEncodings.macJapanese 147 | CFStringEncodings.macChineseTrad 148 | CFStringEncodings.macKorean 149 | CFStringEncodings.macArabic 150 | CFStringEncodings.macHebrew 151 | CFStringEncodings.macGreek 152 | CFStringEncodings.macCyrillic 153 | CFStringEncodings.macDevanagari 154 | CFStringEncodings.macGurmukhi 155 | CFStringEncodings.macGujarati 156 | CFStringEncodings.macThai 157 | CFStringEncodings.macChineseSimp 158 | CFStringEncodings.macTibetan 159 | CFStringEncodings.macCentralEurRoman 160 | CFStringEncodings.macSymbol 161 | CFStringEncodings.macDingbats 162 | CFStringEncodings.macTurkish 163 | CFStringEncodings.macCroatian 164 | CFStringEncodings.macIcelandic 165 | CFStringEncodings.macRomanian 166 | CFStringEncodings.macCeltic 167 | CFStringEncodings.macGaelic 168 | CFStringEncodings.macFarsi 169 | CFStringEncodings.macUkrainian 170 | CFStringEncodings.macInuit 171 | 172 | 173 | */ 174 | } 175 | -------------------------------------------------------------------------------- /SwiftAdditionsTests/MacErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacErrorTests.swift 3 | // SwiftAdditionsTests 4 | // 5 | // Created by C.W. Betts on 12/15/19. 6 | // Copyright © 2019 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SwiftAdditions 12 | #if !SWIFT_PACKAGE 13 | @testable import SwiftAdditions.SAMacError 14 | #endif 15 | 16 | class MacErrorTests: XCTestCase { 17 | 18 | override func setUp() { 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testCatchableError() { 27 | do { 28 | throw NSError(domain: NSOSStatusErrorDomain, code: -50 /* paramErr */, userInfo: nil) 29 | } catch let error as SAMacError { 30 | XCTAssertEqual(error.code, SAMacError.Code.parameter) 31 | } catch { 32 | XCTFail("unknown error \(error)") 33 | } 34 | } 35 | 36 | func testCatchNSError() { 37 | do { 38 | throw SAMacError(.parameter) 39 | } catch let error as NSError { 40 | XCTAssertEqual(error.domain, NSOSStatusErrorDomain) 41 | XCTAssertEqual(error.code, -50) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftAdditionsTests/SwiftAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftAdditionsTests.swift 3 | // SwiftAdditionsTests 4 | // 5 | // Created by C.W. Betts on 8/22/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SwiftAdditions 12 | @testable import FoundationAdditions 13 | 14 | class SwiftAdditionsTests: XCTestCase { 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | super.tearDown() 24 | } 25 | 26 | func testNSUUIDTranslators() { 27 | let aUUID = NSUUID() 28 | let aCFUUID = aUUID.cfUUID 29 | let bUUID = NSUUID(cfUUID: aCFUUID) 30 | XCTAssertEqual(aUUID, bUUID) 31 | } 32 | 33 | func testUUIDTranslators() { 34 | let aUUID = UUID() 35 | let aCFUUID = aUUID.cfUUID 36 | let bUUID = UUID(cfUUID: aCFUUID) 37 | XCTAssertEqual(aUUID, bUUID) 38 | } 39 | 40 | func testSubStringFunction() { 41 | //Simple ASCII string: both representations should be the same 42 | var testString = "hi How are you today?" 43 | var subString = testString.substringWithLength(utf8: 10) 44 | var subString2 = testString.substringWithLength(utf16: 10) 45 | XCTAssertEqual(subString, subString2) 46 | 47 | //Emoji-heavy string. UTF-16 gets more than UTF-8 48 | testString = "hi 🙃🐱🦄 🌊🎮🎯🚵🏹" 49 | subString = testString.substringWithLength(utf8: 10) 50 | subString2 = testString.substringWithLength(utf16: 10) 51 | XCTAssertNotEqual(subString, subString2) 52 | 53 | //Simple, short non-ASCII string 54 | testString = "Olé" 55 | subString = testString.substringWithLength(utf8: 10) 56 | XCTAssertEqual(testString, subString) 57 | subString2 = testString.substringWithLength(utf16: 10) 58 | XCTAssertEqual(testString, subString2) 59 | XCTAssertEqual(subString2, subString) 60 | 61 | //bounds testing 62 | testString = "Résumé" 63 | subString = testString.substringWithLength(utf8: 6) 64 | subString2 = testString.substringWithLength(utf8: 7) 65 | XCTAssertEqual(subString2, subString) 66 | subString = testString.substringWithLength(utf16: 6) 67 | subString2 = testString.substringWithLength(utf16: 7) 68 | XCTAssertEqual(testString, subString2) 69 | XCTAssertEqual(subString, subString2) 70 | 71 | //Emoji-only string. Should get a blank string back 72 | testString = "🙃🐱🦄 🌊🎮🎯🚵🏹" 73 | subString = testString.substringWithLength(utf8: 2) 74 | subString2 = testString.substringWithLength(utf16: 1) 75 | XCTAssertEqual("", subString) 76 | XCTAssertEqual("", subString2) 77 | } 78 | 79 | func testNSNumberCocoaComparable() { 80 | let num1 = NSNumber(value: 9) 81 | let num2 = NSNumber(value: 9.0) 82 | let num3 = NSNumber(value: 7) 83 | let num4 = NSNumber(value: 8) 84 | var numArr = [num1, num3, num4] 85 | 86 | XCTAssert(num1 >= num2) 87 | XCTAssert(num1 <= num2) 88 | XCTAssert(num1 == num2) 89 | XCTAssert(num1 > num4) 90 | XCTAssertFalse(num1 < num4) 91 | XCTAssert(num1 >= num4) 92 | numArr.sort() 93 | XCTAssertEqual(numArr, [num3, num4, num2]) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/AUOutputBL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AUOutputBL.swift 3 | // PlaySequenceSwift 4 | // 5 | // Created by C.W. Betts on 2/6/18. 6 | // 7 | 8 | import Foundation 9 | import AudioToolbox 10 | 11 | /// Simple Buffer List wrapper targetted to use with retrieving AU output. 12 | /// 13 | /// Simple Buffer List wrapper targetted to use with retrieving AU output. 14 | /// Works in one of two ways (both adjustable)... Can use it with `nil` pointers, or allocate 15 | /// memory to receive the data in. 16 | /// 17 | /// Before using this with any call to `AudioUnitRender`, it needs to be Prepared 18 | /// as some calls to `AudioUnitRender` can reset the ABL. 19 | public class AUOutputBL { 20 | public private(set) var format: AudioStreamBasicDescription 21 | private var bufferMemory: UnsafeMutableRawPointer? = nil 22 | private var bufferList: UnsafeMutableAudioBufferListPointer 23 | private var bufferCount: Int 24 | private var bufferSize: Int 25 | public private(set) var frames: UInt32 26 | 27 | /// This is the constructor that you use. 28 | /// It can't be reset once you've constructed it. 29 | public init(streamDescription inDesc: AudioStreamBasicDescription, frameCount inDefaultNumFrames: UInt32 = 512) { 30 | format = inDesc 31 | bufferSize = 0 32 | bufferCount = format.isInterleaved ? 1 : Int(format.mChannelsPerFrame) 33 | frames = inDefaultNumFrames 34 | bufferList = AudioBufferList.allocate(maximumBuffers: bufferCount) 35 | } 36 | 37 | /// You only need to call this if you want to allocate a buffer list. 38 | /// If you want an empty buffer list, just call `prepare()`. 39 | /// If you want to dispose previously allocted memory, pass in `0`, 40 | /// then you either have an empty buffer list, or you can re-allocate. 41 | /// Memory is kept around if an allocation request is less than what is currently allocated. 42 | public func allocate(frames inNumFrames: UInt32) { 43 | if inNumFrames != 0 { 44 | var nBytes = format.framesToBytes(inNumFrames) 45 | 46 | guard nBytes > allocatedBytes else { 47 | return 48 | } 49 | 50 | // align successive buffers for Altivec and to take alternating 51 | // cache line hits by spacing them by odd multiples of 16 52 | if bufferCount > 1 { 53 | nBytes = (nBytes + (0x10 - (nBytes & 0xF))) | 0x10 54 | } 55 | 56 | bufferSize = Int(nBytes) 57 | 58 | let memorySize = bufferSize * bufferCount 59 | let newMemory = malloc(memorySize) 60 | memset(newMemory, 0, memorySize) // make buffer "hot" 61 | 62 | let oldMemory = bufferMemory 63 | bufferMemory = newMemory 64 | free(oldMemory) 65 | 66 | frames = inNumFrames 67 | } else { 68 | if let mBufferMemory = bufferMemory { 69 | free(mBufferMemory) 70 | self.bufferMemory = nil 71 | } 72 | bufferSize = 0 73 | frames = 0 74 | } 75 | } 76 | 77 | public func prepare() { 78 | try! prepare(frames: frames) 79 | } 80 | 81 | /// this version can throw if this is an allocted ABL and `inNumFrames` is `> allocatedFrames` 82 | /// you can set the bool to true if you want a `nil` buffer list even if allocated 83 | /// `inNumFrames` must be a valid number (will throw if inNumFrames is 0) 84 | public func prepare(frames inNumFrames: UInt32, wantNullBufferIfAllocated inWantNullBufferIfAllocated: Bool = false) throws { 85 | let channelsPerBuffer = format.isInterleaved ? format.mChannelsPerFrame : 1 86 | 87 | if bufferMemory == nil || inWantNullBufferIfAllocated { 88 | bufferList.count = bufferCount 89 | for i in 0 ..< bufferCount { 90 | bufferList[i].mNumberChannels = channelsPerBuffer 91 | bufferList[i].mDataByteSize = format.framesToBytes(inNumFrames) 92 | bufferList[i].mData = nil 93 | } 94 | } else { 95 | let nBytes = format.framesToBytes(inNumFrames) 96 | if (Int(nBytes) * bufferCount) > allocatedBytes { 97 | throw SAACoreAudioError(.tooManyFramesToProcess) 98 | } 99 | bufferList.count = bufferCount 100 | var p = bufferMemory! 101 | 102 | for i in 0 ..< bufferCount { 103 | bufferList[i].mNumberChannels = channelsPerBuffer 104 | bufferList[i].mDataByteSize = nBytes 105 | bufferList[i].mData = p 106 | p += bufferSize 107 | } 108 | } 109 | } 110 | 111 | public var ABL: UnsafeMutablePointer { 112 | return bufferList.unsafeMutablePointer 113 | } 114 | 115 | private var allocatedBytes: Int { 116 | return bufferSize * bufferCount 117 | } 118 | 119 | deinit { 120 | bufferList.unsafeMutablePointer.deallocate() 121 | if let bufferMemory = bufferMemory { 122 | free(bufferMemory) 123 | } 124 | } 125 | } 126 | 127 | extension AUOutputBL: CustomDebugStringConvertible { 128 | public var debugDescription: String { 129 | var output = format.description 130 | output += "\n" 131 | output += "Num Buffers:\(bufferList.count), mFrames:\(frames), allocatedMemory:\(bufferMemory != nil ? "T" : "F")\n" 132 | for (i, buf) in bufferList.enumerated() { 133 | output += "\tBuffer:\(i), Size:\(buf.mDataByteSize), Chans:\(buf.mNumberChannels), Buffer:\(buf.mData?.debugDescription ?? "nil")\n" 134 | } 135 | 136 | return output 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/AudioFileClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioFile.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 2/3/16. 6 | // Copyright © 2016 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AudioToolbox 11 | import CoreAudio 12 | import SwiftAdditions 13 | 14 | public final class AudioFile { 15 | private(set) var fileID: AudioFileID 16 | 17 | public init(createWith url: URL, fileType: AudioFileTypeID, format: inout AudioStreamBasicDescription, flags: AudioFileFlags = []) throws { 18 | var fileID: AudioFileID? = nil 19 | let iErr = AudioFileCreateWithURL(url as NSURL, fileType, &format, flags, &fileID) 20 | 21 | guard iErr == noErr else { 22 | throw errorFromOSStatus(iErr, userInfo: [NSURLErrorKey: url]) 23 | } 24 | self.fileID = fileID! 25 | } 26 | 27 | public init(open openURL: URL, permissions: AudioFilePermissions = .readPermission, fileTypeHint fileHint: AudioFileTypeID) throws { 28 | var fileID: AudioFileID? = nil 29 | let iErr = AudioFileOpenURL(openURL as NSURL, permissions, fileHint, &fileID) 30 | 31 | guard iErr == noErr else { 32 | throw errorFromOSStatus(iErr, userInfo: [NSURLErrorKey: openURL]) 33 | } 34 | self.fileID = fileID! 35 | } 36 | 37 | /// Opens an existing file with callbacks you provide. 38 | public init(callbacksWithReadFunction readFunc: @escaping AudioFile_ReadProc, writeFunction: AudioFile_WriteProc? = nil, getSizeFunction: @escaping AudioFile_GetSizeProc, setSizeFunction: AudioFile_SetSizeProc? = nil, clientData: UnsafeMutableRawPointer, fileTypeHint: AudioFileTypeID) throws { 39 | var fileID: AudioFileID? = nil 40 | let iErr = AudioFileOpenWithCallbacks(clientData, readFunc, writeFunction, getSizeFunction, setSizeFunction, fileTypeHint, &fileID) 41 | 42 | guard iErr == noErr else { 43 | throw errorFromOSStatus(iErr) 44 | } 45 | self.fileID = fileID! 46 | } 47 | 48 | /// Consolidates audio data and performs other internal optimizations of the file structure. 49 | public func optimize() throws { 50 | let iErr = AudioFileOptimize(fileID) 51 | 52 | guard iErr == noErr else { 53 | throw errorFromOSStatus(iErr) 54 | } 55 | } 56 | 57 | func readBytes(useCache: Bool = false, startingByte: Int64, byteCount: inout UInt32, buffer outBuffer: UnsafeMutableRawPointer) throws { 58 | let iErr = AudioFileReadBytes(fileID, useCache, startingByte, &byteCount, outBuffer) 59 | 60 | guard iErr == noErr else { 61 | throw errorFromOSStatus(iErr) 62 | } 63 | } 64 | 65 | func writeBytes(useCache: Bool = false, startingByte: Int64, byteCount: inout UInt32, buffer outBuffer: UnsafeRawPointer) throws { 66 | let iErr = AudioFileWriteBytes(fileID, useCache, startingByte, &byteCount, outBuffer) 67 | 68 | guard iErr == noErr else { 69 | throw errorFromOSStatus(iErr) 70 | } 71 | } 72 | 73 | public func userDataCount(ofID userDataID: UInt32) throws -> Int { 74 | var outNumberItems: UInt32 = 0 75 | let iErr = AudioFileCountUserData(fileID, userDataID, &outNumberItems) 76 | guard iErr == noErr else { 77 | throw errorFromOSStatus(iErr) 78 | } 79 | 80 | return Int(outNumberItems) 81 | } 82 | 83 | public func sizeOf(userDataID inUserDataID: UInt32, index: Int) throws -> Int { 84 | var outNumberSize: UInt32 = 0 85 | let iErr = AudioFileGetUserDataSize(fileID, inUserDataID, UInt32(index), &outNumberSize) 86 | guard iErr == noErr else { 87 | throw errorFromOSStatus(iErr) 88 | } 89 | 90 | return Int(outNumberSize) 91 | } 92 | 93 | deinit { 94 | AudioFileClose(fileID) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/AudioUnit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioUnit.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 11/6/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AudioUnit 11 | import SwiftAdditions 12 | 13 | public enum AudioComponentType: Hashable, @unchecked Sendable { 14 | case output(AUOutput) 15 | case musicDevice(AUInstrument) 16 | case musicEffect(OSType) 17 | case formatConverter(AUConverter) 18 | case effect(AUEffect) 19 | case mixer(AUMixer) 20 | case panner(AUPanner) 21 | case generator(AUGenerator) 22 | case offlineEffect(AUEffect) 23 | case MIDIProcessor(OSType) 24 | /// Components don't match any of the other values. 25 | case unknown(type: OSType, subType: OSType) 26 | 27 | public init(type rawType: OSType, subType AUSubtype: OSType) { 28 | guard let aType = AUType(rawValue: rawType) else { 29 | self = .unknown(type: rawType, subType: AUSubtype) 30 | return 31 | } 32 | switch aType { 33 | case .output: 34 | if let aOut = AUOutput(rawValue: AUSubtype) { 35 | self = .output(aOut) 36 | return 37 | } 38 | 39 | case .musicDevice: 40 | if let aDevice = AUInstrument(rawValue: AUSubtype) { 41 | self = .musicDevice(aDevice) 42 | return 43 | } 44 | 45 | case .musicEffect: 46 | self = .musicEffect(AUSubtype) 47 | return 48 | 49 | case .formatConverter: 50 | if let aForm = AUConverter(rawValue: AUSubtype) { 51 | self = .formatConverter(aForm) 52 | return 53 | } 54 | 55 | case .effect: 56 | if let aEffect = AUEffect(rawValue: AUSubtype) { 57 | self = .effect(aEffect) 58 | return 59 | } 60 | 61 | case .mixer: 62 | if let aMix = AUMixer(rawValue: AUSubtype) { 63 | self = .mixer(aMix) 64 | return 65 | } 66 | 67 | case .panner: 68 | if let aPann = AUPanner(rawValue: AUSubtype) { 69 | self = .panner(aPann) 70 | return 71 | } 72 | 73 | case .generator: 74 | if let aGen = AUGenerator(rawValue: AUSubtype) { 75 | self = .generator(aGen) 76 | return 77 | } 78 | 79 | case .offlineEffect: 80 | if let aEffect = AUEffect(rawValue: AUSubtype) { 81 | self = .offlineEffect(aEffect) 82 | return 83 | } 84 | 85 | case .MIDIProcessor: 86 | self = .MIDIProcessor(AUSubtype) 87 | return 88 | } 89 | 90 | 91 | self = .unknown(type: rawType, subType: AUSubtype) 92 | } 93 | 94 | public enum AUType: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 95 | case output = 0x61756F75 96 | case musicDevice = 0x61756D75 97 | case musicEffect = 0x61756D66 98 | case formatConverter = 0x61756663 99 | case effect = 0x61756678 100 | case mixer = 0x61756D78 101 | case panner = 0x6175706E 102 | case generator = 0x6175676E 103 | case offlineEffect = 0x61756F6C 104 | case MIDIProcessor = 0x61756D69 105 | } 106 | 107 | public enum AUOutput: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 108 | case generic = 0x67656E72 109 | case HAL = 0x6168616C 110 | case `default` = 0x64656620 111 | case system = 0x73797320 112 | case voiceProcessingIO = 0x7670696F 113 | } 114 | 115 | public enum AUInstrument: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 116 | #if os(OSX) 117 | case DLS = 0x646C7320 118 | #endif 119 | case sampler = 0x73616D70 120 | case MIDI = 0x6D73796E 121 | } 122 | 123 | public enum AUConverter: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 124 | case AUConverter = 0x636F6E76 125 | #if os(OSX) 126 | case timePitch = 0x746D7074 127 | #endif 128 | #if os(iOS) 129 | case iPodTime = 0x6970746D 130 | #endif 131 | case varispeed = 0x76617269 132 | case deferredRenderer = 0x64656672 133 | case splitter = 0x73706C74 134 | case merger = 0x6D657267 135 | case newTimePitch = 0x6E757470 136 | case iPodTimeOther = 0x6970746F 137 | } 138 | 139 | public enum AUEffect: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 140 | case delay = 0x64656C79 141 | case lowPassFilter = 0x6C706173 142 | case highPassFilter = 0x68706173 143 | case bandPassFilter = 0x62706173 144 | case highShelfFilter = 0x68736866 145 | case lowShelfFilter = 0x6C736866 146 | case parametricEQ = 0x706D6571 147 | case peakLimiter = 0x6C6D7472 148 | case dynamicsProcessor = 0x64636D70 149 | case sampleDelay = 0x73646C79 150 | case distortion = 0x64697374 151 | case NBandEQ = 0x6E626571 152 | 153 | #if os(OSX) 154 | case graphicEQ = 0x67726571 155 | case multiBandCompressor = 0x6D636D70 156 | case matrixReverb = 0x6D726576 157 | case pitch = 0x746D7074 158 | case AUFilter = 0x66696C74 159 | case netSend = 0x6E736E64 160 | case rogerBeep = 0x726F6772 161 | #elseif os(iOS) 162 | case reverb2 = 0x72766232 163 | case iPodEQ = 0x69706571 164 | #endif 165 | @available(macOS 13.0, iOS 16.0, macCatalyst 16.0, *) 166 | @available(watchOS, unavailable) 167 | @available(tvOS, unavailable) 168 | case soundIsolation = 1987012979 169 | } 170 | 171 | public enum AUMixer: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 172 | case multiChannel = 0x6D636D78 173 | case spatial = 0x3364656D 174 | case stereo = 0x736D7872 175 | case matrix = 0x6D786D78 176 | } 177 | 178 | public enum AUPanner: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 179 | case sphericalHead = 0x73706872 180 | case vector = 0x76626173 181 | case soundField = 0x616D6269 182 | case HRTF = 0x68727466 183 | } 184 | 185 | public enum AUGenerator: OSType, OSTypeConvertable, Hashable, @unchecked Sendable { 186 | #if os(OSX) 187 | case netReceive = 0x6E726376 188 | #endif 189 | case scheduledSoundPlayer = 0x7373706C 190 | case audioFilePlayer = 0x6166706C 191 | } 192 | 193 | public var types: (type: OSType, subtype: OSType) { 194 | switch self { 195 | case let .output(aType): 196 | return (AUType.output.rawValue, aType.rawValue) 197 | 198 | case let .musicDevice(aType): 199 | return (AUType.musicDevice.rawValue, aType.rawValue) 200 | 201 | case let .musicEffect(aType): 202 | return (AUType.musicEffect.rawValue, aType) 203 | 204 | case let .formatConverter(aType): 205 | return (AUType.formatConverter.rawValue, aType.rawValue) 206 | 207 | case let .effect(aType): 208 | return (AUType.effect.rawValue, aType.rawValue) 209 | 210 | case let .mixer(aType): 211 | return (AUType.mixer.rawValue, aType.rawValue) 212 | 213 | case let .panner(aType): 214 | return (AUType.panner.rawValue, aType.rawValue) 215 | 216 | case let .generator(aType): 217 | return (AUType.generator.rawValue, aType.rawValue) 218 | 219 | case let .offlineEffect(aType): 220 | return (AUType.offlineEffect.rawValue, aType.rawValue) 221 | 222 | case let .MIDIProcessor(aType): 223 | return (AUType.MIDIProcessor.rawValue, aType) 224 | 225 | case let .unknown(type: aType, subType: aSubtype): 226 | return (aType, aSubtype) 227 | } 228 | } 229 | } 230 | 231 | public extension AudioComponentDescription { 232 | init(component: AudioComponentType, manufacturer: OSType = kAudioUnitManufacturer_Apple, flag: AudioComponentFlags = [], mask: AudioComponentFlags = []) { 233 | self.init(componentType: component.types.type, 234 | componentSubType: component.types.subtype, 235 | componentManufacturer: manufacturer, 236 | componentFlags: flag.rawValue, 237 | componentFlagsMask: mask.rawValue) 238 | } 239 | 240 | var flag: AudioComponentFlags { 241 | get { 242 | return AudioComponentFlags(rawValue: componentFlags) 243 | } 244 | set { 245 | componentFlags = newValue.rawValue 246 | } 247 | } 248 | 249 | var flagMask: AudioComponentFlags { 250 | get { 251 | return AudioComponentFlags(rawValue: componentFlagsMask) 252 | } 253 | set { 254 | componentFlagsMask = newValue.rawValue 255 | } 256 | } 257 | 258 | var component: AudioComponentType { 259 | get { 260 | return AudioComponentType(type: componentType, subType: componentSubType) 261 | } 262 | set { 263 | (componentType, componentSubType) = newValue.types 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/ExtAudioFileExt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtAudioFileExt.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 4/18/15. 6 | // Copyright (c) 2015 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AudioToolbox 11 | import SwiftAdditions 12 | 13 | @inlinable public func ExtAudioFileCreate(url inURL: URL, fileType inFileType: AudioFileType, streamDescription inStreamDesc: inout AudioStreamBasicDescription, channelLayout inChannelLayout: UnsafePointer? = nil, flags: AudioFileFlags = AudioFileFlags(rawValue: 0), audioFile outAudioFile: inout ExtAudioFileRef?) -> OSStatus { 14 | return ExtAudioFileCreateWithURL(inURL as NSURL, inFileType.rawValue, &inStreamDesc, inChannelLayout, flags.rawValue, &outAudioFile) 15 | } 16 | 17 | @inlinable public func ExtAudioFileSetProperty(_ inExtAudioFile: ExtAudioFileRef, propertyID inPropertyID: ExtAudioFilePropertyID, dataSize propertyDataSize: UInt32, data propertyData: UnsafeRawPointer) -> OSStatus { 18 | return ExtAudioFileSetProperty(inExtAudioFile, inPropertyID, propertyDataSize, propertyData) 19 | } 20 | 21 | @inlinable public func ExtAudioFileSetProperty(_ inExtAudioFile: ExtAudioFileRef, propertyID inPropertyID: ExtAudioFilePropertyID, dataSize propertyDataSize: Int, data propertyData: UnsafeRawPointer) -> OSStatus { 22 | return ExtAudioFileSetProperty(inExtAudioFile, inPropertyID, UInt32(propertyDataSize), propertyData) 23 | } 24 | 25 | @inlinable public func ExtAudioFileGetPropertyInfo(_ inExtAudioFile: ExtAudioFileRef, propertyID inPropertyID: ExtAudioFilePropertyID, size outSize: inout Int, writable outWritable: inout Bool) -> OSStatus { 26 | var ouSize = UInt32(outSize) 27 | var ouWritable: DarwinBoolean = false 28 | let aRet = ExtAudioFileGetPropertyInfo(inExtAudioFile, inPropertyID, &ouSize, &ouWritable) 29 | outWritable = ouWritable.boolValue 30 | outSize = Int(ouSize) 31 | return aRet 32 | } 33 | 34 | @inlinable public func ExtAudioFileGetPropertyInfo(_ inExtAudioFile: ExtAudioFileRef, propertyID inPropertyID: ExtAudioFilePropertyID, size outSize: inout UInt32, writable outWritable: inout Bool) -> OSStatus { 35 | var ouWritable: DarwinBoolean = false 36 | let aRet = ExtAudioFileGetPropertyInfo(inExtAudioFile, inPropertyID, &outSize, &ouWritable) 37 | outWritable = ouWritable.boolValue 38 | return aRet 39 | } 40 | 41 | @inlinable public func ExtAudioFileGetProperty(_ inExtAudioFile: ExtAudioFileRef, propertyID inPropertyID: ExtAudioFilePropertyID, propertyDataSize ioPropertyDataSize: inout UInt32, propertyData outPropertyData: UnsafeMutableRawPointer) -> OSStatus { 42 | return ExtAudioFileGetProperty(inExtAudioFile, inPropertyID, &ioPropertyDataSize, outPropertyData) 43 | } 44 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleSignature 6 | ???? 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/SAAError.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAAError.c 3 | // SwiftAudioAdditions 4 | // 5 | // Created by C.W. Betts on 9/23/18. 6 | // Copyright © 2018 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #include "SAAError.h" 10 | NSErrorDomain const SAACoreAudioErrorDomain = @"com.github.maddthesane.SwiftAudioAdditions.errors"; 11 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/SoundBankAdditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoundBankAdditions.swift 3 | // SwiftAudioAdditions 4 | // 5 | // Created by C.W. Betts on 7/2/20. 6 | // Copyright © 2020 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AudioToolbox 11 | import SwiftAdditions 12 | 13 | /// Keys for dictionaries returned by `instrumentInfoFromSoundBank(at:)` 14 | public enum InstrumentInfoKey: RawRepresentable, LosslessStringConvertible, CaseIterable { 15 | public typealias RawValue = String 16 | 17 | public init?(rawValue: String) { 18 | switch rawValue { 19 | case kInstrumentInfoKey_Name: 20 | self = .name 21 | 22 | case kInstrumentInfoKey_MSB: 23 | self = .msb 24 | 25 | case kInstrumentInfoKey_LSB: 26 | self = .lsb 27 | 28 | case kInstrumentInfoKey_Program: 29 | self = .program 30 | 31 | default: 32 | return nil 33 | } 34 | } 35 | 36 | public init?(_ rawValue: String) { 37 | self.init(rawValue: rawValue) 38 | } 39 | 40 | public var rawValue: String { 41 | switch self { 42 | case .name: 43 | return kInstrumentInfoKey_Name 44 | case .msb: 45 | return kInstrumentInfoKey_MSB 46 | case .lsb: 47 | return kInstrumentInfoKey_LSB 48 | case .program: 49 | return kInstrumentInfoKey_Program 50 | } 51 | } 52 | 53 | public var description: String { 54 | return rawValue 55 | } 56 | 57 | /// A `String` containing the name of the instrument. 58 | case name 59 | /// An `Int` for the most-significant byte of the bank number. GM melodic banks will return 120 (0x78). 60 | /// GM percussion banks will return 121 (0x79). Custom banks will return their literal value. 61 | case msb 62 | /// An `Int` for the least-significant byte of the bank number. All GM banks will return 63 | /// the bank variation number (0-127). 64 | case lsb 65 | /// An `Int` for the program number (0-127) of an instrument within a particular bank. 66 | case program 67 | } 68 | 69 | /// This will return the name of a sound bank from a DLS or SF2 bank. 70 | /// - parameter inURL: The URL for the sound bank. 71 | /// - returns: The name of a sound bank. 72 | public func nameFromSoundBank(at inURL: URL) throws -> String { 73 | var str: Unmanaged? = nil 74 | let status = CopyNameFromSoundBank(inURL as NSURL, &str) 75 | guard status == noErr, let toRet = str?.takeRetainedValue() else { 76 | throw errorFromOSStatus(status, userInfo: [NSURLErrorKey: inURL]) 77 | } 78 | 79 | return toRet as String 80 | } 81 | 82 | /// This will return an `Array` of Dictionaries, one per instrument found in the DLS or SF2 bank. 83 | /// Each dictionary will contain four items accessed via `InstrumentInfoKey` versions of the keys `.msb`, 84 | /// `.lsb`, `.program`, and `.name`. 85 | /// - parameter inURL: The URL for the sound bank. 86 | /// 87 | /// Using these MSB, LSB, and Program values will guarantee that the correct instrument is loaded by the DLS synth 88 | /// or Sampler Audio Unit. 89 | func instrumentInfoFromSoundBank(at inURL: URL) throws -> [[InstrumentInfoKey: Any]] { 90 | var tmpArr: Unmanaged? 91 | let status = CopyInstrumentInfoFromSoundBank(inURL as NSURL, &tmpArr) 92 | guard status == noErr, let tmpArr2 = tmpArr?.takeRetainedValue() as? [[String: Any]] else { 93 | throw errorFromOSStatus(status, userInfo: [NSURLErrorKey: inURL]) 94 | } 95 | let toRet = tmpArr2.map { (aDict) -> [InstrumentInfoKey: Any] in 96 | let tmpDict = aDict.compactMap { (key: String, value: Any) -> (InstrumentInfoKey, Any)? in 97 | guard let key2 = InstrumentInfoKey(rawValue: key) else { 98 | print("instrumentInfoFromSoundBank: Uhh... unknown key \(key)?") 99 | return nil 100 | } 101 | return (key2, value) 102 | } 103 | return Dictionary(uniqueKeysWithValues: tmpDict) 104 | } 105 | return toRet 106 | } 107 | -------------------------------------------------------------------------------- /SwiftAudioAdditions/SwiftAudioAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftAudioAdditions.h 3 | // SwiftAudioAdditions 4 | // 5 | // Created by C.W. Betts on 4/21/15. 6 | // Copyright (c) 2015 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftAudioAdditions. 12 | FOUNDATION_EXPORT double SwiftAudioAdditionsVersionNumber; 13 | 14 | //! Project version string for SwiftAudioAdditions. 15 | FOUNDATION_EXPORT const unsigned char SwiftAudioAdditionsVersionString[]; 16 | 17 | #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftAudioAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftAudioAdditionsTests/SwiftAudioAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftAudioAdditionsTests.swift 3 | // SwiftAudioAdditionsTests 4 | // 5 | // Created by C.W. Betts on 4/21/15. 6 | // Copyright (c) 2015 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AudioToolbox 11 | import CoreAudio 12 | @testable import SwiftAudioAdditions 13 | import XCTest 14 | 15 | class SwiftAudioAdditionsTests: XCTestCase { 16 | 17 | override func setUp() { 18 | super.setUp() 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | super.tearDown() 25 | } 26 | 27 | func testASBDStringInit() { 28 | var asbd = AudioStreamBasicDescription() 29 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "BEI16@44100,2")) 30 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "BEI16@44100,2")) 31 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "BEI16.1@44100:L5")) 32 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "-BEI16@44100,2D")) 33 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "-BEUI16@44100,2D")) 34 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "BEI16@44100,2D")) 35 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "BEI16@44100,2I")) 36 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "LEF16@48000,2")) 37 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "aac@48000,2")) 38 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "aac")) 39 | XCTAssertNoThrow(asbd = try AudioStreamBasicDescription(fromText: "aac/5bE")) 40 | _=asbd 41 | } 42 | 43 | func testInvalidASBDStringInits() { 44 | var asbd: AudioStreamBasicDescription? 45 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aac,D")) 46 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aa")) 47 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "")) 48 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "@441100")) 49 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "\\x20\\x20")) 50 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "\\x20\\y20")) 51 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "B")) 52 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "BE")) 53 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "BEI16.c@44100,2")) 54 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aac/5bEé")) 55 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aac/5bE,12D")) 56 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aac/5bE;")) 57 | XCTAssertThrowsError(asbd = try AudioStreamBasicDescription(fromText: "aac/5bG")) 58 | _=asbd 59 | } 60 | 61 | func testSoundBankFunctions() { 62 | let caInstruments = URL(fileURLWithPath: "/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls") 63 | var bankName = "" 64 | XCTAssertNoThrow(bankName = try nameFromSoundBank(at: caInstruments)) 65 | print(bankName) 66 | 67 | var banks = [[InstrumentInfoKey: Any]]() 68 | XCTAssertNoThrow(banks = try instrumentInfoFromSoundBank(at: caInstruments)) 69 | _=banks 70 | 71 | XCTAssertThrowsError(bankName = try nameFromSoundBank(at: URL(fileURLWithPath:"/System/Library/Components/CoreAudio.component/Contents/MacOS/CoreAudio"))) 72 | } 73 | 74 | func testSwiftErrorCatch() { 75 | var bankName = "" 76 | do { 77 | bankName = try nameFromSoundBank(at: URL(fileURLWithPath:"/System/Library/Components/CoreAudio.component/Contents/MacOS/CoreAudio")) 78 | } catch let error as NSError { 79 | XCTAssertEqual(error.domain, NSOSStatusErrorDomain) 80 | print(error) 81 | } 82 | _=bankName 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SwiftIOKitAdditions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleSignature 6 | ???? 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftIOKitAdditions/KextManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KextManager.swift 3 | // SwiftAdditions 4 | // 5 | // Created by C.W. Betts on 12/28/24. 6 | // Copyright © 2024 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IOKit.kext 11 | 12 | /// Namespace for Kext Manager functions 13 | public enum KextManager { 14 | /// Create a URL locating a kext with a given bundle identifier. 15 | /// - parameter ident: The bundle identifier to look up. 16 | /// - returns: A `URL` locating a kext with the requested bundle identifier. Returns `nil` if the kext cannot be found, or on error. 17 | /// 18 | /// Kexts are looked up first by whether they are loaded, second by version. Specifically, if `ident` identifies a kext 19 | /// that is currently loaded, the returned URL will locate that kext if it’s still present on disk. If the requested kext is not 20 | /// loaded, or if its bundle is not at the location it was originally loaded from, the returned URL will locate the latest version 21 | /// of the desired kext, if one can be found within the system extensions folder. If no version of the kext can be found, 22 | /// `nil` is returned. 23 | @inlinable public static func URLForBundleIdentifier(_ ident: String) -> URL? { 24 | return KextManagerCreateURLForBundleIdentifier(kCFAllocatorDefault, ident as NSString).takeRetainedValue() as URL 25 | } 26 | 27 | /// Request the kext loading system to load a kext with a given bundle identifier. 28 | /// - parameter kextIdentifier: The bundle identifier of the kext to look up and load. 29 | /// - parameter dependencies: An array of additional URLs, of individual kexts and of folders that may contain kexts. 30 | /// - returns: `kOSReturnSuccess` if the kext is successfully loaded (or is already loaded), otherwise returns on error. 31 | /// 32 | /// `kextIdentifier` is looked up in the system extensions folder and among any kexts from 33 | /// `dependencies`. Any non-kext URLs in `dependencies` are scanned at the top level for kexts and plugins of kexts. 34 | /// 35 | /// Either the calling process must have an effective user id of 0 (superuser), or the kext being loaded and all its dependencies 36 | /// must reside in */System* and have an *OSBundleAllowUserLoad* property of `true`. 37 | @inlinable public static func loadKextWithIdentifier(_ kextIdentifier: String, dependencies: [URL]) -> OSReturn { 38 | return KextManagerLoadKextWithIdentifier(kextIdentifier as NSString, dependencies as NSArray) 39 | } 40 | 41 | /// Request the kext loading system to load a kext with a given URL. 42 | /// - parameter kextURL: The URL of the kext to load. 43 | /// - parameter dependencies: An array of additional URLs, of individual kexts and of folders that may contain kexts. 44 | /// - returns: `kOSReturnSuccess` if the kext is successfully loaded (or is already loaded), otherwise returns on error. 45 | /// 46 | /// Any non-kext URLs in `dependencies` are scanned at the top level for kexts and plugins of kexts. 47 | /// 48 | /// Either the calling process must have an effective user id of 0 (superuser), or the kext being loaded and all 49 | /// its dependencies must reside in /System and have an *OSBundleAllowUserLoad* property of `true`. 50 | @inlinable public static func loadKext(with kextURL: URL, dependencies: [URL]) -> OSReturn { 51 | return KextManagerLoadKextWithURL(kextURL as NSURL, dependencies as NSArray) 52 | } 53 | 54 | /// Request the kernel to unload a kext with a given bundle identifier. 55 | /// - parameter identifier: The bundle identifier of the kext to unload. 56 | /// - returns: `kOSReturnSuccess` if the kext is found and successfully unloaded, otherwise returns on error. 57 | /// See */usr/include/libkern/OSKextLib.h* for error codes. 58 | /// 59 | /// The calling process must have an effective user id of 0 (superuser). 60 | @inlinable public static func unloadKext(with identifier: CFString) -> OSReturn { 61 | return KextManagerUnloadKextWithIdentifier(identifier as CFString) 62 | } 63 | 64 | /// Returns information about loaded kexts in a dictionary. 65 | /// - parameter identifiers: An array of kext identifiers to read from the kernel. Pass `nil` to read info for all loaded kexts. 66 | /// - parameter infoKeys: An array of info keys to read from the kernel. Pass `nil` to read all information. 67 | /// - returns: A dictionary, keyed by bundle identifier, of dictionaries containing information about loaded kexts. 68 | /// 69 | /// The information keys returned by this function are listed below. Some are taken directly from the kext’s information 70 | /// property list, and some are generated at run time. Never assume a given key will be present for a kext. 71 | /// * `CFBundleIdentifier` - CFString 72 | /// * `CFBundleVersion` - CFString (note: version strings may be canonicalized 73 | /// but their numeric values will be the same; "1.2.0" may become "1.2", for example) 74 | /// * `OSBundleCompatibleVersion` - CFString 75 | /// * `OSBundleIsInterface` - CFBoolean 76 | /// * `OSKernelResource` - CFBoolean 77 | /// * `OSBundleCPUType` - CFNumber 78 | /// * `OSBundleCPUSubtype` - CFNumber 79 | /// * `OSBundlePath` - CFString (this is merely a hint stored in the kernel; 80 | /// the kext is not guaranteed to be at this path) 81 | /// * `OSBundleExecutablePath` - CFString 82 | /// (the absolute path to the executable within the kext bundle; a hint as above) 83 | /// * `OSBundleUUID` - CFData (the UUID of the kext executable, if it has one) 84 | /// * `OSBundleStarted` - CFBoolean (true if the kext is running) 85 | /// * `OSBundlePrelinked` - CFBoolean (true if the kext is loaded from a prelinked kernel) 86 | /// * `OSBundleLoadTag` - CFNumber (the "Index" given by kextstat) 87 | /// * `OSBundleLoadAddress` - CFNumber 88 | /// * `OSBundleLoadSize` - CFNumber 89 | /// * `OSBundleWiredSize` - CFNumber 90 | /// * `OSBundleDependencies` - CFArray of load tags identifying immediate link dependencies 91 | /// * `OSBundleRetainCount` - CFNumber (the OSObject retain count of the kext itself) 92 | /// * `OSBundleClasses` - CFArray of CFDictionary containing info on C++ classes 93 | /// defined by the kext: 94 | /// * * `OSMetaClassName` - CFString 95 | /// * * `OSMetaClassSuperclassName` - CFString, absent for root classes 96 | /// * * `OSMetaClassTrackingCount` - CFNumber giving the instance count 97 | /// of the class itself, *plus* 1 for each direct subclass with any instances 98 | static public func loadedKextInfo(forIdentifiers identifiers: [String]?, infoKeys: [String]?) -> [String: [String: Any]]? { 99 | return KextManagerCopyLoadedKextInfo(identifiers as NSArray?, infoKeys as NSArray?)?.takeRetainedValue() as? [String: [String: Any]] 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SwiftIOKitAdditions/SwiftIOKitAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftIOKitAdditions.h 3 | // SwiftIOKitAdditions 4 | // 5 | // Created by C.W. Betts on 11/20/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftIOKitAdditions. 12 | FOUNDATION_EXPORT double SwiftIOKitAdditionsVersionNumber; 13 | 14 | //! Project version string for SwiftIOKitAdditions. 15 | FOUNDATION_EXPORT const unsigned char SwiftIOKitAdditionsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | -------------------------------------------------------------------------------- /SwiftIOKitAdditions/hid/IOHIDValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IOHIDValue.swift 3 | // SwiftIOKitAdditions 4 | // 5 | // Created by C.W. Betts on 12/9/19. 6 | // Copyright © 2019 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IOKit.hid 11 | import Darwin.Mach.mach_time 12 | 13 | public extension IOHIDValue { 14 | /// Creates a new element value using an integer value. 15 | /// 16 | /// `timeStamp` should represent OS AbsoluteTime, not `CFAbsoluteTime`. 17 | /// To obtain the OS AbsoluteTime, please reference the APIs declared in ** 18 | /// - parameter allocator: The `CFAllocator` which should be used to allocate memory for the value. 19 | /// - parameter element: `IOHIDElement` associated with this value. 20 | /// - parameter timeStamp: OS absolute time timestamp for this value. 21 | /// - parameter value: Integer value to be copied to this object. 22 | /// - returns: a reference to a new IOHIDValueRef. 23 | @inlinable class func create(allocator: CFAllocator? = kCFAllocatorDefault, element: IOHIDElement, timeStamp: UInt64 = mach_absolute_time(), with value: Int) -> IOHIDValue { 24 | return IOHIDValueCreateWithIntegerValue(allocator, element, timeStamp, value) 25 | } 26 | 27 | /// Creates a new element value using byte data. 28 | /// 29 | /// `timeStamp` should represent OS AbsoluteTime, not `CFAbsoluteTime`. 30 | /// To obtain the OS AbsoluteTime, please reference the APIs declared in ** 31 | /// - parameter allocator: The `CFAllocator` which should be used to allocate memory for the value. 32 | /// - parameter element: `IOHIDElement` associated with this value. 33 | /// - parameter timeStamp: OS absolute time timestamp for this value. 34 | /// - parameter bytes: Pointer to a buffer of uint8_t to be copied to this object. 35 | /// - parameter length: Number of bytes in the passed buffer. 36 | /// - returns: Returns a reference to a new `IOHIDValue`. 37 | @inlinable class func create(allocator: CFAllocator? = kCFAllocatorDefault, element: IOHIDElement, timeStamp: UInt64 = mach_absolute_time(), bytes: UnsafePointer, length: CFIndex) -> IOHIDValue? { 38 | return IOHIDValueCreateWithBytes(allocator, element, timeStamp, bytes, length) 39 | } 40 | 41 | /// Creates a new element value using byte data without performing a copy. 42 | /// 43 | /// `timeStamp` should represent OS AbsoluteTime, not `CFAbsoluteTime`. 44 | /// To obtain the OS AbsoluteTime, please reference the APIs declared in ** 45 | /// - parameter allocator: The `CFAllocator` which should be used to allocate memory for the value. 46 | /// - parameter element: `IOHIDElement` associated with this value. 47 | /// - parameter timeStamp: OS absolute time timestamp for this value. 48 | /// - parameter bytes: Pointer to a buffer of uint8_t to be copied to this object. 49 | /// - parameter length: Number of bytes in the passed buffer. 50 | /// - returns: Returns a reference to a new `IOHIDValue`. 51 | @inlinable class func create(allocator: CFAllocator? = kCFAllocatorDefault, element: IOHIDElement, timeStamp: UInt64 = mach_absolute_time(), bytesNoCopy bytes: UnsafePointer, length: CFIndex) -> IOHIDValue? { 52 | return IOHIDValueCreateWithBytesNoCopy(allocator, element, timeStamp, bytes, length) 53 | } 54 | 55 | /// The element value associated with this `IOHIDValue`. 56 | @inlinable var element: IOHIDElement { 57 | return IOHIDValueGetElement(self) 58 | } 59 | 60 | /// The timestamp value contained in this `IOHIDValue`. 61 | @inlinable var timeStamp: UInt64 { 62 | return IOHIDValueGetTimeStamp(self) 63 | } 64 | 65 | /// The size, in bytes, of the value contained in this `IOHIDValue`. 66 | @inlinable var length: Int { 67 | return IOHIDValueGetLength(self) 68 | } 69 | 70 | /// A byte pointer to the value contained in this `IOHIDValue`. 71 | @inlinable var bytePtr: UnsafePointer { 72 | return IOHIDValueGetBytePtr(self) 73 | } 74 | 75 | /// an integer representaion of the value contained in this `IOHIDValue`. 76 | /// 77 | /// The value is based on the logical element value contained in the report returned by the device. 78 | @inlinable var integer: Int { 79 | return IOHIDValueGetIntegerValue(self) 80 | } 81 | 82 | /// Returns an scaled representaion of the value contained in this `IOHIDValue` based on the scale type. 83 | /// 84 | /// The scaled value is based on the range described by the scale type's min and max, such that: 85 | /// ```` 86 | /// scaledValue = ((value - min) * (scaledMax - scaledMin) / (max - min)) + scaledMin 87 | /// ```` 88 | /// **Note:** 89 | /// 90 | /// There are currently two types of scaling that can be applied: 91 | /// - **kIOHIDValueScaleTypePhysical**: Scales element value using the physical bounds of the device such that *scaledMin = physicalMin* and *scaledMax = physicalMax*. 92 | /// - **kIOHIDValueScaleTypeCalibrated**: Scales element value such that *scaledMin = -1* and *scaledMax = 1*. This value will also take into account the calibration properties associated with this element. 93 | /// - parameter type: The type of scaling to be performed. 94 | /// - returns: Returns an scaled floating point representation of the value. 95 | @inlinable func scaledValue(type: IOHIDValueScaleType) -> double_t { 96 | return IOHIDValueGetScaledValue(self, type) 97 | } 98 | } 99 | 100 | public extension IOHIDValue { 101 | var data: Data { 102 | return Data(bytes: bytePtr, count: length) 103 | } 104 | 105 | /// Creates a new element value using byte data. 106 | /// 107 | /// `timeStamp` should represent OS AbsoluteTime, not `CFAbsoluteTime`. 108 | /// To obtain the OS AbsoluteTime, please reference the APIs declared in ** 109 | /// - parameter allocator: The `CFAllocator` which should be used to allocate memory for the value. 110 | /// - parameter element: `IOHIDElement` associated with this value. 111 | /// - parameter timeStamp: OS absolute time timestamp for this value. 112 | /// - parameter data: Data object to be copied to this object. 113 | /// - returns: Returns a reference to a new `IOHIDValue`. 114 | class func create(allocator: CFAllocator? = kCFAllocatorDefault, element: IOHIDElement, timeStamp: UInt64 = mach_absolute_time(), data: Data) -> IOHIDValue? { 115 | return data.withUnsafeBytes { (bufPtr) -> IOHIDValue? in 116 | return IOHIDValueCreateWithBytes(allocator, element, timeStamp, bufPtr.bindMemory(to: UInt8.self).baseAddress!, bufPtr.count) 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /SwiftIOKitAdditions/hid/misc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // misc.swift 3 | // SwiftIOKitAdditions 4 | // 5 | // Created by C.W. Betts on 6/14/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import FoundationAdditions 11 | import IOKit.hid 12 | 13 | extension IOHIDDevice: @retroactive CFTypeProtocol { 14 | /// The type identifier of all `IOHIDDevice` instances. 15 | @inlinable public class var typeID: CFTypeID { 16 | return IOHIDDeviceGetTypeID() 17 | } 18 | } 19 | 20 | extension IOHIDElement: @retroactive CFTypeProtocol { 21 | /// Returns the type identifier of all `IOHIDElement` instances. 22 | @inlinable public class var typeID: CFTypeID { 23 | return IOHIDElementGetTypeID() 24 | } 25 | } 26 | 27 | extension IOHIDManager: @retroactive CFTypeProtocol { 28 | /// The type identifier of all `IOHIDManager` instances. 29 | @inlinable public class var typeID: CFTypeID { 30 | return IOHIDManagerGetTypeID() 31 | } 32 | } 33 | 34 | extension IOHIDQueue: @retroactive CFTypeProtocol { 35 | /// The type identifier of all IOHIDQueue instances. 36 | @inlinable public class var typeID: CFTypeID { 37 | return IOHIDQueueGetTypeID() 38 | } 39 | } 40 | 41 | extension IOHIDTransaction: @retroactive CFTypeProtocol { 42 | /// The type identifier of all `IOHIDTransaction` instances. 43 | @inlinable public class var typeID: CFTypeID { 44 | return IOHIDTransactionGetTypeID() 45 | } 46 | } 47 | 48 | extension IOHIDValue: @retroactive CFTypeProtocol { 49 | /// The type identifier of all `IOHIDValue` instances. 50 | @inlinable public class var typeID: CFTypeID { 51 | return IOHIDValueGetTypeID() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SwiftIOKitAdditionsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftIOKitAdditionsTests/SwiftIOKitAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftIOKitAdditionsTests.swift 3 | // SwiftIOKitAdditionsTests 4 | // 5 | // Created by C.W. Betts on 11/20/14. 6 | // Copyright (c) 2014 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | @testable import SwiftIOKitAdditions 12 | 13 | class SwiftIOKitAdditionsTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testExample() { 26 | // This is an example of a functional test case. 27 | XCTAssert(true, "Pass") 28 | } 29 | 30 | func testPerformanceExample() { 31 | // This is an example of a performance test case. 32 | self.measure() { 33 | // Put the code you want to measure the time of here. 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /TISAdditions/TISAdditions.docc/TISAdditions.md: -------------------------------------------------------------------------------- 1 | # ``TISAdditions`` 2 | 3 | Swift additions for the `TISInputSource` Core Foundation object, including wrappers around common enumerations with Swift-friendly versions. 4 | 5 | ## Overview 6 | 7 | Text 8 | 9 | ## Topics 10 | 11 | ### Group 12 | 13 | - ``Symbol`` 14 | -------------------------------------------------------------------------------- /TISAdditions/TISAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // TISAdditions.h 3 | // TISAdditions 4 | // 5 | // Created by C.W. Betts on 10/4/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TISAdditions. 12 | FOUNDATION_EXPORT double TISAdditionsVersionNumber; 13 | 14 | //! Project version string for TISAdditions. 15 | FOUNDATION_EXPORT const unsigned char TISAdditionsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /TISAdditionsTests/TISAdditionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TISAdditionsTests.swift 3 | // TISAdditionsTests 4 | // 5 | // Created by C.W. Betts on 10/4/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Carbon.HIToolbox 11 | @testable import TISAdditions 12 | 13 | class TISAdditionsTests: XCTestCase { 14 | 15 | override func setUpWithError() throws { 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDownWithError() throws { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | } 22 | 23 | func testExample() throws { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() throws { 29 | // This is an example of a performance test case. 30 | self.measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /UTTypeOSTypes/UTTypeOSTypes.docc/UTTypeOSTypes.md: -------------------------------------------------------------------------------- 1 | # ``UTTypeOSTypes`` 2 | 3 | Additional helpers for `UTType`s to handle legacy Mac file type codes. 4 | 5 | If you need to convert to/from an `OSType`, use the SwiftAdditions framework. 6 | 7 | ## Overview 8 | 9 | Text 10 | 11 | ## Topics 12 | 13 | ### OSType UTType 14 | 15 | - UTTagClass.osType 16 | - UTType.preferredOSType 17 | - UTType(osType:conformingTo:) 18 | -------------------------------------------------------------------------------- /UTTypeOSTypes/UTTypeOSTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // UTTypeOSTypes.h 3 | // UTTypeOSTypes 4 | // 5 | // Created by C.W. Betts on 11/13/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for UTTypeOSTypes. 12 | FOUNDATION_EXPORT double UTTypeOSTypesVersionNumber; 13 | 14 | //! Project version string for UTTypeOSTypes. 15 | FOUNDATION_EXPORT const unsigned char UTTypeOSTypesVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /UTTypeOSTypes/UTTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UTTypes.swift 3 | // UTTypeOSTypes 4 | // 5 | // Created by C.W. Betts on 11/13/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UniformTypeIdentifiers 11 | 12 | public extension UTTagClass { 13 | /** 14 | The tag class for Mac OS type codes such as `"TEXT"`. 15 | 16 | The raw value of this tag class is `"com.apple.ostype"`. 17 | */ 18 | static var osType: UTTagClass { 19 | return UTTagClass(rawValue: "com.apple.ostype") 20 | } 21 | } 22 | 23 | public extension UTType { 24 | /** 25 | If available, the preferred (first available) tag of class 26 | `.osType`. 27 | 28 | If not `nil`, the value of this property is the best available Mac OS type code 29 | for the given type, according to its declaration. The value of this 30 | property is equivalent to: 31 | 32 | ``` 33 | type.tags[.osType]?.first 34 | ``` 35 | */ 36 | var preferredOSType: String? { 37 | tags[.osType]?.first 38 | } 39 | 40 | /** 41 | Create a type given an OSType. 42 | 43 | - Parameters: 44 | - osType: The legacy OSType for which a type is 45 | desired. 46 | - supertype: Another type that the resulting type must conform to. 47 | Typically, you would pass `.data`. 48 | 49 | - Returns: A type. If no types are known to the system with the 50 | specified OS Type file code and conformance but the inputs were 51 | otherwise valid, a dynamic type may be provided. If the inputs were 52 | not valid, returns `nil`. 53 | 54 | This method is equivalent to: 55 | 56 | ``` 57 | UTType(tag: osType, tagClass: .osType, conformingTo: supertype) 58 | ``` 59 | 60 | To get the type of a file on disk, use `URLResourceValues.contentType`. 61 | You should not attempt to derive the type of a file system object based 62 | solely on its OSType file type. 63 | */ 64 | init?(osType: String, conformingTo supertype: UTType = .data) { 65 | self.init(tag: osType, tagClass: .osType, conformingTo: supertype) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UTTypeOSTypesTests/UTTypeOSTypesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UTTypeOSTypesTests.swift 3 | // UTTypeOSTypesTests 4 | // 5 | // Created by C.W. Betts on 11/13/21. 6 | // Copyright © 2021 C.W. Betts. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import UTTypeOSTypes 11 | 12 | class UTTypeOSTypesTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() throws { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() throws { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | --------------------------------------------------------------------------------