From 4a383cec50709ec8cb2a1a06ced1735292a34b28 Mon Sep 17 00:00:00 2001 From: Xander Deng Date: Tue, 8 Oct 2019 19:24:12 +0800 Subject: [PATCH] add dependency SwiftCF --- LyricsX.xcodeproj/project.pbxproj | 37 +- .../xcshareddata/swiftpm/Package.resolved | 9 + .../Controller/KaraokeLyricsController.swift | 1 + LyricsX/Supporting Files/Info.plist | 4 +- LyricsX/Utility/CFExtension.swift | 71 +-- LyricsX/Utility/CGExtension.swift | 528 ------------------ LyricsX/Utility/CTExtension.swift | 124 ---- LyricsX/Utility/StdExtension.swift | 21 - LyricsX/View/KaraokeLabel.swift | 35 +- 9 files changed, 57 insertions(+), 773 deletions(-) delete mode 100644 LyricsX/Utility/CGExtension.swift delete mode 100644 LyricsX/Utility/CTExtension.swift diff --git a/LyricsX.xcodeproj/project.pbxproj b/LyricsX.xcodeproj/project.pbxproj index fb9db92f..73c0abe9 100644 --- a/LyricsX.xcodeproj/project.pbxproj +++ b/LyricsX.xcodeproj/project.pbxproj @@ -31,11 +31,11 @@ BB0BE95E1F44508F0068FF67 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0BE95C1F44508F0068FF67 /* Fabric.framework */; }; BB1A3185207B261500406E32 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1A3184207B261500406E32 /* Regex.swift */; }; BB1B2FFD20E8780A002F1AF8 /* CFExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B2FFC20E8780A002F1AF8 /* CFExtension.swift */; }; - BB1B2FFF20E87838002F1AF8 /* CTExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B2FFE20E87838002F1AF8 /* CTExtension.swift */; }; - BB1B300120E8788C002F1AF8 /* CGExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B300020E8788C002F1AF8 /* CGExtension.swift */; }; BB1B300320E878AE002F1AF8 /* StdExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B300220E878AE002F1AF8 /* StdExtension.swift */; }; BB1B300520E87CC9002F1AF8 /* KaraokeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B300420E87CC9002F1AF8 /* KaraokeLabel.swift */; }; BB1B631B1E8CD7CA004CC9E0 /* ScrollLyricsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B631A1E8CD7CA004CC9E0 /* ScrollLyricsView.swift */; }; + BB22681C234C9085008FAFED /* SwiftCF in Frameworks */ = {isa = PBXBuildFile; productRef = BB22681B234C9085008FAFED /* SwiftCF */; }; + BB22681E234C908D008FAFED /* SwiftCF in Frameworks */ = {isa = PBXBuildFile; productRef = BB22681D234C908D008FAFED /* SwiftCF */; }; BB34F4A91EFFBCBC008B9E4B /* MASShortcut.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB34F4A81EFFBCBC008B9E4B /* MASShortcut.framework */; }; BB34F4AA1EFFBCBC008B9E4B /* MASShortcut.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BB34F4A81EFFBCBC008B9E4B /* MASShortcut.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BB34F7E61F21911700B7C484 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB34F7E51F21911700B7C484 /* Updater.swift */; }; @@ -60,11 +60,9 @@ BB50786E22014C3900B695C3 /* DragNDropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD03C361E9B2C7200EFB975 /* DragNDropView.swift */; }; BB50786F22014C3900B695C3 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EBDA2421115A150019FDF9 /* Observation.swift */; }; BB50787022014C3900B695C3 /* TouchBarLyricsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5863F221EFFD9F0084D2C0 /* TouchBarLyricsItem.swift */; }; - BB50787122014C3900B695C3 /* CGExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B300020E8788C002F1AF8 /* CGExtension.swift */; }; BB50787222014C3900B695C3 /* CFExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B2FFC20E8780A002F1AF8 /* CFExtension.swift */; }; BB50787322014C3900B695C3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4141AD1E458BA800A51775 /* AppDelegate.swift */; }; BB50787422014C3900B695C3 /* LyricsMetaData+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB458C62201C5F4500740C3B /* LyricsMetaData+Extension.swift */; }; - BB50787522014C3900B695C3 /* CTExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1B2FFE20E87838002F1AF8 /* CTExtension.swift */; }; BB50787622014C3900B695C3 /* PreferenceGeneralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC7C5541E6AE28600E3EC4F /* PreferenceGeneralViewController.swift */; }; BB50787722014C3900B695C3 /* Then.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE8FC741F6BEA6200122AFA /* Then.swift */; }; BB50787822014C3900B695C3 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB34F7E51F21911700B7C484 /* Updater.swift */; }; @@ -211,8 +209,6 @@ BB0BE95C1F44508F0068FF67 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fabric.framework; path = LyricsX/Frameworks/Fabric.framework; sourceTree = ""; }; BB1A3184207B261500406E32 /* Regex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = ""; }; BB1B2FFC20E8780A002F1AF8 /* CFExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CFExtension.swift; sourceTree = ""; }; - BB1B2FFE20E87838002F1AF8 /* CTExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTExtension.swift; sourceTree = ""; }; - BB1B300020E8788C002F1AF8 /* CGExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGExtension.swift; sourceTree = ""; }; BB1B300220E878AE002F1AF8 /* StdExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StdExtension.swift; sourceTree = ""; }; BB1B300420E87CC9002F1AF8 /* KaraokeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KaraokeLabel.swift; sourceTree = ""; }; BB1B631A1E8CD7CA004CC9E0 /* ScrollLyricsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollLyricsView.swift; sourceTree = ""; }; @@ -301,6 +297,7 @@ BBB03F3A2331D8A800A3879E /* OpenCC in Frameworks */, BBB03F30232FB40400A3879E /* Semver in Frameworks */, BB34F4A91EFFBCBC008B9E4B /* MASShortcut.framework in Frameworks */, + BB22681C234C9085008FAFED /* SwiftCF in Frameworks */, BBB03F2D232FB3AF00A3879E /* GenericID in Frameworks */, BBB03F3D2331DACD00A3879E /* LyricsKit in Frameworks */, ); @@ -316,6 +313,7 @@ BB50788D22014C3900B695C3 /* DFRPrivate.framework in Frameworks */, BBB03F3F2331DBB000A3879E /* GenericID in Frameworks */, BBB03F452331DBB000A3879E /* LyricsKit in Frameworks */, + BB22681E234C908D008FAFED /* SwiftCF in Frameworks */, BBB03F412331DBB000A3879E /* Semver in Frameworks */, BB9C3D8523435A6300860698 /* MusicPlayer in Frameworks */, BB50788F22014C3900B695C3 /* SnapKit.framework in Frameworks */, @@ -439,9 +437,7 @@ E97593E52150F6FB00D80616 /* IBInspection.swift */, BB9BCEB91E843157001BC54B /* Extension.swift */, BB1B300220E878AE002F1AF8 /* StdExtension.swift */, - BB1B300020E8788C002F1AF8 /* CGExtension.swift */, BB1B2FFC20E8780A002F1AF8 /* CFExtension.swift */, - BB1B2FFE20E87838002F1AF8 /* CTExtension.swift */, E9EBDA2421115A150019FDF9 /* Observation.swift */, BB1A3184207B261500406E32 /* Regex.swift */, BBE8FC741F6BEA6200122AFA /* Then.swift */, @@ -518,6 +514,7 @@ BBB03F392331D8A800A3879E /* OpenCC */, BBB03F3C2331DACD00A3879E /* LyricsKit */, BB9C3D8223435A5700860698 /* MusicPlayer */, + BB22681B234C9085008FAFED /* SwiftCF */, ); productName = LyricsX; productReference = BB4141AA1E458BA800A51775 /* LyricsX.app */; @@ -548,6 +545,7 @@ BBB03F422331DBB000A3879E /* OpenCC */, BBB03F442331DBB000A3879E /* LyricsKit */, BB9C3D8423435A6300860698 /* MusicPlayer */, + BB22681D234C908D008FAFED /* SwiftCF */, ); productName = LyricsX; productReference = BB5078AD22014C3900B695C3 /* LyricsX.app */; @@ -644,6 +642,7 @@ BBB03F382331D8A800A3879E /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */, BBB03F3B2331DACD00A3879E /* XCRemoteSwiftPackageReference "LyricsKit" */, BB9C3D8123435A5700860698 /* XCRemoteSwiftPackageReference "MusicPlayer" */, + BB22681A234C9085008FAFED /* XCRemoteSwiftPackageReference "SwiftCF" */, ); productRefGroup = BB4141AB1E458BA800A51775 /* Products */; projectDirPath = ""; @@ -871,11 +870,9 @@ BBD03C371E9B2C7200EFB975 /* DragNDropView.swift in Sources */, E9EBDA2521115A150019FDF9 /* Observation.swift in Sources */, BB5863F321EFFD9F0084D2C0 /* TouchBarLyricsItem.swift in Sources */, - BB1B300120E8788C002F1AF8 /* CGExtension.swift in Sources */, BB1B2FFD20E8780A002F1AF8 /* CFExtension.swift in Sources */, BB4141AE1E458BA800A51775 /* AppDelegate.swift in Sources */, BB458C63201C5F4500740C3B /* LyricsMetaData+Extension.swift in Sources */, - BB1B2FFF20E87838002F1AF8 /* CTExtension.swift in Sources */, BBC7C5551E6AE28600E3EC4F /* PreferenceGeneralViewController.swift in Sources */, BBE8FC751F6BEA6200122AFA /* Then.swift in Sources */, BB34F7E61F21911700B7C484 /* Updater.swift in Sources */, @@ -916,11 +913,9 @@ BB50786E22014C3900B695C3 /* DragNDropView.swift in Sources */, BB50786F22014C3900B695C3 /* Observation.swift in Sources */, BB50787022014C3900B695C3 /* TouchBarLyricsItem.swift in Sources */, - BB50787122014C3900B695C3 /* CGExtension.swift in Sources */, BB50787222014C3900B695C3 /* CFExtension.swift in Sources */, BB50787322014C3900B695C3 /* AppDelegate.swift in Sources */, BB50787422014C3900B695C3 /* LyricsMetaData+Extension.swift in Sources */, - BB50787522014C3900B695C3 /* CTExtension.swift in Sources */, BB50787622014C3900B695C3 /* PreferenceGeneralViewController.swift in Sources */, BB50787722014C3900B695C3 /* Then.swift in Sources */, BB50787822014C3900B695C3 /* Updater.swift in Sources */, @@ -1356,6 +1351,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + BB22681A234C9085008FAFED /* XCRemoteSwiftPackageReference "SwiftCF" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ddddxxx/SwiftCF.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.1.0; + }; + }; BB9C3D8123435A5700860698 /* XCRemoteSwiftPackageReference "MusicPlayer" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ddddxxx/MusicPlayer"; @@ -1399,6 +1402,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + BB22681B234C9085008FAFED /* SwiftCF */ = { + isa = XCSwiftPackageProductDependency; + package = BB22681A234C9085008FAFED /* XCRemoteSwiftPackageReference "SwiftCF" */; + productName = SwiftCF; + }; + BB22681D234C908D008FAFED /* SwiftCF */ = { + isa = XCSwiftPackageProductDependency; + package = BB22681A234C9085008FAFED /* XCRemoteSwiftPackageReference "SwiftCF" */; + productName = SwiftCF; + }; BB9C3D8223435A5700860698 /* MusicPlayer */ = { isa = XCSwiftPackageProductDependency; package = BB9C3D8123435A5700860698 /* XCRemoteSwiftPackageReference "MusicPlayer" */; diff --git a/LyricsX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LyricsX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8aa822e8..e3132955 100644 --- a/LyricsX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LyricsX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -91,6 +91,15 @@ "version": "0.1.2" } }, + { + "package": "SwiftCF", + "repositoryURL": "https://github.com/ddddxxx/SwiftCF.git", + "state": { + "branch": null, + "revision": "97d353849f530d6c3df47f5e429e021514edce2d", + "version": "0.1.0" + } + }, { "package": "SwiftyOpenCC", "repositoryURL": "https://github.com/ddddxxx/SwiftyOpenCC.git", diff --git a/LyricsX/Controller/KaraokeLyricsController.swift b/LyricsX/Controller/KaraokeLyricsController.swift index d832813a..46c84669 100644 --- a/LyricsX/Controller/KaraokeLyricsController.swift +++ b/LyricsX/Controller/KaraokeLyricsController.swift @@ -25,6 +25,7 @@ import LyricsCore import MusicPlayer import OpenCC import SnapKit +import SwiftCF class KaraokeLyricsWindowController: NSWindowController { diff --git a/LyricsX/Supporting Files/Info.plist b/LyricsX/Supporting Files/Info.plist index f2408555..b42c8d07 100644 --- a/LyricsX/Supporting Files/Info.plist +++ b/LyricsX/Supporting Files/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.5.1 CFBundleVersion - 2202 + 2209 Fabric APIKey @@ -43,7 +43,7 @@ LSUIElement LX_BUILD_TIME - 1570527258 + 1570533589 NSAppTransportSecurity NSExceptionDomains diff --git a/LyricsX/Utility/CFExtension.swift b/LyricsX/Utility/CFExtension.swift index 305b9997..d778f2fd 100644 --- a/LyricsX/Utility/CFExtension.swift +++ b/LyricsX/Utility/CFExtension.swift @@ -32,71 +32,11 @@ extension NSString { extension CFStringTokenizer { - struct Attribute: RawRepresentable { - - var rawValue: CFOptionFlags - - init(rawValue: CFOptionFlags) { - self.rawValue = rawValue - } - - static let latinTranscription = Attribute(rawValue: kCFStringTokenizerAttributeLatinTranscription) - static let language = Attribute(rawValue: kCFStringTokenizerAttributeLanguage) - } - - struct Unit: RawRepresentable { - - var rawValue: CFOptionFlags - - init(rawValue: CFOptionFlags) { - self.rawValue = rawValue - } - - static let word = Unit(rawValue: kCFStringTokenizerUnitWord) - static let sentence = Unit(rawValue: kCFStringTokenizerUnitSentence) - static let paragraph = Unit(rawValue: kCFStringTokenizerUnitParagraph) - static let lineBreak = Unit(rawValue: kCFStringTokenizerUnitLineBreak) - static let wordBoundary = Unit(rawValue: kCFStringTokenizerUnitWordBoundary) - } - - static func create(_ string: NSString, range: NSRange? = nil, unit: Unit = .wordBoundary, locale: NSLocale? = nil) -> CFStringTokenizer { - let cfStr = string as CFString - return CFStringTokenizerCreate(nil, cfStr, range?.asCF ?? cfStr.fullRange, unit.rawValue, locale as CFLocale?) - } - - func nextToken() -> CFStringTokenizerTokenType? { - let token = CFStringTokenizerAdvanceToNextToken(self) - if token.isEmpty { return nil } - return token - } - - func token(at index: CFIndex) -> CFStringTokenizerTokenType? { - let token = CFStringTokenizerGoToTokenAtIndex(self, index) - if token.isEmpty { return nil } - return token - } - - func currentTokenRange() -> NSRange { - return CFStringTokenizerGetCurrentTokenRange(self).asNS - } - - func currentTokenAttribute(_ attribute: Attribute) -> NSString? { - // swiftlint:disable:next force_cast - return CFStringTokenizerCopyCurrentTokenAttribute(self, attribute.rawValue) as! NSString? - } - - func currentSubTokens() -> [CFStringTokenizerTokenType] { - let arr = NSMutableArray() - CFStringTokenizerGetCurrentSubTokens(self, nil, 0, arr as CFMutableArray) - // swiftlint:disable:next force_cast - return arr as! [CFStringTokenizerTokenType] - } - func currentFuriganaAnnotation(in string: NSString) -> (NSString, NSRange)? { let range = currentTokenRange() - let tokenStr = string.substring(with: range) + let tokenStr = string.substring(with: range.asNS) guard tokenStr.unicodeScalars.contains(where: CharacterSet.kanji.contains), - let latin = currentTokenAttribute(.latinTranscription), + let latin = currentTokenAttribute(.latinTranscription)?.asNS, let hiragana = latin.applyingTransform(.latinToHiragana, reverse: false), let (rangeToAnnotate, rangeInAnnotation) = rangeOfUncommonContent(tokenStr, hiragana) else { return nil @@ -134,10 +74,3 @@ private func rangeOfUncommonContent(_ s1: String, _ s2: String) -> (Range CFStringTokenizerTokenType? { - return nextToken() - } -} diff --git a/LyricsX/Utility/CGExtension.swift b/LyricsX/Utility/CGExtension.swift deleted file mode 100644 index a2b67df9..00000000 --- a/LyricsX/Utility/CGExtension.swift +++ /dev/null @@ -1,528 +0,0 @@ -// -// CGExtension.swift -// -// This file is part of LyricsX -// Copyright (C) 2017 Xander Deng - https://github.com/ddddxxx/LyricsX -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -import CoreGraphics -import QuartzCore - -// swiftlint:disable shorthand_operator identifier_name operator_whitespace - -// MARK: - CGFloat - -extension CGFloat { - - /// degrees to radians - func toRadians() -> CGFloat { - return self * .pi / 180 - } - - /// radians to degrees - func toDegrees() -> CGFloat { - return self * 180 / .pi - } -} - -// MARK: - CGPoint - -extension CGPoint { - - init(_ vector: CGVector) { - self.init(x: vector.dx, y: vector.dy) - } - - func distance(to point: CGPoint) -> CGFloat { - return CGVector(from: self, to: point).length - } - - static func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint { - return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) - } - - static func +=(lhs: inout CGPoint, rhs: CGPoint) { - lhs = lhs + rhs - } - - static func +(lhs: CGPoint, rhs: CGVector) -> CGPoint { - return CGPoint(x: lhs.x + rhs.dx, y: lhs.y + rhs.dy) - } - - static func +=(lhs: inout CGPoint, rhs: CGVector) { - lhs = lhs + rhs - } - - static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint { - return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) - } - - static func -=(lhs: inout CGPoint, rhs: CGPoint) { - lhs = lhs - rhs - } - - static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint { - return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy) - } - - static func -=(lhs: inout CGPoint, rhs: CGVector) { - lhs = lhs - rhs - } - - static func *(point: CGPoint, scalar: CGFloat) -> CGPoint { - return CGPoint(x: point.x * scalar, y: point.y * scalar) - } - - static func *=(point: inout CGPoint, scalar: CGFloat) { - point = point * scalar - } - - static func /(point: CGPoint, scalar: CGFloat) -> CGPoint { - return CGPoint(x: point.x / scalar, y: point.y / scalar) - } - - static func /=(point: inout CGPoint, scalar: CGFloat) { - point = point / scalar - } - - mutating func apply(t: CGAffineTransform) { - self = applying(t) - } -} - -// MARK: - CGVector - -extension CGVector { - - init(_ point: CGPoint) { - self.init(dx: point.x, dy: point.y) - } - - init(from: CGPoint, to: CGPoint) { - self.init(dx: to.x - from.x, dy: to.y - from.y) - } - - init(angle: CGFloat, length: CGFloat = 1) { - self.init(dx: cos(angle) * length, dy: sin(angle) * length) - } - - var length: CGFloat { - get { - return hypot(dx, dy) - } - set { - guard self != .zero else { return } - let scale = newValue / length - dx *= scale - dy *= scale - } - } - - /// in radians - var angle: CGFloat { - return atan2(dy, dx) - } - - static func +(lhs: CGVector, rhs: CGVector) -> CGVector { - return CGVector(dx: lhs.dx + rhs.dx, dy: lhs.dy + rhs.dy) - } - - static func +=(lhs: inout CGVector, rhs: CGVector) { - lhs = lhs + rhs - } - - static func -(lhs: CGVector, rhs: CGVector) -> CGVector { - return CGVector(dx: lhs.dx - rhs.dx, dy: lhs.dy - rhs.dy) - } - - static func -=(lhs: inout CGVector, rhs: CGVector) { - lhs = lhs - rhs - } - - static func *(vector: CGVector, scalar: CGFloat) -> CGVector { - return CGVector(dx: vector.dx * scalar, dy: vector.dy * scalar) - } - - static func *=(vector: inout CGVector, scalar: CGFloat) { - vector = vector * scalar - } - - static func /(vector: CGVector, scalar: CGFloat) -> CGVector { - return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar) - } - - static func /=(vector: inout CGVector, scalar: CGFloat) { - vector = vector / scalar - } -} - -// MARK: - CGSize - -extension CGSize { - - var area: CGFloat { - return width * height - } - - func aspectFit(to size: CGSize) -> CGSize { - let xScale = size.width / width - let yScale = size.height / height - return self * min(xScale, yScale) - } - - var aspectFitSquare: CGSize { - let minSide = min(width, height) - return CGSize(width: minSide, height: minSide) - } - - func aspectFill(to size: CGSize) -> CGSize { - let xScale = size.width / width - let yScale = size.height / height - return self * max(xScale, yScale) - } - - var aspectFillSquare: CGSize { - let maxSide = max(width, height) - return CGSize(width: maxSide, height: maxSide) - } - - static func +(lhs: CGSize, rhs: CGSize) -> CGSize { - return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) - } - - static func +=(lhs: inout CGSize, rhs: CGSize) { - lhs = lhs + rhs - } - - static func -(lhs: CGSize, rhs: CGSize) -> CGSize { - return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) - } - - static func -=(lhs: inout CGSize, rhs: CGSize) { - lhs = lhs - rhs - } - - static func *(size: CGSize, scalar: CGFloat) -> CGSize { - return CGSize(width: size.width * scalar, height: size.height * scalar) - } - - static func *=(size: inout CGSize, scalar: CGFloat) { - size = size * scalar - } - - static func /(size: CGSize, scalar: CGFloat) -> CGSize { - return CGSize(width: size.width / scalar, height: size.height / scalar) - } - - static func /=(size: inout CGSize, scalar: CGFloat) { - size = size / scalar - } - - mutating func apply(t: CGAffineTransform) { - self = applying(t) - } -} - -// MARK: - CGRect - -extension CGRect { - - init(minX: CGFloat, minY: CGFloat, maxX: CGFloat, maxY: CGFloat) { - self.init(x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY) - } - - init(center: CGPoint, size: CGSize) { - self.init(x: center.x - size.width / 2, - y: center.y - size.height / 2, - width: size.width, - height: size.height) - } - - var area: CGFloat { - return width * height - } - - var center: CGPoint { - get { - return CGPoint(x: midX, y: midY) - } - set { - origin.x = newValue.x - size.width / 2 - origin.y = newValue.y - size.height / 2 - } - } - - func center(on edge: CGRectEdge) -> CGPoint { - switch edge { - case .maxXEdge: return CGPoint(x: maxX, y: midY) - case .maxYEdge: return CGPoint(x: midX, y: maxY) - case .minXEdge: return CGPoint(x: minX, y: midY) - case .minYEdge: return CGPoint(x: midX, y: minY) - } - } - - func offsetBy(dx: CGFloat = 0, dy: CGFloat = 0) -> CGRect { - return CGRect(x: minX + dx, y: minY + dy, width: width, height: height) - } - - mutating func formOffsetBy(dx: CGFloat = 0, dy: CGFloat = 0) { - self = offsetBy(dx: dx, dy: dy) - } - - func insetBy(x: CGFloat = 0, y: CGFloat = 0) -> CGRect { - return insetBy(minX: x, minY: y, maxX: x, maxY: y) - } - - mutating func formInsetBy(x: CGFloat = 0, y: CGFloat = 0) { - self = insetBy(x: x, y: y) - } - - func insetBy(minX: CGFloat = 0, minY: CGFloat = 0, maxX: CGFloat = 0, maxY: CGFloat = 0) -> CGRect { - return CGRect(x: self.minX + minX, - y: self.minY + minY, - width: width - minX - maxX, - height: height - minY - maxY) - } - - mutating func formInsetBy(minX: CGFloat = 0, minY: CGFloat = 0, maxX: CGFloat = 0, maxY: CGFloat = 0) { - self = insetBy(minX: minX, minY: minY, maxX: maxX, maxY: maxY) - } - - func extendBy(x: CGFloat = 0, y: CGFloat = 0) -> CGRect { - return insetBy(x: -x, y: -y) - } - - mutating func formExtendBy(x: CGFloat = 0, y: CGFloat = 0) { - self = extendBy(x: x, y: y) - } - - func extendBy(minX: CGFloat = 0, minY: CGFloat = 0, maxX: CGFloat = 0, maxY: CGFloat = 0) -> CGRect { - return insetBy(minX: -minX, minY: -minY, maxX: -maxX, maxY: -maxY) - } - - mutating func formExtendBy(minX: CGFloat = 0, minY: CGFloat = 0, maxX: CGFloat = 0, maxY: CGFloat = 0) { - self = extendBy(minX: minX, minY: minY, maxX: maxX, maxY: maxY) - } - - mutating func apply(t: CGAffineTransform) { - self = applying(t) - } -} - -// MARK: - CGAffineTransform - -extension CGAffineTransform { - - // MARK: creat - - init() { - self = .identity - } - - static func translate(x: CGFloat = 0, y: CGFloat = 0) -> CGAffineTransform { - return CGAffineTransform(translationX: x, y: y) - } - - static func scale(x: CGFloat = 1, y: CGFloat = 1) -> CGAffineTransform { - return CGAffineTransform(scaleX: x, y: y) - } - - static func rotate(_ angle: CGFloat) -> CGAffineTransform { - return CGAffineTransform(rotationAngle: angle) - } - - static func flip(height: CGFloat) -> CGAffineTransform { - return CGAffineTransform(translationX: 0, y: height).scaledBy(x: 1, y: -1) - } - - static func swap() -> CGAffineTransform { - return CGAffineTransform(scaleX: -1, y: 1).rotated(by: .pi / 2) - } - - // MARK: mutate - - func transformed(by t2: CGAffineTransform) -> CGAffineTransform { - return t2.concatenating(self) - } - - mutating func invert() { - self = inverted() - } - - mutating func transform(by t2: CGAffineTransform) { - self = transformed(by: t2) - } - - mutating func translateBy(x: CGFloat = 0, y: CGFloat = 0) { - self = translatedBy(x: x, y: y) - } - - mutating func scaleBy(x: CGFloat = 1, y: CGFloat = 1) { - self = scaledBy(x: x, y: y) - } - - mutating func rotate(by angle: CGFloat) { - self = rotated(by: angle) - } - - // MARK: operator - - static func *(lhs: CGAffineTransform, rhs: CGAffineTransform) -> CGAffineTransform { - return lhs.concatenating(rhs) - } - - static func *=(lhs: inout CGAffineTransform, rhs: CGAffineTransform) { - lhs = lhs * rhs - } - - static func /(lhs: CGAffineTransform, rhs: CGAffineTransform) -> CGAffineTransform { - return lhs.concatenating(rhs.inverted()) - } - - static func /=(lhs: inout CGAffineTransform, rhs: CGAffineTransform) { - lhs = lhs / rhs - } -} - -// MARK: - CATransform3D - -extension CATransform3D { - - var cameraDistance: CGFloat { - get { - return -1 / m34 - } - set { - m34 = -1 / newValue - } - } - - // MAKE: creat - - static var identity: CATransform3D { - return CATransform3DIdentity - } - - init() { - self = CATransform3DIdentity - } - - static func translation(x tx: CGFloat = 0, y ty: CGFloat = 0, z tz: CGFloat = 0) -> CATransform3D { - return CATransform3DMakeTranslation(tx, ty, tz) - } - - static func scale(x sx: CGFloat = 1, y sy: CGFloat = 1, z sz: CGFloat = 1) -> CATransform3D { - return CATransform3DMakeScale(sx, sy, sz) - } - - static func rotation(angle: CGFloat, x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> CATransform3D { - return CATransform3DMakeRotation(angle, x, y, z) - } - - // MARK: affine - - init(_ affineTransform: CGAffineTransform) { - self = CATransform3DMakeAffineTransform(affineTransform) - } - - var isAffine: Bool { - return CATransform3DIsAffine(self) - } - - var affineTransform: CGAffineTransform { - return CATransform3DGetAffineTransform(self) - } - - // MARK: transform - - var inverse: CATransform3D { - return CATransform3DInvert(self) - } - - func translatedBy(x tx: CGFloat = 0, y ty: CGFloat = 0, z tz: CGFloat = 0) -> CATransform3D { - return CATransform3DTranslate(self, tx, ty, tz) - } - - func scaledBy(x sx: CGFloat = 1, y sy: CGFloat = 1, z sz: CGFloat = 1) -> CATransform3D { - return CATransform3DScale(self, sx, sy, sz) - } - - func rotatedBy(angle: CGFloat, x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> CATransform3D { - return CATransform3DRotate(self, angle, x, y, z) - } - - func transformed(by t2: CATransform3D) -> CATransform3D { - return CATransform3DConcat(t2, self) - } - - func concatenating(_ t2: CATransform3D) -> CATransform3D { - return CATransform3DConcat(self, t2) - } - - // MARK: mutate - - mutating func inverte() { - self = inverse - } - - mutating func scaleBy(x sx: CGFloat = 1, y sy: CGFloat = 1, z sz: CGFloat = 1) { - self = scaledBy(x: sx, y: sy, z: sz) - } - - mutating func rotateBy(angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) { - self = rotatedBy(angle: angle, x: x, y: y, z: z) - } - - mutating func transforme(by t2: CATransform3D) { - self = transformed(by: t2) - } - - mutating func translateBy(x tx: CGFloat = 0, y ty: CGFloat = 0, z tz: CGFloat = 0) { - self = translatedBy(x: tx, y: ty, z: tz) - } - - mutating func concat(_ t2: CATransform3D) { - self = concatenating(t2) - } - - // MARK: operator - - static func *(lhs: CATransform3D, rhs: CATransform3D) -> CATransform3D { - return lhs.concatenating(rhs) - } - - static func *=(lhs: inout CATransform3D, rhs: CATransform3D) { - lhs = lhs * rhs - } - - static func /(lhs: CATransform3D, rhs: CATransform3D) -> CATransform3D { - return lhs.concatenating(rhs.inverse) - } - - static func /=(lhs: inout CATransform3D, rhs: CATransform3D) { - lhs = lhs / rhs - } -} - -extension CATransform3D: Equatable { - - public static func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool { - return CATransform3DEqualToTransform(lhs, rhs) - } -} diff --git a/LyricsX/Utility/CTExtension.swift b/LyricsX/Utility/CTExtension.swift deleted file mode 100644 index a6c70631..00000000 --- a/LyricsX/Utility/CTExtension.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// CTExtension.swift -// -// This file is part of LyricsX -// Copyright (C) 2017 Xander Deng - https://github.com/ddddxxx/LyricsX -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -import CoreText -import Foundation - -// MARK: - CTRubyAnnotation - -extension NSAttributedString.Key { - - static let rubyAnnotation = kCTRubyAnnotationAttributeName as NSAttributedString.Key - - /// kCTRubyAnnotationSizeFactorAttributeName - static let rubyAnnotationSizeFactor = NSAttributedString.Key("CTRubyAnnotationSizeFactor") - - static let ctForegroundColor = kCTForegroundColorAttributeName as NSAttributedString.Key -} - -extension CTRubyAnnotation { - - static func create(_ string: NSString, - position: CTRubyPosition = .before, - alignment: CTRubyAlignment = .auto, - overhang: CTRubyOverhang = .auto, - attributes: [NSAttributedString.Key: Any] = [:]) -> CTRubyAnnotation { - if #available(OSX 10.12, *) { - return CTRubyAnnotationCreateWithAttributes(alignment, overhang, position, string as CFString, attributes as CFDictionary) - } else { - let string = NSString(string: string) - let count = Int(CTRubyPosition.count.rawValue) - let text = UnsafeMutablePointer?>.allocate(capacity: count) - let sizeFactor = attributes[.rubyAnnotationSizeFactor] as? NSNumber as? CGFloat ?? 0.5 - defer { text.deallocate() } - text.initialize(repeating: nil, count: count) - let pos = Int(position.rawValue).clamped(to: 0.. { - let lines = self.lines - let lineCount = lines.count - let range = CFRange(location: 0, length: lineCount) - var arr = [CGPoint](repeating: .zero, count: lineCount) - CTFrameGetLineOrigins(self, range, &arr) - return zip(lines, arr) - } -} - -// MARK: - CTLine - -extension CTLine { - - var glyphCount: CFIndex { - return CTLineGetGlyphCount(self) - } - - var glyphRuns: [CTRun] { - // swiftlint:disable:next force_cast - return CTLineGetGlyphRuns(self) as! [CTRun] - } - - var stringRange: NSRange { - return CTLineGetStringRange(self).asNS - } - - var trailingWhitespaceWidth: Double { - return CTLineGetTrailingWhitespaceWidth(self) - } - - func bounds(options: CTLineBoundsOptions = []) -> CGRect { - return CTLineGetBoundsWithOptions(self, options) - } - - func imageBounds(context: CGContext) -> CGRect { - return CTLineGetImageBounds(self, context) - } - - func offset(charIndex: CFIndex) -> CGFloat { - return CTLineGetOffsetForStringIndex(self, charIndex, nil) - } - - func enumerateCaretOffsets(_ block: (_ offset: Double, _ charIndex: CFIndex, _ leadingEdge: Bool, _ stop: UnsafeMutablePointer) -> Void) { - withoutActuallyEscaping(block) { block in - CTLineEnumerateCaretOffsets(self, block) - } - } -} diff --git a/LyricsX/Utility/StdExtension.swift b/LyricsX/Utility/StdExtension.swift index bd1992cb..7a81f88a 100644 --- a/LyricsX/Utility/StdExtension.swift +++ b/LyricsX/Utility/StdExtension.swift @@ -53,27 +53,6 @@ extension Strideable { // MARK: - Range -extension CFRange { - - var asNS: NSRange { - return NSRange(location: location, length: length) - } -} - -extension NSRange { - - var asCF: CFRange { - return CFRange(location: location, length: length) - } -} - -extension CFString { - - var fullRange: CFRange { - return CFRange(location: 0, length: CFStringGetLength(self)) - } -} - extension NSString { var fullRange: NSRange { diff --git a/LyricsX/View/KaraokeLabel.swift b/LyricsX/View/KaraokeLabel.swift index 2cb5fa07..018684d3 100644 --- a/LyricsX/View/KaraokeLabel.swift +++ b/LyricsX/View/KaraokeLabel.swift @@ -19,6 +19,7 @@ // import Cocoa +import SwiftCF class KaraokeLabel: NSTextField { @@ -76,20 +77,20 @@ class KaraokeLabel: NSTextField { let attrString = NSMutableAttributedString(attributedString: attributedStringValue) let string = attrString.string as NSString let shouldDrawFurigana = drawFurigana && string.dominantLanguage == "ja" - let tokenizer = CFStringTokenizer.create(string) - for tokenType in tokenizer where tokenType.contains(.isCJWordMask) { + let tokenizer = CFStringTokenizer.create(string: .from(string)) + for tokenType in IteratorSequence(tokenizer) where tokenType.contains(.isCJWordMask) { if isVertical { let tokenRange = tokenizer.currentTokenRange() let attr: [NSAttributedString.Key: Any] = [ .verticalGlyphForm: true, .baselineOffset: (font?.pointSize ?? 24) * 0.25, ] - attrString.addAttributes(attr, range: tokenRange) + attrString.addAttributes(attr, range: tokenRange.asNS) } guard shouldDrawFurigana else { continue } if let (furigana, range) = tokenizer.currentFuriganaAnnotation(in: string) { - var attr: [NSAttributedString.Key: Any] = [.rubyAnnotationSizeFactor: 0.5] - attr[.ctForegroundColor] = textColor + var attr: [CFAttributedString.Key: Any] = [.rubySizeFactor: 0.5] + attr[.foregroundColor] = textColor let annotation = CTRubyAnnotation.create(furigana, attributes: attr) attrString.addAttribute(.rubyAnnotation, value: annotation, range: range) } @@ -106,25 +107,21 @@ class KaraokeLabel: NSTextField { } layoutSubtreeIfNeeded() let progression: CTFrameProgression = isVertical ? .rightToLeft : .topToBottom - let frameAttr: NSDictionary = [kCTFrameProgressionAttributeName: NSNumber(value: progression.rawValue)] - let framesetter = CTFramesetterCreateWithAttributedString(attrString) - let fullRange = attrString.fullRange.asCF - var fitRange = CFRange() - let suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, fullRange, frameAttr, bounds.size, &fitRange) + let frameAttr: [CTFrame.AttributeKey: Any] = [.progression: progression] + let framesetter = CTFramesetter.create(attributedString: attrString) + let (suggestSize, fitRange) = framesetter.suggestFrameSize(constraints: bounds.size, frameAttributes: frameAttr) let path = CGPath(rect: CGRect(origin: .zero, size: suggestSize), transform: nil) - let ctFrame = CTFramesetterCreateFrame(framesetter, fitRange, path, frameAttr) + let ctFrame = framesetter.frame(stringRange: fitRange, path: path, frameAttributes: frameAttr) _ctFrame = ctFrame return ctFrame } override var intrinsicContentSize: NSSize { - let framesetter = CTFramesetterCreateWithAttributedString(attrString) - let cfRange = attrString.fullRange.asCF let progression: CTFrameProgression = isVertical ? .rightToLeft : .topToBottom - let frameAttr: NSDictionary = [kCTFrameProgressionAttributeName: NSNumber(value: progression.rawValue)] + let frameAttr: [CTFrame.AttributeKey: Any] = [.progression: progression] + let framesetter = CTFramesetter.create(attributedString: attrString) let constraints = CGSize(width: CGFloat.infinity, height: .infinity) - let suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, cfRange, frameAttr, constraints, nil) - return suggestSize + return framesetter.suggestFrameSize(constraints: constraints, frameAttributes: frameAttr).size } override func draw(_ dirtyRect: NSRect) { @@ -157,7 +154,7 @@ class KaraokeLabel: NSTextField { func setProgressAnimation(color: NSColor, progress: [(TimeInterval, Int)]) { removeProgressAnimation() guard let line = ctFrame.lines.first, - let origin = ctFrame.lineOrigins.first else { + let origin = ctFrame.lineOrigins(range: CFRange(location: 0, length: 1)).first else { return } var lineBounds = line.bounds() @@ -224,3 +221,7 @@ class KaraokeLabel: NSTextField { CATransaction.commit() } } + +extension CFAttributedString.Key { + static let rubyAnnotation = kCTRubyAnnotationAttributeName as CFAttributedString.Key +}