diff --git a/CHANGELOG.md b/CHANGELOG.md index 060a55b0..c0582633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +v1.2.0 +---------------- +- Reimplemented Huffman Coding using a tree-like structure. +- Now only DataWithPointer is used during the processing. + v1.1.2 ---------------- - Fixed memory problem in Deflate. diff --git a/README.md b/README.md index ddbce6e7..a0166bee 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Then run `carthage update`. Finally, drag and drop `SWCompression.framework` from `Carthage/Build` folder into the "Embedded Binaries" section on your targets' "General" tab. ##### Swift Package Manager -Add to you package specification's dependecies `.Package(url: "https://github.com/tsolomko/SWCompression.git")`, for example like this: +Add to you package dependecies `.Package(url: "https://github.com/tsolomko/SWCompression.git")`, for example like this: ```swift import PackageDescription @@ -79,7 +79,7 @@ If you'd like to decompress "deflated" data just use: let data = try! Data(contentsOf: URL(fileURLWithPath: "path/to/file")) let decompressedData = try? Deflate.decompress(compressedData: data) ``` -**Note:** you should properly handle possible errors in loading data from file +_Note:_ you should properly handle possible errors in loading data from file and decide yourself if you need any `Data.ReadingOptions`. However, it is unlikely that you will encounter deflated data outside of any archive. diff --git a/SWCompression.podspec b/SWCompression.podspec index c4f1b93e..175cd944 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SWCompression" - s.version = "1.1.2" + s.version = "1.2.0" s.summary = "Framework with implementations in Swift of different (de)compression algorithms" s.description = <<-DESC @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.subspec 'Common' do |sp| sp.public_header_files = 'Sources/Service/SWCompression.h' - sp.source_files = "Sources/{DataWithPointer.swift,HuffmanTable.swift,HuffmanLength.swift,Protocols.swift,Extensions.swift,Service/*.swift,Service/*.h}" + sp.source_files = "Sources/{DataWithPointer.swift,HuffmanTree.swift,HuffmanLength.swift,Protocols.swift,Extensions.swift,Service/*.swift,Service/*.h}" end s.subspec 'Deflate' do |sp| diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 46acd8d1..65ca7ebf 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -17,16 +17,16 @@ 063DF1101DE1F07800F38082 /* HuffmanLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1071DE1F07800F38082 /* HuffmanLength.swift */; }; 063DF1111DE1F07800F38082 /* HuffmanLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1071DE1F07800F38082 /* HuffmanLength.swift */; }; 063DF1121DE1F07800F38082 /* HuffmanLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1071DE1F07800F38082 /* HuffmanLength.swift */; }; - 063DF1131DE1F07800F38082 /* HuffmanTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTable.swift */; }; - 063DF1141DE1F07800F38082 /* HuffmanTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTable.swift */; }; - 063DF1151DE1F07800F38082 /* HuffmanTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTable.swift */; }; + 063DF1131DE1F07800F38082 /* HuffmanTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTree.swift */; }; + 063DF1141DE1F07800F38082 /* HuffmanTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTree.swift */; }; + 063DF1151DE1F07800F38082 /* HuffmanTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTree.swift */; }; 063DF1161DE1F07800F38082 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1091DE1F07800F38082 /* Protocols.swift */; }; 063DF1171DE1F07800F38082 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1091DE1F07800F38082 /* Protocols.swift */; }; 063DF1181DE1F07800F38082 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1091DE1F07800F38082 /* Protocols.swift */; }; 063DF1191DE1F2E200F38082 /* DataWithPointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1051DE1F07800F38082 /* DataWithPointer.swift */; }; 063DF11A1DE1F2E200F38082 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1061DE1F07800F38082 /* Extensions.swift */; }; 063DF11B1DE1F2E200F38082 /* HuffmanLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1071DE1F07800F38082 /* HuffmanLength.swift */; }; - 063DF11C1DE1F2E200F38082 /* HuffmanTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTable.swift */; }; + 063DF11C1DE1F2E200F38082 /* HuffmanTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1081DE1F07800F38082 /* HuffmanTree.swift */; }; 063DF11D1DE1F2E200F38082 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063DF1091DE1F07800F38082 /* Protocols.swift */; }; 065569681DC65C2A00A47E66 /* GzipArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063364E21DC52979007E313F /* GzipArchive.swift */; }; 065569691DC65C2A00A47E66 /* ZlibArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064492581DC606D400F10981 /* ZlibArchive.swift */; }; @@ -101,7 +101,7 @@ 063DF1051DE1F07800F38082 /* DataWithPointer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DataWithPointer.swift; path = Sources/DataWithPointer.swift; sourceTree = SOURCE_ROOT; }; 063DF1061DE1F07800F38082 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Extensions.swift; path = Sources/Extensions.swift; sourceTree = SOURCE_ROOT; }; 063DF1071DE1F07800F38082 /* HuffmanLength.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HuffmanLength.swift; path = Sources/HuffmanLength.swift; sourceTree = SOURCE_ROOT; }; - 063DF1081DE1F07800F38082 /* HuffmanTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HuffmanTable.swift; path = Sources/HuffmanTable.swift; sourceTree = SOURCE_ROOT; }; + 063DF1081DE1F07800F38082 /* HuffmanTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HuffmanTree.swift; path = Sources/HuffmanTree.swift; sourceTree = SOURCE_ROOT; }; 063DF1091DE1F07800F38082 /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Protocols.swift; path = Sources/Protocols.swift; sourceTree = SOURCE_ROOT; }; 064492581DC606D400F10981 /* ZlibArchive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZlibArchive.swift; sourceTree = ""; }; 0655693F1DC659FF00A47E66 /* SWCompression.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWCompression.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -197,7 +197,7 @@ 063DF1051DE1F07800F38082 /* DataWithPointer.swift */, 063DF1061DE1F07800F38082 /* Extensions.swift */, 063DF1071DE1F07800F38082 /* HuffmanLength.swift */, - 063DF1081DE1F07800F38082 /* HuffmanTable.swift */, + 063DF1081DE1F07800F38082 /* HuffmanTree.swift */, 063DF1091DE1F07800F38082 /* Protocols.swift */, ); path = Common; @@ -592,7 +592,7 @@ files = ( 065569691DC65C2A00A47E66 /* ZlibArchive.swift in Sources */, 063DF10E1DE1F07800F38082 /* Extensions.swift in Sources */, - 063DF1141DE1F07800F38082 /* HuffmanTable.swift in Sources */, + 063DF1141DE1F07800F38082 /* HuffmanTree.swift in Sources */, 063DF1171DE1F07800F38082 /* Protocols.swift in Sources */, 0655696A1DC65C2A00A47E66 /* Deflate.swift in Sources */, 06FED40D1DD7717E0013DFB2 /* BZip2.swift in Sources */, @@ -609,7 +609,7 @@ 063DF1191DE1F2E200F38082 /* DataWithPointer.swift in Sources */, 063DF11A1DE1F2E200F38082 /* Extensions.swift in Sources */, 063DF11B1DE1F2E200F38082 /* HuffmanLength.swift in Sources */, - 063DF11C1DE1F2E200F38082 /* HuffmanTable.swift in Sources */, + 063DF11C1DE1F2E200F38082 /* HuffmanTree.swift in Sources */, 063DF11D1DE1F2E200F38082 /* Protocols.swift in Sources */, 065569701DC65C2B00A47E66 /* ZlibArchive.swift in Sources */, 065569711DC65C2B00A47E66 /* Deflate.swift in Sources */, @@ -624,7 +624,7 @@ files = ( 065569771DC65C2B00A47E66 /* ZlibArchive.swift in Sources */, 063DF10F1DE1F07800F38082 /* Extensions.swift in Sources */, - 063DF1151DE1F07800F38082 /* HuffmanTable.swift in Sources */, + 063DF1151DE1F07800F38082 /* HuffmanTree.swift in Sources */, 063DF1181DE1F07800F38082 /* Protocols.swift in Sources */, 065569781DC65C2B00A47E66 /* Deflate.swift in Sources */, 06FED40F1DD7717E0013DFB2 /* BZip2.swift in Sources */, @@ -640,7 +640,7 @@ files = ( 06A393391DE0709300182E12 /* GzipArchive.swift in Sources */, 063DF10D1DE1F07800F38082 /* Extensions.swift in Sources */, - 063DF1131DE1F07800F38082 /* HuffmanTable.swift in Sources */, + 063DF1131DE1F07800F38082 /* HuffmanTree.swift in Sources */, 063DF1161DE1F07800F38082 /* Protocols.swift in Sources */, 06A3933A1DE0709300182E12 /* ZlibArchive.swift in Sources */, 06A3933B1DE0709300182E12 /* Deflate.swift in Sources */, diff --git a/Sources/BZip2.swift b/Sources/BZip2.swift index 9f6c16d9..c3126103 100644 --- a/Sources/BZip2.swift +++ b/Sources/BZip2.swift @@ -149,8 +149,8 @@ public class BZip2: DecompressionAlgorithm { let selectorsList = try computeSelectorsList() let symbolsInUse = used.filter { $0 }.count + 2 - func computeTables() throws -> [HuffmanTable] { - var tables: [HuffmanTable] = [] + func computeTables() throws -> [HuffmanTree] { + var tables: [HuffmanTree] = [] for _ in 0..= 0 && symbol <= 15 { @@ -147,18 +148,17 @@ public class Deflate: DecompressionAlgorithm { } // We have read codeLengths for both tables at once. // Now we need to split them and make corresponding tables. - mainLiterals = HuffmanTable(lengthsToOrder: Array(codeLengths[0..= 0 && nextSymbol <= 255 { // It is a literal symbol so we add it straight to the output data. @@ -173,21 +173,20 @@ public class Deflate: DecompressionAlgorithm { let extraLength = (257 <= nextSymbol && nextSymbol <= 260) || nextSymbol == 285 ? 0 : (((nextSymbol - 257) >> 2) - 1) // Actually, nextSymbol is not a starting value of length but an index for special array of starting values. - let length = HuffmanTable.Constants.lengthBase[nextSymbol - 257] + + let length = HuffmanTree.Constants.lengthBase[nextSymbol - 257] + pointerData.intFromBits(count: extraLength) // Then we need to get distance code. - guard let distanceLength = mainDistances.findNextSymbol(in: pointerData) else { + guard let distanceCode = mainDistances.findNextSymbol(in: pointerData) else { throw DeflateError.HuffmanTableError } - let distanceCode = distanceLength.code if distanceCode >= 0 && distanceCode <= 29 { // Again, depending on the distanceCode's value there might be additional bits in data, // which we need to combine with distanceCode to get the actual distance. let extraDistance = distanceCode == 0 || distanceCode == 1 ? 0 : ((distanceCode >> 1) - 1) // And yes, distanceCode is not a first part of distance but rather an index for special array. - let distance = HuffmanTable.Constants.distanceBase[distanceCode] + + let distance = HuffmanTree.Constants.distanceBase[distanceCode] + pointerData.intFromBits(count: extraDistance) // We should repeat last 'distance' amount of data. @@ -215,7 +214,7 @@ public class Deflate: DecompressionAlgorithm { throw DeflateError.UnknownBlockType } - // End the cycle if it was the last block + // End the cycle if it was the last block. if isLastBit == 1 { break } } diff --git a/Sources/HuffmanLength.swift b/Sources/HuffmanLength.swift index a82c9356..13f578cb 100644 --- a/Sources/HuffmanLength.swift +++ b/Sources/HuffmanLength.swift @@ -11,10 +11,9 @@ import Foundation struct HuffmanLength: Comparable, CustomStringConvertible { let code: Int let bits: Int - var symbol: Int? = nil var description: String { - return "(code: \(code), bits: \(bits), symbol: \(symbol)" + return "(code: \(code), bits: \(bits)" } static func < (left: HuffmanLength, right: HuffmanLength) -> Bool { diff --git a/Sources/HuffmanTable.swift b/Sources/HuffmanTree.swift similarity index 80% rename from Sources/HuffmanTable.swift rename to Sources/HuffmanTree.swift index d7cc1ea0..56366a1a 100644 --- a/Sources/HuffmanTable.swift +++ b/Sources/HuffmanTree.swift @@ -1,5 +1,5 @@ // -// HuffmanTable.swift +// HuffmanTree.swift // SWCompression // // Created by Timofey Solomko on 24.10.16. @@ -8,7 +8,7 @@ import Foundation -class HuffmanTable: CustomStringConvertible { +class HuffmanTree: CustomStringConvertible { struct Constants { static let codeLengthOrders: [Int] = @@ -23,18 +23,17 @@ class HuffmanTable: CustomStringConvertible { [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577] - } - var tree: [HuffmanLength?] - let leafCount: Int - var description: String { return self.tree.reduce("HuffmanTree:\n") { $0.appending("\($1)\n") } } - init(bootstrap: [Array]) { - // Fills the 'lengths' array with numerous HuffmanLengths from a 'bootstrap' + private var tree: [Int?] + private let leafCount: Int + + init(bootstrap: [[Int]]) { + // Fills the 'lengths' array with numerous HuffmanLengths from a 'bootstrap'. var lengths: [HuffmanLength] = [] var start = bootstrap[0][0] var bits = bootstrap[0][1] @@ -43,16 +42,16 @@ class HuffmanTable: CustomStringConvertible { let endbits = pair[1] if bits > 0 { lengths.append(contentsOf: - (start.. Int { - // Auxiliarly function, which generates reversed order of bits in a number + // Auxiliarly function, which generates reversed order of bits in a number. var a = 1 << 0 var b = 1 << (bits - 1) var z = 0 @@ -65,36 +64,32 @@ class HuffmanTable: CustomStringConvertible { return z } - // Calculates symbols for each length in 'lengths' array + // Calculate maximum amount of leaves possible in a tree. + self.leafCount = Int(pow(Double(2), Double(lengths.last!.bits + 1))) + // Create a tree (array, actually) with all leaves equal nil. + self.tree = Array(repeating: nil, count: leafCount) + + // Calculates symbols for each length in 'lengths' array and put them in the tree. var loopBits = -1 var symbol = -1 - for index in 0..>= 1 + treeCode >>= 1 } - self.tree[index] = length + self.tree[index] = length.code } } @@ -106,14 +101,14 @@ class HuffmanTable: CustomStringConvertible { self.init(bootstrap: (zip(range, addedLengths)).map { [$0, $1] }) } - func findNextSymbol(in pointerData: DataWithPointer) -> HuffmanLength? { + func findNextSymbol(in pointerData: DataWithPointer) -> Int? { var index = 0 while true { let bit = pointerData.bit() index = bit == 0 ? 2 * index + 1 : 2 * index + 2 guard index < self.leafCount else { return nil } - if let length = self.tree[index] { - return length + if let code = self.tree[index] { + return code } } } diff --git a/Sources/Service/Info-iOS.plist b/Sources/Service/Info-iOS.plist index c5ddeda1..d7fb9549 100644 --- a/Sources/Service/Info-iOS.plist +++ b/Sources/Service/Info-iOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 1.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Sources/Service/Info-tvOS.plist b/Sources/Service/Info-tvOS.plist index c5ddeda1..d7fb9549 100644 --- a/Sources/Service/Info-tvOS.plist +++ b/Sources/Service/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 1.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Sources/Service/Info-watchOS.plist b/Sources/Service/Info-watchOS.plist index c5ddeda1..d7fb9549 100644 --- a/Sources/Service/Info-watchOS.plist +++ b/Sources/Service/Info-watchOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 1.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Sources/Service/Info.plist b/Sources/Service/Info.plist index ac1638f3..0c1e9b6f 100644 --- a/Sources/Service/Info.plist +++ b/Sources/Service/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.2 + 1.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Sources/ZlibArchive.swift b/Sources/ZlibArchive.swift index 6ff1f471..9190a5ea 100644 --- a/Sources/ZlibArchive.swift +++ b/Sources/ZlibArchive.swift @@ -44,7 +44,7 @@ public class ZlibArchive: Archive { let windowSize: Int let compressionLevel: CompressionLevel - public static func ==(lhs: ServiceInfo, rhs: ServiceInfo) -> Bool { + public static func == (lhs: ServiceInfo, rhs: ServiceInfo) -> Bool { return lhs.compressionMethod == rhs.compressionMethod && lhs.windowSize == rhs.windowSize && lhs.compressionLevel == rhs.compressionLevel diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift index 4b521a3e..a10478b7 100644 --- a/Tests/DeflateTests.swift +++ b/Tests/DeflateTests.swift @@ -73,7 +73,6 @@ class DeflateTests: XCTestCase { XCTAssertEqual(decompressedData, answerData, "Decompression was incorrect") } - func testDeflate4() { let testName = "test4" guard let testData = try? Data(contentsOf: Constants.url(forTest: testName, withType: DeflateTests.testType)) else { @@ -94,7 +93,6 @@ class DeflateTests: XCTestCase { XCTAssertEqual(decompressedData, answerData, "Decompression was incorrect") } - func testDeflate5() { let testName = "test5" guard let testData = try? Data(contentsOf: Constants.url(forTest: testName, withType: DeflateTests.testType)) else { @@ -115,7 +113,6 @@ class DeflateTests: XCTestCase { XCTAssertEqual(decompressedData, answerData, "Decompression was incorrect") } - func testDeflate6() { let testName = "test6" guard let testData = try? Data(contentsOf: Constants.url(forTest: testName, withType: DeflateTests.testType)) else { @@ -136,7 +133,6 @@ class DeflateTests: XCTestCase { XCTAssertEqual(decompressedData, answerData, "Decompression was incorrect") } - func testDeflate7() { let testName = "test7" guard let testData = try? Data(contentsOf: Constants.url(forTest: testName, withType: DeflateTests.testType)) else { diff --git a/Tests/GzipTests.swift b/Tests/GzipTests.swift index f5d68cc9..13806ab8 100644 --- a/Tests/GzipTests.swift +++ b/Tests/GzipTests.swift @@ -113,7 +113,6 @@ class GzipTests: XCTestCase { XCTAssertEqual(testServiceInfo, answerServiceInfo, "Incorrect service info") } - func testGzip5() { let testName = "test5" guard let testData = try? Data(contentsOf: Constants.url(forTest: testName, withType: GzipTests.testType)) else {