From 85df8a757b09f6c7a55b4ad129912e03819cf919 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 14:11:07 -0500 Subject: [PATCH 01/22] stuff - remove minifiers - move compressors to global scope instead of behind `CompressionTechnique` - fix Swift 6.0 error - move compression & decompression to their own files - remove `DecompressionError.unsupportedOperation` parameter - remove `@inlinable` annotations - support noncopyable types in protocols --- Package.swift | 34 -- Sources/CSS/CSS.swift | 120 ------- Sources/DNA/DNABinaryEncoding.swift | 104 ------ .../binary/DNABinaryEncoding+Compress.swift | 31 ++ .../binary/DNABinaryEncoding+Decompress.swift | 26 ++ Sources/DNA/binary/DNABinaryEncoding.swift | 45 +++ .../DNASingleBlockEncoding+Compress.swift} | 40 +-- .../DNASingleBlockEncoding+Decompress.swift | 15 + .../singleBlock/DNASingleBlockEncoding.swift | 22 ++ Sources/JavaScript/JavaScript.swift | 176 ---------- Sources/LZ/Brotli.swift | 58 --- Sources/LZ/LZ77.swift | 168 --------- Sources/LZ/brotli/Brotli+Compress.swift | 11 + Sources/LZ/brotli/Brotli+Decompress.swift | 14 + Sources/LZ/brotli/Brotli.swift | 33 ++ Sources/LZ/deflate/DEFLATE+Compress.swift | 29 ++ Sources/LZ/deflate/DEFLATE+Decompress.swift | 10 + Sources/LZ/{ => deflate}/DEFLATE.swift | 61 +--- Sources/LZ/lz77/LZ77+Compress.swift | 56 +++ Sources/LZ/lz77/LZ77+Decompress.swift | 58 +++ Sources/LZ/lz77/LZ77.swift | 48 +++ Sources/Snappy/IWA.swift | 2 - Sources/Snappy/Snappy+Compress.swift | 96 +++++ Sources/Snappy/Snappy+Decompress.swift | 202 +++++++++++ Sources/Snappy/Snappy.swift | 331 +----------------- Sources/Snappy/SnappyFramed.swift | 46 --- .../Snappy/framed/SnappyFramed+Compress.swift | 11 + .../framed/SnappyFramed+Decompress.swift | 14 + Sources/Snappy/framed/SnappyFramed.swift | 21 ++ .../SwiftCompression/SwiftCompression.swift | 20 +- .../SwiftCompression/techniques/JSON.swift | 87 ----- .../techniques/MoveToFront.swift | 14 +- .../techniques/RunLengthEncoding.swift | 123 ------- .../programmingLanguage/SwiftLang.swift | 125 ------- .../RunLengthEncoding+Compress.swift | 57 +++ .../RunLengthEncoding+Decompress.swift | 27 ++ .../runLength/RunLengthEncoding.swift | 37 ++ .../AnyCompressor.swift | 2 +- .../AnyDecompressor.swift | 2 +- .../CompressionAlgorithm.swift | 87 +++-- .../CompressionTechnique.swift | 37 +- .../Compressor.swift | 6 +- .../Decompressor.swift | 2 +- .../ProgrammingLanguage.swift | 7 - .../errors/DecompressionError.swift | 2 +- Tests/CSSTests/CSSTests.swift | 96 ----- .../DNASingleBlockEncodingTestsTests.swift | 4 +- Tests/JavaScriptTests/JavaScriptTests.swift | 217 ------------ Tests/LZTests/LZ77Tests.swift | 2 +- .../RunLengthEncodingTests.swift | 20 +- .../SwiftCompressionTests.swift | 10 - .../programmingLanguage/SwiftLangTests.swift | 34 -- 52 files changed, 1001 insertions(+), 1899 deletions(-) delete mode 100644 Sources/CSS/CSS.swift delete mode 100644 Sources/DNA/DNABinaryEncoding.swift create mode 100644 Sources/DNA/binary/DNABinaryEncoding+Compress.swift create mode 100644 Sources/DNA/binary/DNABinaryEncoding+Decompress.swift create mode 100644 Sources/DNA/binary/DNABinaryEncoding.swift rename Sources/DNA/{DNASingleBlockEncoding.swift => singleBlock/DNASingleBlockEncoding+Compress.swift} (80%) create mode 100644 Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift create mode 100644 Sources/DNA/singleBlock/DNASingleBlockEncoding.swift delete mode 100644 Sources/JavaScript/JavaScript.swift delete mode 100644 Sources/LZ/Brotli.swift delete mode 100644 Sources/LZ/LZ77.swift create mode 100644 Sources/LZ/brotli/Brotli+Compress.swift create mode 100644 Sources/LZ/brotli/Brotli+Decompress.swift create mode 100644 Sources/LZ/brotli/Brotli.swift create mode 100644 Sources/LZ/deflate/DEFLATE+Compress.swift create mode 100644 Sources/LZ/deflate/DEFLATE+Decompress.swift rename Sources/LZ/{ => deflate}/DEFLATE.swift (62%) create mode 100644 Sources/LZ/lz77/LZ77+Compress.swift create mode 100644 Sources/LZ/lz77/LZ77+Decompress.swift create mode 100644 Sources/LZ/lz77/LZ77.swift create mode 100644 Sources/Snappy/Snappy+Compress.swift create mode 100644 Sources/Snappy/Snappy+Decompress.swift delete mode 100644 Sources/Snappy/SnappyFramed.swift create mode 100644 Sources/Snappy/framed/SnappyFramed+Compress.swift create mode 100644 Sources/Snappy/framed/SnappyFramed+Decompress.swift create mode 100644 Sources/Snappy/framed/SnappyFramed.swift delete mode 100644 Sources/SwiftCompression/techniques/JSON.swift delete mode 100644 Sources/SwiftCompression/techniques/RunLengthEncoding.swift delete mode 100644 Sources/SwiftCompression/techniques/programmingLanguage/SwiftLang.swift create mode 100644 Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift create mode 100644 Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift create mode 100644 Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift delete mode 100644 Sources/SwiftCompressionUtilities/ProgrammingLanguage.swift delete mode 100644 Tests/CSSTests/CSSTests.swift delete mode 100644 Tests/JavaScriptTests/JavaScriptTests.swift delete mode 100644 Tests/SwiftCompressionTests/SwiftCompressionTests.swift delete mode 100644 Tests/SwiftCompressionTests/programmingLanguage/SwiftLangTests.swift diff --git a/Package.swift b/Package.swift index a1c6c16..fd50978 100644 --- a/Package.swift +++ b/Package.swift @@ -11,21 +11,11 @@ let package = Package( targets: ["SwiftCompression"] ), - .library( - name: "SwiftCompressionCSS", - targets: ["CompressionCSS"] - ), - .library( name: "SwiftCompressionDNA", targets: ["CompressionDNA"] ), - .library( - name: "SwiftCompressionJavaScript", - targets: ["CompressionJavaScript"] - ), - .library( name: "SwiftCompressionLZ", targets: ["CompressionLZ"] @@ -46,22 +36,13 @@ let package = Package( name: "SwiftCompression", dependencies: [ "SwiftCompressionUtilities", - "CompressionCSS", "CompressionDNA", - "CompressionJavaScript", "CompressionLZ", "CompressionSnappy" ] ), // MARK: Techniques - .target( - name: "CompressionCSS", - dependencies: [ - "SwiftCompressionUtilities" - ], - path: "Sources/CSS" - ), .target( name: "CompressionDNA", dependencies: [ @@ -69,13 +50,6 @@ let package = Package( ], path: "Sources/DNA" ), - .target( - name: "CompressionJavaScript", - dependencies: [ - "SwiftCompressionUtilities" - ], - path: "Sources/JavaScript" - ), .target( name: "CompressionLZ", dependencies: [ @@ -104,18 +78,10 @@ let package = Package( name: "SwiftCompressionTests", dependencies: ["SwiftCompression"] ), - .testTarget( - name: "CSSTests", - dependencies: ["CompressionCSS"] - ), .testTarget( name: "DNATests", dependencies: ["CompressionDNA"] ), - .testTarget( - name: "JavaScriptTests", - dependencies: ["CompressionJavaScript"] - ), .testTarget( name: "LZTests", dependencies: ["CompressionLZ"] diff --git a/Sources/CSS/CSS.swift b/Sources/CSS/CSS.swift deleted file mode 100644 index 3bc0080..0000000 --- a/Sources/CSS/CSS.swift +++ /dev/null @@ -1,120 +0,0 @@ - -import SwiftCompressionUtilities - -extension CompressionTechnique { - - /// CSS compression techniques. - public static let css = CSS() - - public struct CSS: Compressor { - @inlinable - public var algorithm: CompressionAlgorithm { - .programmingLanguage(.css) - } - - @inlinable - public var quality: CompressionQuality { - .lossy - } - } -} - -// MARK: Compress -extension CompressionTechnique.CSS { - public func compress(data: some Collection, reserveCapacity: Int) throws(CompressionError) -> CompressionResult<[UInt8]> { - throw CompressionError.unsupportedOperation // TODO: support? - } - - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - closure: Logic to execute for a run. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Sequence, closure: (UInt8) -> Void) -> UInt8? { - return nil // TODO: support? - } -} - -// MARK: Minify -extension CompressionTechnique.CSS { - /// Optimizes CSS code to make it suitable for production-only usage, which results in the minimum binary size required to represent the same code. - /// - /// Optionally removes comments and unnecessary whitespace - public func minify(data: some Collection, reserveCapacity: Int) -> [UInt8] { - var index = 0 - let count = data.count - var i = 0 - var result = [UInt8](repeating: 0, count: count) - - let space:UInt8 = 32 - let pound:UInt8 = 35 - let lineFeed:UInt8 = 10 - let carriageReturn:UInt8 = 13 - let asterisk:UInt8 = 42 - let horizontalTab:UInt8 = 9 - let period:UInt8 = 46 - let forwardSlash:UInt8 = 47 - let a:UInt8 = 97 - let c:UInt8 = 99 - let d:UInt8 = 100 - let l:UInt8 = 108 - let n:UInt8 = 110 - while data[i] == space || data[i] == horizontalTab || data[i] == lineFeed || data[i] == carriageReturn { - i += 1 - } - var calcDepth:UInt8 = 0 - while i < count { - let char = data[i] - switch char { - case space, - horizontalTab, - lineFeed, - carriageReturn: - if calcDepth > 0 { - assign(byte: char, to: &index, in: &result) - } else { - let next = data[i+1] - let iMinus1 = data[i-1] - if iMinus1.char.isLetter && (next == pound || next == period || next.char.isNumber || next.char.isLetter || iMinus1 == d && data[i-2] == n && data[i-3] == a) { - assign(byte: char, to: &index, in: &result) - } - } - case forwardSlash: - if data[i+1] == asterisk { - i += 2 - for j in i.. 0 || data[i-1] == c && data[i-2] == l && data[i-3] == a && data[i-4] == c { // in calc or calc( was found - calcDepth += 1 - } - assign(byte: char, to: &index, in: &result) - case 41: // ) - if calcDepth > 0 { - calcDepth -= 1 - } - assign(byte: char, to: &index, in: &result) - default: - assign(byte: char, to: &index, in: &result) - } - i += 1 - } - return .init(result[0.. DNABinaryEncoding { - return DNABinaryEncoding(baseBits: baseBits) - } - - public struct DNABinaryEncoding: Compressor, Decompressor { - public let baseBits:[UInt8:[Bool]] - - public init(baseBits: [UInt8:[Bool]] = [ - 65: [false, false], // A - 67: [false, true], // C - 71: [true, false], // G - 84: [true, true] // T - ]) { - self.baseBits = baseBits - } - - @inlinable - public var algorithm: CompressionAlgorithm { - .dnaBinaryEncoding(baseBits: baseBits) - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - - public var baseBitsReversed: [[Bool]:UInt8] { - var reversed = [[Bool]:UInt8]() - reversed.reserveCapacity(baseBits.count) - for (byte, bits) in baseBits { - reversed[bits] = byte - } - return reversed - } - } -} - -// MARK: Compress -extension CompressionTechnique.DNABinaryEncoding { - /// Compress a collection of bytes using the DNA binary encoding technique. - /// - /// - Parameters: - /// - data: Collection of bytes to compress. - /// - baseBits: Bit codes for the unique base nucleotides. - /// - closure: Logic to execute when a byte was encoded. - /// - Returns: Valid bits for the last byte, if necessary. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress( - data: some Collection, - closure: (UInt8) -> Void - ) -> UInt8? { - var bitWriter = ByteBuilder() - for base in data { - if let bits = baseBits[base] { - for bit in bits { - if let wrote = bitWriter.write(bit: bit) { - closure(wrote) - } - } - } - } - guard let (byte, validBits) = bitWriter.flush() else { return nil } - closure(byte) - return validBits - } -} - -// MARK: Decompress -extension CompressionTechnique.DNABinaryEncoding { - /// Decompress a collection of bytes using the DNA binary encoding technique. - /// - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a given base nucleotide is found. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - let reversed = baseBitsReversed - for byte in data { - var bits = [Bool]() - bits.reserveCapacity(4) - for bit in byte.bits { - bits.append(bit) - if let base = reversed[bits] { - closure(base) - bits.removeAll(keepingCapacity: true) - } - } - } - } -} \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift new file mode 100644 index 0000000..8f962c1 --- /dev/null +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -0,0 +1,31 @@ + +import SwiftCompressionUtilities + +extension DNABinaryEncoding { + /// Compress a collection of bytes using the DNA binary encoding technique. + /// + /// - Parameters: + /// - data: Collection of bytes to compress. + /// - baseBits: Bit codes for the unique base nucleotides. + /// - closure: Logic to execute when a byte was encoded. + /// - Returns: Valid bits for the last byte, if necessary. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress( + data: some Collection, + closure: (UInt8) -> Void + ) -> UInt8? { + var bitWriter = ByteBuilder() + for base in data { + if let bits = baseBits[base] { + for bit in bits { + if let wrote = bitWriter.write(bit: bit) { + closure(wrote) + } + } + } + } + guard let (byte, validBits) = bitWriter.flush() else { return nil } + closure(byte) + return validBits + } +} \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift new file mode 100644 index 0000000..4ab0644 --- /dev/null +++ b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift @@ -0,0 +1,26 @@ + +extension DNABinaryEncoding { + /// Decompress a collection of bytes using the DNA binary encoding technique. + /// + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a given base nucleotide is found. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + closure: (UInt8) -> Void + ) { + let reversed = baseBitsReversed + for byte in data { + var bits = [Bool]() + bits.reserveCapacity(4) + for bit in byte.bits { + bits.append(bit) + if let base = reversed[bits] { + closure(base) + bits.removeAll(keepingCapacity: true) + } + } + } + } +} \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding.swift b/Sources/DNA/binary/DNABinaryEncoding.swift new file mode 100644 index 0000000..3f7dca7 --- /dev/null +++ b/Sources/DNA/binary/DNABinaryEncoding.swift @@ -0,0 +1,45 @@ + +import SwiftCompressionUtilities + +/// The DNA binary encoding compression technique. +public struct DNABinaryEncoding: Compressor, Decompressor { + public let baseBits:[UInt8:[Bool]] + + public init(baseBits: [UInt8:[Bool]] = [ + 65: [false, false], // A + 67: [false, true], // C + 71: [true, false], // G + 84: [true, true] // T + ]) { + self.baseBits = baseBits + } + + public var algorithm: CompressionAlgorithm { + .dnaBinaryEncoding(baseBits: baseBits) + } + + public var quality: CompressionQuality { + .lossless + } + + public var baseBitsReversed: [[Bool]:UInt8] { + var reversed = [[Bool]:UInt8]() + reversed.reserveCapacity(baseBits.count) + for (byte, bits) in baseBits { + reversed[bits] = byte + } + return reversed + } +} + +extension CompressionTechnique { + /// The DNA binary encoding compression technique. + public static func dnaBinaryEncoding(baseBits: [UInt8:[Bool]] = [ + 65: [false, false], // A + 67: [false, true], // C + 71: [true, false], // G + 84: [true, true] // T + ]) -> DNABinaryEncoding { + return DNABinaryEncoding(baseBits: baseBits) + } +} \ No newline at end of file diff --git a/Sources/DNA/DNASingleBlockEncoding.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift similarity index 80% rename from Sources/DNA/DNASingleBlockEncoding.swift rename to Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift index 9864056..b90f7ec 100644 --- a/Sources/DNA/DNASingleBlockEncoding.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift @@ -1,27 +1,7 @@ import SwiftCompressionUtilities -extension CompressionTechnique { - /// The DNA single block encoding compression technique. - /// - /// https://www.mdpi.com/1999-4893/13/4/99 - public static let dnaSingleBlockEncoding:DNASingleBlockEncoding = DNASingleBlockEncoding() - - public struct DNASingleBlockEncoding: Compressor, Decompressor { - @inlinable - public var algorithm: CompressionAlgorithm { - .dnaSingleBlockEncoding - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.DNASingleBlockEncoding { +extension DNASingleBlockEncoding { public func compress(data: some Collection, closure: (UInt8) -> Void) throws(CompressionError) -> UInt8? { // TODO: fix return nil } @@ -43,7 +23,7 @@ extension CompressionTechnique.DNASingleBlockEncoding { } } -extension CompressionTechnique.DNASingleBlockEncoding { +extension DNASingleBlockEncoding { /// Compress a sequence of bytes using phase one (compress data to binary) of the DNA single block encoding technique. /// /// - Parameters: @@ -75,7 +55,7 @@ extension CompressionTechnique.DNASingleBlockEncoding { } } -extension CompressionTechnique.DNASingleBlockEncoding { +extension DNASingleBlockEncoding { /// Compress a collection of bits using phase two (compress bits to bit blocks) of the DNA single block encoding technique. /// /// https://www.mdpi.com/algorithms/algorithms-13-00099/article_deploy/html/images/algorithms-13-00099-g002.png @@ -151,18 +131,4 @@ extension CompressionTechnique.DNASingleBlockEncoding { } return (compressed, controlBits) } -} - -// MARK: Decompress -extension CompressionTechnique.DNASingleBlockEncoding { // TODO: finish - public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { - } - - /// Decompress a sequence of bytes using the DNA single block encoding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to decompress. - public static func decompress(data: [UInt8]) -> [UInt8] { - return [] - } } \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift new file mode 100644 index 0000000..26b4ce6 --- /dev/null +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift @@ -0,0 +1,15 @@ + +import SwiftCompressionUtilities + +extension DNASingleBlockEncoding { // TODO: finish + public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { + } + + /// Decompress a sequence of bytes using the DNA single block encoding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to decompress. + public static func decompress(data: [UInt8]) -> [UInt8] { + return [] + } +} \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift new file mode 100644 index 0000000..62a0418 --- /dev/null +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift @@ -0,0 +1,22 @@ + +import SwiftCompressionUtilities + +/// The DNA single block encoding compression technique. +/// +/// https://www.mdpi.com/1999-4893/13/4/99 +public struct DNASingleBlockEncoding: Compressor, Decompressor { + public var algorithm: CompressionAlgorithm { + .dnaSingleBlockEncoding + } + + public var quality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The DNA single block encoding compression technique. + /// + /// https://www.mdpi.com/1999-4893/13/4/99 + public static let dnaSingleBlockEncoding = DNASingleBlockEncoding() +} \ No newline at end of file diff --git a/Sources/JavaScript/JavaScript.swift b/Sources/JavaScript/JavaScript.swift deleted file mode 100644 index 0bf7400..0000000 --- a/Sources/JavaScript/JavaScript.swift +++ /dev/null @@ -1,176 +0,0 @@ - -import SwiftCompressionUtilities - -extension CompressionTechnique { - - /// JavaScript compression techniques. - public static let javascript:JavaScript = JavaScript() - - public struct JavaScript: Compressor { - @inlinable - public var algorithm: CompressionAlgorithm { - .programmingLanguage(.javascript) - } - - @inlinable - public var quality: CompressionQuality { - .lossy - } - } -} - -// MARK: Compress -extension CompressionTechnique.JavaScript { - public func compress(data: some Collection, reserveCapacity: Int) throws(CompressionError) -> CompressionResult<[UInt8]> { - throw CompressionError.unsupportedOperation // TODO: support? - } - - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - closure: Logic to execute for a run. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Sequence, closure: (UInt8) -> Void) -> UInt8? { - return nil // TODO: support? - } -} - -// MARK: Minify -extension CompressionTechnique.JavaScript { - /// - Complexity: O(_n_) where _n_ is the length of the collection. - public func minify(data: some Collection) -> [UInt8] { // TODO: optimize? - var index = 0 - var keep = true - let count = data.count - var i = 0 - var result:[UInt8] = .init(repeating: 0, count: count) - - let space:UInt8 = 32 - let lineFeed:UInt8 = 10 - let carriageReturn:UInt8 = 13 - let asterisk:UInt8 = 42 - let horizontalTab:UInt8 = 9 - let forwardSlash:UInt8 = 47 - let a:UInt8 = 97 - let c:UInt8 = 99 - let e:UInt8 = 101 - let f:UInt8 = 102 - let iChar:UInt8 = 105 - let l:UInt8 = 108 - let n:UInt8 = 110 - let o:UInt8 = 111 - let p:UInt8 = 112 - let r:UInt8 = 114 - let s:UInt8 = 115 - let t:UInt8 = 116 - let u:UInt8 = 117 - let v:UInt8 = 118 - let w:UInt8 = 119 - let y:UInt8 = 121 - var char = data[i] - while char == space || char == horizontalTab || char == lineFeed || char == carriageReturn { - i += 1 - char = data[i] - } - while i < count { - char = data[i] - switch char { - case space, - horizontalTab, - lineFeed, - carriageReturn: - let iPlus1 = data.getPositive(i+1) - let iPlus2 = data.getPositive(i+2) - let iPlus3 = data.getPositive(i+3) - keep = iPlus1 == o && iPlus2 == f && iPlus3 == space // of - || iPlus1 == iChar && iPlus2 == n && iPlus3 == s && data.getPositive(i+4) == t && data.getPositive(i+5) == a && data.getPositive(i+6) == n && data.getPositive(i+7) == c && data.getPositive(i+8) == e && data.getPositive(i+9) == o && data.getPositive(i+10) == f && data.getPositive(i+11) == space // instanceof - if !keep { - switch data.get(i-1) { - case r: // var - keep = data.get(i-2) == a && data.get(i-3) == v - case t: // let, const - let iMinus2 = data.get(i-2) - let iMinus3 = data.get(i-3) - keep = (iMinus2 == e && iMinus3 == l) || (iMinus2 == s && iMinus3 == n && data.get(i-4) == o && data.get(i-5) == c) - case n: // function, return - let iMinus2 = data.get(i-2) - let iMinus3 = data.get(i-3) - let iMinus4 = data.get(i-4) - let iMinus5 = data.get(i-5) - let iMinus6 = data.get(i-6) - keep = (iMinus2 == o && iMinus3 == iChar && iMinus4 == t && iMinus5 == c && iMinus6 == n && data.get(i-7) == u && data.get(i-8) == f) - || (iMinus2 == r && iMinus3 == u && iMinus4 == t && iMinus5 == e && iMinus6 == r) - case f: // of, typeof, instanceof - let iMinus3 = data.get(i-3) - let iMinus4 = data.get(i-4) - let iMinus5 = data.get(i-5) - let iMinus6 = data.get(i-6) - keep = data.get(i-2) == o && (iMinus3 == space - || iMinus3 == e && iMinus4 == p && iMinus5 == y && iMinus6 == t - || iMinus3 == e && iMinus4 == c && iMinus5 == n && iMinus6 == a && data.get(i-7) == t && data.get(i-8) == s && data.get(i-9) == n && data.get(i-10) == iChar - ) - case w: // new - keep = data.get(i-2) == e && data.get(i-3) == n - case e: // else if, case 0-9 - let iMinus2 = data.get(i-2) - let iMinus3 = data.get(i-3) - let iMinus4 = data.get(i-4) - keep = (iMinus2 == s && iMinus3 == l && iMinus4 == e) && iPlus1 == iChar && iPlus2 == f - || (iMinus2 == s && iMinus3 == a && iMinus4 == c && (iPlus1 != nil ? iPlus1! >= 48 && iPlus1! <= 57 : false)) // 48 = "0"; 57 = "9" - default: - break - } - } - case forwardSlash: - let iPlus1 = data.getPositive(i+1), isMultiline:Bool = iPlus1 == asterisk - if iPlus1 == forwardSlash || isMultiline { - let endFunction:(UInt8?, Int) -> Bool, increment:Int - if isMultiline { - endFunction = { char, index in - return char == asterisk && data.getPositive(index+1) == forwardSlash - } - increment = 1 - } else { - endFunction = { char, _ in - return char == horizontalTab || char == lineFeed || char == carriageReturn - } - increment = 0 - } - i += 2 - loop: for j in i.. Brotli { - return Brotli(windowSize: windowSize) - } - - public struct Brotli: Compressor, Decompressor { // TODO: finish - /// Size of the window. - public let windowSize:Int - - /// Predefined dictionary to use. - //public let dictionary:[String:String] - - public init(windowSize: Int = 32768) { - self.windowSize = windowSize - } - - @inlinable - public var algorithm: CompressionAlgorithm { - .brotli(windowSize: windowSize) - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.Brotli { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - return nil - } -} - -// MARK: Decompress -extension CompressionTechnique.Brotli { // TODO: finish - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - } -} \ No newline at end of file diff --git a/Sources/LZ/LZ77.swift b/Sources/LZ/LZ77.swift deleted file mode 100644 index d935efa..0000000 --- a/Sources/LZ/LZ77.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// LZ77.swift -// -// -// Created by Evan Anderson on 12/12/24. -// - -import SwiftCompressionUtilities - -extension CompressionTechnique { - /// The LZ77 compression technique. - /// - /// - Parameters: - /// - T: Integer type the offset is encoded as. Default is `UInt16`. - /// - windowSize: Size of the sliding window, measured in bytes. - /// - bufferSize: Size of the buffer, measured in bytes. - /// - /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 - public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { - return LZ77(windowSize: windowSize, bufferSize: bufferSize) - } - - /// The LZ77 compression technique where the offset is encoded as a `UInt16`. - /// - /// - Parameters: - /// - windowSize: Size of the sliding window, measured in bytes. - /// - bufferSize: Size of the buffer, measured in bytes. - /// - /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 - public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { - return LZ77(windowSize: windowSize, bufferSize: bufferSize) - } - - public struct LZ77: Compressor, Decompressor { - /// Size of the window. - public let windowSize:Int - - /// Size of the buffer. - public let bufferSize:Int - - public init(windowSize: Int, bufferSize: Int) { - self.windowSize = windowSize - self.bufferSize = bufferSize - } - - @inlinable - public var algorithm: CompressionAlgorithm { - .lz77(windowSize: windowSize, bufferSize: bufferSize, offsetBitWidth: T.bitWidth) - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.LZ77 { - /// Compress a collection of bytes using the LZ77 technique. - /// - /// - Parameters: - /// - data: Collection of bytes to compress. - /// - closure: Logic to execute when a byte is compressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress( - data: some Collection, - closure: (UInt8) -> Void - ) -> UInt8? { - let count = data.count - var index = 0 - while index < count { - let bufferEndIndex = min(index + bufferSize, count) - guard index < bufferEndIndex else { break } - let bufferCount = bufferEndIndex - index - let bufferRange = data.index(data.startIndex, offsetBy: index)..= windowCount { - break - } - } - if length > bestLength { - bestLength = length - offset = windowCount - i - } - } - let byte:UInt8 - if index + bestLength < count { - byte = data[index + bestLength] - } else { - byte = 0 - } - for byte in T(offset).reversedBytes { - closure(byte) - } - closure(UInt8(bestLength)) - closure(byte) - index += bestLength + 1 - } - return nil - } -} - -// MARK: Decompress -extension CompressionTechnique.LZ77 { - /// Decompress a collection of bytes using the LZ77 technique. - /// - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte was decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - let count = data.count - var history = [UInt8]() - var window = [UInt8]() - var index = 0 - let bytesForOffset = T.bitWidth / 8 - let byteIndexOffset = bytesForOffset + 1 - while index < count { - let length = Int(data[index + bytesForOffset]) - if length > 0 { - let offset = parseOffset(data: data, index: index) - let startIndex = window.count - Int(offset) - let endIndex = min(startIndex + length, window.count) - if startIndex < endIndex { - let bytes = window[startIndex.. windowSize { - window.removeFirst(window.count - windowSize) - } - index += bytesForOffset + 2 - } - } - - func parseOffset(data: some Collection, index: Int) -> T { - var byte = T() - var offsetIndex = index - for _ in 0...(T.bitWidth / 8)-1 { - byte <<= 8 - byte += T(data[offsetIndex]) - offsetIndex += 1 - } - return byte - } -} \ No newline at end of file diff --git a/Sources/LZ/brotli/Brotli+Compress.swift b/Sources/LZ/brotli/Brotli+Compress.swift new file mode 100644 index 0000000..992e74a --- /dev/null +++ b/Sources/LZ/brotli/Brotli+Compress.swift @@ -0,0 +1,11 @@ + +import SwiftCompressionUtilities + +extension Brotli { // TODO: finish + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { + return nil + } +} \ No newline at end of file diff --git a/Sources/LZ/brotli/Brotli+Decompress.swift b/Sources/LZ/brotli/Brotli+Decompress.swift new file mode 100644 index 0000000..c16de79 --- /dev/null +++ b/Sources/LZ/brotli/Brotli+Decompress.swift @@ -0,0 +1,14 @@ + +import SwiftCompressionUtilities + +extension Brotli { // TODO: finish + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte is decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + closure: (UInt8) -> Void + ) { + } +} \ No newline at end of file diff --git a/Sources/LZ/brotli/Brotli.swift b/Sources/LZ/brotli/Brotli.swift new file mode 100644 index 0000000..458fa84 --- /dev/null +++ b/Sources/LZ/brotli/Brotli.swift @@ -0,0 +1,33 @@ + +import SwiftCompressionUtilities + +public struct Brotli: Compressor, Decompressor { // TODO: finish + /// Size of the window. + public let windowSize:Int + + /// Predefined dictionary to use. + //public let dictionary:[String:String] + + public init(windowSize: Int = 32768) { + self.windowSize = windowSize + } + + public var algorithm: CompressionAlgorithm { + .brotli(windowSize: windowSize) + } + + public var quality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The Brotli compression technique. + /// + /// https://github.com/google/brotli + /// + /// https://en.wikipedia.org/wiki/Brotli + public static func brotli(windowSize: Int = 32768) -> Brotli { + return Brotli(windowSize: windowSize) + } +} \ No newline at end of file diff --git a/Sources/LZ/deflate/DEFLATE+Compress.swift b/Sources/LZ/deflate/DEFLATE+Compress.swift new file mode 100644 index 0000000..152caab --- /dev/null +++ b/Sources/LZ/deflate/DEFLATE+Compress.swift @@ -0,0 +1,29 @@ + +import SwiftCompressionUtilities + +extension Deflate { + public static func compress( + data: some Sequence, + flags: borrowing Flags = .init(flags: 0), + mtime: (UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0), + quality: borrowing Quality = .default, + os: borrowing FileSystem = .unknown + ) -> CompressionResult<[UInt8]>? { + var result = [UInt8]() + + // gzip header + result.append(contentsOf: [ + 0x1F, // ID1 + 0x8B, // ID2 + 0x08, // CM (compression method; deflate=8) + flags.bits, // FLG (flags) + mtime.0.littleEndian, // MTIME byte 1 (modification time) + mtime.1.littleEndian, // MTIME byte 2 (modification time) + mtime.2.littleEndian, // MTIME byte 3 (modification time) + mtime.3.littleEndian, // MTIME byte 4 (modification time) + quality.rawValue.littleEndian // XFL (extra flags) + ]) + // TODO: finish + return .init(data: result) + } +} \ No newline at end of file diff --git a/Sources/LZ/deflate/DEFLATE+Decompress.swift b/Sources/LZ/deflate/DEFLATE+Decompress.swift new file mode 100644 index 0000000..852c797 --- /dev/null +++ b/Sources/LZ/deflate/DEFLATE+Decompress.swift @@ -0,0 +1,10 @@ + +import SwiftCompressionUtilities + +extension Deflate { + public static func decompress( + data: [UInt8] + ) -> [UInt8] { + return data + } +} \ No newline at end of file diff --git a/Sources/LZ/DEFLATE.swift b/Sources/LZ/deflate/DEFLATE.swift similarity index 62% rename from Sources/LZ/DEFLATE.swift rename to Sources/LZ/deflate/DEFLATE.swift index 6aa2653..e7303cf 100644 --- a/Sources/LZ/DEFLATE.swift +++ b/Sources/LZ/deflate/DEFLATE.swift @@ -4,19 +4,17 @@ import SwiftCompressionUtilities // https://en.wikipedia.org/wiki/Gzip // https://www.rfc-editor.org/rfc/rfc1952#page-5 -extension CompressionTechnique { - /// The Deflate compression technique. - /// - /// https://en.wikipedia.org/wiki/Deflate - public enum Deflate { - struct Block: Sendable { - let head:Head - } +/// The Deflate compression technique. +/// +/// https://en.wikipedia.org/wiki/Deflate +public enum Deflate { + struct Block: Sendable { + let head:Head } } // MARK: Head -extension CompressionTechnique.Deflate { +extension Deflate { struct Head: Sendable { let bits:UInt8 @@ -36,7 +34,7 @@ extension CompressionTechnique.Deflate { } // MARK: Encoding -extension CompressionTechnique.Deflate { +extension Deflate { public enum Encoding: ~Copyable, Sendable { /// A stored (a.k.a. raw or literal) section, between 0 and 65,535 bytes in length case stored @@ -53,7 +51,7 @@ extension CompressionTechnique.Deflate { } // MARK: Flags -extension CompressionTechnique.Deflate { +extension Deflate { public struct Flags: ~Copyable, Sendable { let bits:UInt8 @@ -79,7 +77,7 @@ extension CompressionTechnique.Deflate { } // MARK: Quality -extension CompressionTechnique.Deflate { +extension Deflate { public enum Quality: ~Copyable, Sendable { case `default` @@ -100,7 +98,7 @@ extension CompressionTechnique.Deflate { } // MARK: FileSystem -extension CompressionTechnique.Deflate { +extension Deflate { public enum FileSystem: ~Copyable, Sendable { case fat case amiga @@ -138,41 +136,4 @@ extension CompressionTechnique.Deflate { } } } -} - -// MARK: Compress -extension CompressionTechnique.Deflate { - public static func compress( - data: some Sequence, - flags: borrowing Flags = .init(flags: 0), - mtime: (UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0), - quality: borrowing Quality = .default, - os: borrowing FileSystem = .unknown - ) -> CompressionResult<[UInt8]>? { - var result = [UInt8]() - - // gzip header - result.append(contentsOf: [ - 0x1F, // ID1 - 0x8B, // ID2 - 0x08, // CM (compression method; deflate=8) - flags.bits, // FLG (flags) - mtime.0.littleEndian, // MTIME byte 1 (modification time) - mtime.1.littleEndian, // MTIME byte 2 (modification time) - mtime.2.littleEndian, // MTIME byte 3 (modification time) - mtime.3.littleEndian, // MTIME byte 4 (modification time) - quality.rawValue.littleEndian // XFL (extra flags) - ]) - // TODO: finish - return .init(data: result) - } -} - -// MARK: Decompress -extension CompressionTechnique.Deflate { - public static func decompress( - data: [UInt8] - ) -> [UInt8] { - return data - } } \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift new file mode 100644 index 0000000..7842609 --- /dev/null +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -0,0 +1,56 @@ + +import SwiftCompressionUtilities + +extension LZ77 { + /// Compress a collection of bytes using the LZ77 technique. + /// + /// - Parameters: + /// - data: Collection of bytes to compress. + /// - closure: Logic to execute when a byte is compressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress( + data: some Collection, + closure: (UInt8) -> Void + ) -> UInt8? { + let count = data.count + var index = 0 + while index < count { + let bufferEndIndex = min(index + bufferSize, count) + guard index < bufferEndIndex else { break } + let bufferCount = bufferEndIndex - index + let bufferRange = data.index(data.startIndex, offsetBy: index)..= windowCount { + break + } + } + if length > bestLength { + bestLength = length + offset = windowCount - i + } + } + let byte:UInt8 + if index + bestLength < count { + byte = data[index + bestLength] + } else { + byte = 0 + } + for byte in T(offset).reversedBytes { + closure(byte) + } + closure(UInt8(bestLength)) + closure(byte) + index += bestLength + 1 + } + return nil + } +} \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77+Decompress.swift b/Sources/LZ/lz77/LZ77+Decompress.swift new file mode 100644 index 0000000..06a5a24 --- /dev/null +++ b/Sources/LZ/lz77/LZ77+Decompress.swift @@ -0,0 +1,58 @@ + +import SwiftCompressionUtilities + +extension LZ77 { + /// Decompress a collection of bytes using the LZ77 technique. + /// + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte was decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + closure: (UInt8) -> Void + ) { + let count = data.count + var history = [UInt8]() + var window = [UInt8]() + var index = 0 + let bytesForOffset = T.bitWidth / 8 + let byteIndexOffset = bytesForOffset + 1 + while index < count { + let length = Int(data[index + bytesForOffset]) + if length > 0 { + let offset = parseOffset(data: data, index: index) + let startIndex = window.count - Int(offset) + let endIndex = min(startIndex + length, window.count) + if startIndex < endIndex { + let bytes = window[startIndex.. windowSize { + window.removeFirst(window.count - windowSize) + } + index += bytesForOffset + 2 + } + } + + func parseOffset(data: some Collection, index: Int) -> T { + var byte = T() + var offsetIndex = index + for _ in 0...(T.bitWidth / 8)-1 { + byte <<= 8 + byte += T(data[offsetIndex]) + offsetIndex += 1 + } + return byte + } +} \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77.swift b/Sources/LZ/lz77/LZ77.swift new file mode 100644 index 0000000..02f9dbc --- /dev/null +++ b/Sources/LZ/lz77/LZ77.swift @@ -0,0 +1,48 @@ + +import SwiftCompressionUtilities + +public struct LZ77: Compressor, Decompressor { + /// Size of the window. + public let windowSize:Int + + /// Size of the buffer. + public let bufferSize:Int + + public init(windowSize: Int, bufferSize: Int) { + self.windowSize = windowSize + self.bufferSize = bufferSize + } + + public var algorithm: CompressionAlgorithm { + .lz77(windowSize: windowSize, bufferSize: bufferSize, offsetBitWidth: T.bitWidth) + } + + public var quality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The LZ77 compression technique. + /// + /// - Parameters: + /// - T: Integer type the offset is encoded as. Default is `UInt16`. + /// - windowSize: Size of the sliding window, measured in bytes. + /// - bufferSize: Size of the buffer, measured in bytes. + /// + /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 + public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { + return LZ77(windowSize: windowSize, bufferSize: bufferSize) + } + + /// The LZ77 compression technique where the offset is encoded as a `UInt16`. + /// + /// - Parameters: + /// - windowSize: Size of the sliding window, measured in bytes. + /// - bufferSize: Size of the buffer, measured in bytes. + /// + /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 + public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { + return LZ77(windowSize: windowSize, bufferSize: bufferSize) + } +} \ No newline at end of file diff --git a/Sources/Snappy/IWA.swift b/Sources/Snappy/IWA.swift index 17fd362..65c0a2f 100644 --- a/Sources/Snappy/IWA.swift +++ b/Sources/Snappy/IWA.swift @@ -17,12 +17,10 @@ extension CompressionTechnique { self.version = version } - @inlinable public var algorithm: CompressionAlgorithm { .iwa(version: version) } - @inlinable public var quality: CompressionQuality { .lossless } diff --git a/Sources/Snappy/Snappy+Compress.swift b/Sources/Snappy/Snappy+Compress.swift new file mode 100644 index 0000000..bdc10b5 --- /dev/null +++ b/Sources/Snappy/Snappy+Compress.swift @@ -0,0 +1,96 @@ + +import SwiftCompressionUtilities + +extension Snappy { + public func compress(data: some Collection, closure: (UInt8) -> Void) throws(CompressionError) -> UInt8? { + return nil + } + public func compress(data: some Collection, reserveCapacity: Int) throws -> CompressionResult<[UInt8]> { + throw CompressionError.unsupportedOperation + } +} + +/* +extension Snappy { // TODO: finish + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { + var index = data.startIndex + while index != data.endIndex { + let (length, matchLength, offset) = longestMatch(data, from: index) + if matchLength == 0 { + let next = data.index(index, offsetBy: length) + compressLiteral(data[index..>(_ data: C, from startIndex: C.Index) -> (length: Int, matchLength: Int, offset: Int) { + let maxLength = 60 + var longestMatchLength = 0 + var offset = 0 + + var length = 0 + var index = startIndex + while length < maxLength && index != data.endIndex { + let starts = data.index(index, offsetBy: -min(length, windowSize), limitedBy: data.startIndex) ?? data.startIndex + let longestMatch = longestCommonPrefix(data, index1: index, index2: starts) + if length > longestMatchLength { + longestMatchLength = longestMatch + offset = data.distance(from: starts, to: index) + } + length += 1 + index = data.index(after: index) + } + return (length, longestMatchLength, offset: offset) + } + + func longestCommonPrefix>(_ data: C, index1: C.Index, index2: C.Index) -> Int { + var length = 0 + var index1 = index1 + var index2 = index2 + while index1 != data.endIndex && index2 != data.endIndex && data[index1] == data[index2] { + length += 1 + index1 = data.index(after: index1) + index2 = data.index(after: index2) + } + return length + } +} + +// MARK: Literal +extension Snappy { + func compressLiteral>(_ data: C, closure: (UInt8) -> Void) { + let count = data.count + if count < 60 { + closure(UInt8(count << 2)) + } else { + closure(UInt8(60 << 2)) + closure(UInt8(count)) + } + for value in data { + closure(value) + } + } +} + +// MARK: Copy +extension Snappy { + func compressCopy(length: Int, offset: Int, closure: (UInt8) -> Void) { + if length < 12 && offset < 2048 { + let cmd = UInt8((offset >> 8) << 5 | (length - 4) << 2 | 1) + closure(cmd) + closure(UInt8(offset & 0xFF)) + } else { + closure(UInt8((length - 1) << 2) | 2) + closure(UInt8(offset & 0xFF)) + closure(UInt8((offset >> 8) & 0xFF)) + } + } +}*/ \ No newline at end of file diff --git a/Sources/Snappy/Snappy+Decompress.swift b/Sources/Snappy/Snappy+Decompress.swift new file mode 100644 index 0000000..1a4e28b --- /dev/null +++ b/Sources/Snappy/Snappy+Decompress.swift @@ -0,0 +1,202 @@ + +import SwiftCompressionUtilities + +extension Snappy { + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - reserveCapacity: Ignored. + public func decompress( + data: some Collection, + reserveCapacity: Int = 0 + ) throws(DecompressionError) -> [UInt8] { + var decompressed = [UInt8]() + var index = data.startIndex + let length:Int = try decompressLength(data: data, index: &index) + decompressed.reserveCapacity(length) + try decompress(data: data, index: &index, amount: length) { decompressed.append($0) } + return decompressed + } + + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - continuation: Yielding async throwing stream continuation. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func decompress( + data: some Collection, + continuation: AsyncThrowingStream.Continuation + ) throws(DecompressionError) { + var index = data.startIndex + let length:Int = try decompressLength(data: data, index: &index) + try decompress(data: data, index: &index, amount: length) { continuation.yield($0) } + } + + /// Calling this function directly will throw a DecompressionError.unsupportedOperation. + @available(*, deprecated, message: "Use decompress(data:index:amount:closure:) instead") + public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { + throw DecompressionError.unsupportedOperation + } + + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - index: Where to begin decompressing data. + /// - amount: Number of bytes to decompress. + /// - closure: Logic to execute when a byte is decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress>( + data: C, + index: inout C.Index, + amount: Int, + closure: (UInt8) -> Void + ) throws(DecompressionError) { + guard let endIndex = data.index(data.startIndex, offsetBy: amount, limitedBy: data.endIndex) else { throw DecompressionError.malformedInput } + while index < endIndex { + let control = data[index] + switch control & 0b11 { + case 0: decompressLiteral(flagBits: control, index: &index, compressed: data, closure: closure) + case 1: decompressCopy1(flagBits: control, index: &index, compressed: data, closure: closure) + case 2: decompressCopy2(flagBits: control, index: &index, compressed: data, closure: closure) + case 3: decompressCopy4(flagBits: control, index: &index, compressed: data, closure: closure) + default: throw DecompressionError.malformedInput + } + } + } + + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - index: Where to begin parsing the uncompressed length. + /// - Returns: The uncompressed length, as described in the `Preamble` at https://github.com/google/snappy/blob/main/format_description.txt . + /// - Complexity: O(1). + func decompressLength, I: FixedWidthInteger>( + data: C, + index: inout C.Index + ) throws(DecompressionError) -> I { + var totalSize:I + var byte = data[index] + if byte & 0b10000000 != 0 { + totalSize = I(byte) + data.formIndex(after: &index) + guard let second = data[positive: index] else { throw DecompressionError.malformedInput } + byte = second + var shift = 7 + while byte & 0b10000000 != 0 { + totalSize |= I(byte) << shift + shift += 7 + data.formIndex(after: &index) + if let next = data[positive: index] { + byte = next + } else { + throw DecompressionError.malformedInput + } + } + // final length byte + totalSize |= I(byte) << shift + } else { + totalSize = I(byte) + } + data.formIndex(after: &index) + return totalSize + } +} + +// MARK: Literal +extension Snappy { + /// - Complexity: O(_n_) where _n_ is the length of the literal. + func decompressLiteral>( + flagBits: UInt8, + index: inout C.Index, + compressed: C, + closure: (_ byte: UInt8) -> Void + ) { + let length = decompressLiteralLength(flagBits: flagBits, index: &index, compressed: compressed) + for _ in 0...length { + closure(compressed[index]) + compressed.formIndex(after: &index) + } + } + + /// - Complexity: O(1). + func decompressLiteralLength>(flagBits: UInt8, index: inout C.Index, compressed: C) -> Int { + let length = flagBits >> 2 // ignore tag bits + compressed.formIndex(after: &index) + var totalLength:Int + if length >= 60 { + totalLength = 0 + for _ in 0..>( + flagBits: UInt8, + index: inout C.Index, + compressed: C, + closure: (_ byte: UInt8) -> Void + ) { + let length = 4 + ((flagBits >> 2) & 0b00000111) + let offset = Int(((UInt16(flagBits) << 8) & 0b11100000) + UInt16(compressed[compressed.index(index, offsetBy: 1)])) + var begins = compressed.index(index, offsetBy: -offset) + for _ in 0..>( + flagBits: UInt8, + index: inout C.Index, + compressed: C, + closure: (_ byte: UInt8) -> Void + ) { + //let offset:UInt16 = UInt16( UInt16(compressed[compressed.index(index, offsetBy: 1)]) << 8 | UInt16(compressed[compressed.index(index, offsetBy: 2)]) ) + let offset = UInt16(fromBits: + compressed[compressed.index(index, offsetBy: 1)], + compressed[compressed.index(index, offsetBy: 2)] + ) + decompressCopyN(flagBits: flagBits, index: &index, compressed: compressed, offset: offset, readBytes: 3, closure: closure) + } + + /// - Complexity: O(_n_) where _n_ is the `UInt8` created from the `flagBits`. + func decompressCopy4>( + flagBits: UInt8, + index: inout C.Index, + compressed: C, + closure: (_ byte: UInt8) -> Void + ) { + let offset = UInt32.init(fromBits: + compressed[compressed.index(index, offsetBy: 1)], + compressed[compressed.index(index, offsetBy: 2)], + compressed[compressed.index(index, offsetBy: 3)] + ) + decompressCopyN(flagBits: flagBits, index: &index, compressed: compressed, offset: offset, readBytes: 5, closure: closure) + } + + /// - Complexity: O(_n_) where _n_ is the `UInt8` created from the `flagBits`. + func decompressCopyN, T: FixedWidthInteger>( + flagBits: UInt8, + index: inout C.Index, + compressed: C, + offset: T, + readBytes: Int, + closure: (_ byte: UInt8) -> Void + ) { + let length = flagBits & 0b11111100 + //print("decompressCopyN;readBytes=\(readBytes);length=\(length)") + var begins = compressed.index(index, offsetBy: -Int(offset)) + for _ in 0.. Snappy { - return Snappy(windowSize: windowSize) - } - - public struct Snappy: Compressor, Decompressor { - - /// Size of the window. - public let windowSize:Int - - public init(windowSize: Int = Int(UInt16.max)) { - self.windowSize = windowSize - } - - @inlinable - public var algorithm: CompressionAlgorithm { - .snappy(windowSize: windowSize) - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.Snappy { - public func compress(data: some Collection, closure: (UInt8) -> Void) throws(CompressionError) -> UInt8? { - return nil - } - public func compress(data: some Collection, reserveCapacity: Int) throws -> CompressionResult<[UInt8]> { - throw CompressionError.unsupportedOperation - } -} -/* -extension CompressionTechnique.Snappy { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - var index = data.startIndex - while index != data.endIndex { - let (length, matchLength, offset) = longestMatch(data, from: index) - if matchLength == 0 { - let next = data.index(index, offsetBy: length) - compressLiteral(data[index..>(_ data: C, from startIndex: C.Index) -> (length: Int, matchLength: Int, offset: Int) { - let maxLength = 60 - var longestMatchLength = 0 - var offset = 0 - - var length = 0 - var index = startIndex - while length < maxLength && index != data.endIndex { - let starts = data.index(index, offsetBy: -min(length, windowSize), limitedBy: data.startIndex) ?? data.startIndex - let longestMatch = longestCommonPrefix(data, index1: index, index2: starts) - if length > longestMatchLength { - longestMatchLength = longestMatch - offset = data.distance(from: starts, to: index) - } - length += 1 - index = data.index(after: index) - } - return (length, longestMatchLength, offset: offset) - } - - func longestCommonPrefix>(_ data: C, index1: C.Index, index2: C.Index) -> Int { - var length = 0 - var index1 = index1 - var index2 = index2 - while index1 != data.endIndex && index2 != data.endIndex && data[index1] == data[index2] { - length += 1 - index1 = data.index(after: index1) - index2 = data.index(after: index2) - } - return length - } -} +public struct Snappy: Compressor, Decompressor { -// MARK: Literal -extension CompressionTechnique.Snappy { - func compressLiteral>(_ data: C, closure: (UInt8) -> Void) { - let count = data.count - if count < 60 { - closure(UInt8(count << 2)) - } else { - closure(UInt8(60 << 2)) - closure(UInt8(count)) - } - for value in data { - closure(value) - } - } -} - -// MARK: Copy -extension CompressionTechnique.Snappy { - func compressCopy(length: Int, offset: Int, closure: (UInt8) -> Void) { - if length < 12 && offset < 2048 { - let cmd = UInt8((offset >> 8) << 5 | (length - 4) << 2 | 1) - closure(cmd) - closure(UInt8(offset & 0xFF)) - } else { - closure(UInt8((length - 1) << 2) | 2) - closure(UInt8(offset & 0xFF)) - closure(UInt8((offset >> 8) & 0xFF)) - } - } -}*/ - -// MARK: Decompress -extension CompressionTechnique.Snappy { - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - reserveCapacity: Ignored. - public func decompress( - data: some Collection, - reserveCapacity: Int = 0 - ) throws(DecompressionError) -> [UInt8] { - var decompressed = [UInt8]() - var index = data.startIndex - let length:Int = try decompressLength(data: data, index: &index) - decompressed.reserveCapacity(length) - try decompress(data: data, index: &index, amount: length) { decompressed.append($0) } - return decompressed - } - - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - continuation: Yielding async throwing stream continuation. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func decompress( - data: some Collection, - continuation: AsyncThrowingStream.Continuation - ) throws(DecompressionError) { - var index = data.startIndex - let length:Int = try decompressLength(data: data, index: &index) - try decompress(data: data, index: &index, amount: length) { continuation.yield($0) } - } + /// Size of the window. + public let windowSize:Int - /// Calling this function directly will throw a DecompressionError.unsupportedOperation. - @available(*, deprecated, message: "Use decompress(data:index:amount:closure:) instead") - public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { - throw DecompressionError.unsupportedOperation("Use decompress(data:index:amount:closure:) instead") + public init(windowSize: Int = Int(UInt16.max)) { + self.windowSize = windowSize } - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - index: Where to begin decompressing data. - /// - amount: Number of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress>( - data: C, - index: inout C.Index, - amount: Int, - closure: (UInt8) -> Void - ) throws(DecompressionError) { - guard let endIndex = data.index(data.startIndex, offsetBy: amount, limitedBy: data.endIndex) else { throw DecompressionError.malformedInput } - while index < endIndex { - let control = data[index] - switch control & 0b11 { - case 0: decompressLiteral(flagBits: control, index: &index, compressed: data, closure: closure) - case 1: decompressCopy1(flagBits: control, index: &index, compressed: data, closure: closure) - case 2: decompressCopy2(flagBits: control, index: &index, compressed: data, closure: closure) - case 3: decompressCopy4(flagBits: control, index: &index, compressed: data, closure: closure) - default: throw DecompressionError.malformedInput - } - } + public var algorithm: CompressionAlgorithm { + .snappy(windowSize: windowSize) } - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - index: Where to begin parsing the uncompressed length. - /// - Returns: The uncompressed length, as described in the `Preamble` at https://github.com/google/snappy/blob/main/format_description.txt . - /// - Complexity: O(1). - func decompressLength, I: FixedWidthInteger>( - data: C, - index: inout C.Index - ) throws(DecompressionError) -> I { - var totalSize:I - var byte = data[index] - if byte & 0b10000000 != 0 { - totalSize = I(byte) - data.formIndex(after: &index) - guard let second = data[positive: index] else { throw DecompressionError.malformedInput } - byte = second - var shift = 7 - while byte & 0b10000000 != 0 { - totalSize |= I(byte) << shift - shift += 7 - data.formIndex(after: &index) - if let next = data[positive: index] { - byte = next - } else { - throw DecompressionError.malformedInput - } - } - // final length byte - totalSize |= I(byte) << shift - } else { - totalSize = I(byte) - } - data.formIndex(after: &index) - return totalSize + public var quality: CompressionQuality { + .lossless } } -// MARK: Literal -extension CompressionTechnique.Snappy { - /// - Complexity: O(_n_) where _n_ is the length of the literal. - func decompressLiteral>( - flagBits: UInt8, - index: inout C.Index, - compressed: C, - closure: (_ byte: UInt8) -> Void - ) { - let length = decompressLiteralLength(flagBits: flagBits, index: &index, compressed: compressed) - for _ in 0...length { - closure(compressed[index]) - compressed.formIndex(after: &index) - } - } - - /// - Complexity: O(1). - func decompressLiteralLength>(flagBits: UInt8, index: inout C.Index, compressed: C) -> Int { - let length = flagBits >> 2 // ignore tag bits - compressed.formIndex(after: &index) - var totalLength:Int - if length >= 60 { - totalLength = 0 - for _ in 0..>( - flagBits: UInt8, - index: inout C.Index, - compressed: C, - closure: (_ byte: UInt8) -> Void - ) { - let length = 4 + ((flagBits >> 2) & 0b00000111) - let offset = Int(((UInt16(flagBits) << 8) & 0b11100000) + UInt16(compressed[compressed.index(index, offsetBy: 1)])) - var begins = compressed.index(index, offsetBy: -offset) - for _ in 0..>( - flagBits: UInt8, - index: inout C.Index, - compressed: C, - closure: (_ byte: UInt8) -> Void - ) { - //let offset:UInt16 = UInt16( UInt16(compressed[compressed.index(index, offsetBy: 1)]) << 8 | UInt16(compressed[compressed.index(index, offsetBy: 2)]) ) - let offset = UInt16(fromBits: - compressed[compressed.index(index, offsetBy: 1)], - compressed[compressed.index(index, offsetBy: 2)] - ) - decompressCopyN(flagBits: flagBits, index: &index, compressed: compressed, offset: offset, readBytes: 3, closure: closure) - } - - /// - Complexity: O(_n_) where _n_ is the `UInt8` created from the `flagBits`. - func decompressCopy4>( - flagBits: UInt8, - index: inout C.Index, - compressed: C, - closure: (_ byte: UInt8) -> Void - ) { - let offset = UInt32.init(fromBits: - compressed[compressed.index(index, offsetBy: 1)], - compressed[compressed.index(index, offsetBy: 2)], - compressed[compressed.index(index, offsetBy: 3)] - ) - decompressCopyN(flagBits: flagBits, index: &index, compressed: compressed, offset: offset, readBytes: 5, closure: closure) - } - - /// - Complexity: O(_n_) where _n_ is the `UInt8` created from the `flagBits`. - func decompressCopyN, T: FixedWidthInteger>( - flagBits: UInt8, - index: inout C.Index, - compressed: C, - offset: T, - readBytes: Int, - closure: (_ byte: UInt8) -> Void - ) { - let length = flagBits & 0b11111100 - //print("decompressCopyN;readBytes=\(readBytes);length=\(length)") - var begins = compressed.index(index, offsetBy: -Int(offset)) - for _ in 0.. Snappy { + return Snappy(windowSize: windowSize) } } \ No newline at end of file diff --git a/Sources/Snappy/SnappyFramed.swift b/Sources/Snappy/SnappyFramed.swift deleted file mode 100644 index 242e187..0000000 --- a/Sources/Snappy/SnappyFramed.swift +++ /dev/null @@ -1,46 +0,0 @@ - -import SwiftCompressionUtilities - -extension CompressionTechnique { - /// The Snappy Framed compression technique. - /// - /// https://en.wikipedia.org/wiki/Snappy_(compression)#Framing_format - /// - /// https://github.com/google/snappy - public static let snappyFramed:SnappyFramed = SnappyFramed() - - public struct SnappyFramed: Compressor, Decompressor { - @inlinable - public var algorithm: CompressionAlgorithm { - .snappyFramed - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.SnappyFramed { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - return nil - } -} - -// MARK: Decompress -extension CompressionTechnique.SnappyFramed { // TODO: finish - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - } -} \ No newline at end of file diff --git a/Sources/Snappy/framed/SnappyFramed+Compress.swift b/Sources/Snappy/framed/SnappyFramed+Compress.swift new file mode 100644 index 0000000..f61028f --- /dev/null +++ b/Sources/Snappy/framed/SnappyFramed+Compress.swift @@ -0,0 +1,11 @@ + +import SwiftCompressionUtilities + +extension SnappyFramed { // TODO: finish + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { + return nil + } +} \ No newline at end of file diff --git a/Sources/Snappy/framed/SnappyFramed+Decompress.swift b/Sources/Snappy/framed/SnappyFramed+Decompress.swift new file mode 100644 index 0000000..d335d63 --- /dev/null +++ b/Sources/Snappy/framed/SnappyFramed+Decompress.swift @@ -0,0 +1,14 @@ + +import SwiftCompressionUtilities + +extension SnappyFramed { // TODO: finish + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte is decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + closure: (UInt8) -> Void + ) { + } +} \ No newline at end of file diff --git a/Sources/Snappy/framed/SnappyFramed.swift b/Sources/Snappy/framed/SnappyFramed.swift new file mode 100644 index 0000000..b6a335c --- /dev/null +++ b/Sources/Snappy/framed/SnappyFramed.swift @@ -0,0 +1,21 @@ + +import SwiftCompressionUtilities + +public struct SnappyFramed: Compressor, Decompressor { + public var algorithm: CompressionAlgorithm { + .snappyFramed + } + + public var quality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The Snappy Framed compression technique. + /// + /// https://en.wikipedia.org/wiki/Snappy_(compression)#Framing_format + /// + /// https://github.com/google/snappy + public static let snappyFramed:SnappyFramed = SnappyFramed() +} \ No newline at end of file diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index a520976..5ec2376 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -1,7 +1,5 @@ @_exported import CompressionDNA -@_exported import CompressionCSS -@_exported import CompressionJavaScript @_exported import CompressionLZ @_exported import CompressionSnappy @_exported import SwiftCompressionUtilities @@ -21,22 +19,21 @@ extension CompressionAlgorithm { case .bwt: return nil case .deflate: return nil case .huffmanCoding: return nil - case .json: return nil case .lz4: return nil case .lz77(let windowSize, let bufferSize, let offsetBitWidth): switch offsetBitWidth { case 8: - return CompressionTechnique.LZ77(windowSize: windowSize, bufferSize: bufferSize) + return LZ77(windowSize: windowSize, bufferSize: bufferSize) case 16: - return CompressionTechnique.LZ77(windowSize: windowSize, bufferSize: bufferSize) + return LZ77(windowSize: windowSize, bufferSize: bufferSize) case 32: - return CompressionTechnique.LZ77(windowSize: windowSize, bufferSize: bufferSize) + return LZ77(windowSize: windowSize, bufferSize: bufferSize) case 64: - return CompressionTechnique.LZ77(windowSize: windowSize, bufferSize: bufferSize) + return LZ77(windowSize: windowSize, bufferSize: bufferSize) #if compiler(>=6.0) case 128: if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { - return CompressionTechnique.LZ77(windowSize: windowSize, bufferSize: bufferSize) + return LZ77(windowSize: windowSize, bufferSize: bufferSize) } return nil #endif @@ -82,13 +79,6 @@ extension CompressionAlgorithm { case .iwa(let version): return CompressionTechnique.iwa(version: version) - case .programmingLanguage(let lang): - switch lang { - case .css: return CompressionTechnique.css - case .javascript: return CompressionTechnique.javascript - case .swift: return CompressionTechnique.swift - @unknown default: return nil - } @unknown default: return nil } } diff --git a/Sources/SwiftCompression/techniques/JSON.swift b/Sources/SwiftCompression/techniques/JSON.swift deleted file mode 100644 index ff9adaf..0000000 --- a/Sources/SwiftCompression/techniques/JSON.swift +++ /dev/null @@ -1,87 +0,0 @@ - -import SwiftCompressionUtilities - -#if canImport(Foundation) -import Foundation -#endif - -extension CompressionTechnique { - public enum JSON { // TODO: finish - } -} - -#if canImport(Foundation) -// MARK: Compress encodable -extension CompressionTechnique.JSON { - public static func compress(encodable: some Encodable) -> CompressionResult<[UInt8]>? { - guard let data = try? JSONEncoder().encode(encodable) else { return nil } - var compressed = [UInt8]() - compressed.reserveCapacity(data.count) - guard let object = try? JSONSerialization.jsonObject(with: data) else { return nil } - let quotationMark:UInt8 = 34 - let comma:UInt8 = 44 - func encode(_ value: Any) { - if var string = value as? String { - compressed.append(comma) - compressed.append(quotationMark) - while !string.isEmpty { - let char = string.removeFirst() - if let value = char.asciiValue { - compressed.append(value) - } - } - compressed.append(quotationMark) - } else if let int = value as? any FixedWidthInteger { - compressed.append(comma) - var builder = CompressionTechnique.DataBuilder() - builder.write(bits: int.bits) - builder.finalize() - compressed.append(contentsOf: builder.data) - } else if let dic = value as? [String:Any] { - compressed.append(comma) - for value in dic.values { - encode(value) - } - } else if let array = value as? [Any] { - compressed.append(comma) - for value in array { - encode(value) - } - } else if let bool = value as? Bool { - compressed.append(comma) - compressed.append(contentsOf: bool ? [] : []) - } - } - if let dic = object as? [String:Any] { - for value in dic.values { - encode(value) - } - compressed[0] = 91 // [ - compressed.append(93) // ] - } else if let array = object as? [Any] { - for value in array { - encode(value) - } - compressed[0] = 91 // [ - compressed.append(93) // ] - } else { - return nil - } - return compress(data: compressed) - } -} -#endif - -// MARK: Compress -extension CompressionTechnique.JSON { - public static func compress(data: some Sequence) -> CompressionResult<[UInt8]>? { - return nil - } -} - -// MARK: Decompress -extension CompressionTechnique.JSON { - public static func decompress(data: [UInt8]) -> [UInt8] { - return data - } -} \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/MoveToFront.swift b/Sources/SwiftCompression/techniques/MoveToFront.swift index 285f073..c591f11 100644 --- a/Sources/SwiftCompression/techniques/MoveToFront.swift +++ b/Sources/SwiftCompression/techniques/MoveToFront.swift @@ -1,16 +1,14 @@ import SwiftCompressionUtilities -extension CompressionTechnique { - /// The Move-to-front transform compression technique. - /// - /// https://en.wikipedia.org/wiki/Move-to-front_transform - public enum MoveToFront { // TODO: finish - } +/// The Move-to-front transform compression technique. +/// +/// https://en.wikipedia.org/wiki/Move-to-front_transform +public enum MoveToFront { // TODO: finish } // MARK: Transform -extension CompressionTechnique.MoveToFront { +extension MoveToFront { public static func transform( data: some Sequence, reserveCapacity: Int = 1024 @@ -26,7 +24,7 @@ extension CompressionTechnique.MoveToFront { } } -extension CompressionTechnique.MoveToFront { +extension MoveToFront { struct Record { } } \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/RunLengthEncoding.swift b/Sources/SwiftCompression/techniques/RunLengthEncoding.swift deleted file mode 100644 index f7f7eab..0000000 --- a/Sources/SwiftCompression/techniques/RunLengthEncoding.swift +++ /dev/null @@ -1,123 +0,0 @@ - -import SwiftCompressionUtilities - -extension CompressionTechnique { - - /// The Run-length encoding compression technique. - /// - /// https://en.wikipedia.org/wiki/Run-length_encoding - public static func runLength(minRun: Int, alwaysIncludeRunCount: Bool) -> RunLengthEncoding { - return RunLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) - } - - public struct RunLengthEncoding: Compressor, Decompressor { - public typealias CompressClosureParameters = (run: Int, byte: UInt8) - - /// Minimum run count required to compress identical sequential bytes. - public let minRun:Int - - /// Whether or not to always include the run count in the result, regardless of run count. - public let alwaysIncludeRunCount:Bool - - public init(minRun: Int, alwaysIncludeRunCount: Bool) { - self.minRun = minRun - self.alwaysIncludeRunCount = alwaysIncludeRunCount - } - - @inlinable - public var algorithm: CompressionAlgorithm { - .runLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) - } - - @inlinable - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.RunLengthEncoding { - public func compressClosure(closure: @escaping (UInt8) -> Void) -> @Sendable (CompressClosureParameters) -> Void { - if alwaysIncludeRunCount { - return { (arg) in - let (run, runByte) = arg - closure(UInt8(191 + run)) - closure(runByte) - } - } else { - return { (arg) in - let (run, runByte) = arg - if runByte <= 191 && run < minRun { - for byte in Array(repeating: runByte, count: run) { - closure(byte) - } - } else { - closure(UInt8(191 + run)) - closure(runByte) - } - } - } - } - - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - minRun: Minimum run count required to compress identical sequential bytes. - /// - closure: Logic to execute for a run. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Sequence, closure: (CompressClosureParameters) -> Void) -> UInt8? { - var run = 0 - var runByte:UInt8? = nil - data.withContiguousStorageIfAvailable { p in - for index in 0.., closure: (UInt8) -> Void) { - let count = data.count - var index = 0 - var run:UInt8 = 0 - var character:UInt8 = 0 - while index < count { - run = data[index] - if run > 191 { - run -= 191 - character = data[index+1] - index += 2 - for _ in 0.., reserveCapacity: Int) throws(CompressionError) -> CompressionResult<[UInt8]> { - throw CompressionError.unsupportedOperation // TODO: support? - } - - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - closure: Logic to execute for a run. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Sequence, closure: (UInt8) -> Void) -> UInt8? { - return nil // TODO: support? - } -} - -/* -extension CompressionTechnique.SwiftLang { // TODO: support - public func compress(filePath: String) throws(CompressionError) -> String { - return "" - } -}*/ -// MARK: Minify -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension CompressionTechnique.SwiftLang { - /// Minifies Swift code to make it suitable for production-only usage, which results in the minimum binary size required to represent the same code. - /// - /// Optionally removes documentation, comments, unnecessary whitespace and access control declarations, and unit tests - /// - /// - Parameters: - /// - swiftSourceCode: Literal, valid, Swift code. - func minify(swiftSourceCode: T) throws -> T { - // TODO: finish - let accessControlInternal = try Regex(#"(internal\s+)"#) - let documentation = try Regex(#"(\/\/\/.*)"#) - let comments = try Regex(#"(\/\/.*)"#) - - var cleanup:[(Regex, String)] = try [ - (Regex(#"(\s*:\s*)"#), ":"), - (Regex(#"(\s*{\s+)"#), "{"), - (Regex(#"(\s*\(\s+)"#), "("), - (Regex(#"(\s*\)\s+)"#), ")"), - - (Regex(#"(\s*{\s*get\s*})"#), "{get}"), // { get } - (Regex(#"(\s*{\s*set\s*})"#), "{set}"), // { set } - (Regex(#"(\s*{(\s*get\s*)\s*(\s*set\s*)\s*})"#), "{get set}"), // { get set } - (Regex(#"(\s*{(\s*set\s*)\s*(\s*get\s*)\s*})"#), "{set get}") // { set get } - ] - let recursive:[Regex:String] = try [ - Regex(#"(\s*{\s+{)"#): "{{", - Regex(#"(\s*}\s+})"#): "}}" - ] - let finalCleanup:[(Regex, String)] = try [ - (Regex(#"(}\s+fileprivate\s+var)"#), "};fileprivate var"), - (Regex(#"(}\s+fileprivate\s+let)"#), "};fileprivate let"), - (Regex(#"(}\s+open\s+var)"#), "};open var"), - (Regex(#"(}\s+open\s+let)"#), "};open let"), - (Regex(#"(}\s+package\s+var)"#), "};package var"), - (Regex(#"(}\s+package\s+let)"#), "};package let"), - (Regex(#"(}\s+private\s+var)"#), "};private var"), - (Regex(#"(}\s+private\s+let)"#), "};private let"), - (Regex(#"(}\s+public\s+var)"#), "};public var"), - (Regex(#"(}\s+public\s+let)"#), "};public let"), - (Regex(#"(}\s+var)"#), "};var"), - (Regex(#"(}\s+let)"#), "};let"), - ] - cleanup.append(contentsOf: finalCleanup) - - //let stringLiterals:Regex = try Regex(#"(".+")"#) - var result = String(swiftSourceCode) - //let stringLiteralMatches = result.matches(of: stringLiterals) - result.replace(accessControlInternal, with: "") - result.replace(documentation, with: "") - result.replace(comments, with: "") - for (regex, replacement) in recursive { - var matches = result.matches(of: regex) - while !matches.isEmpty { - for match in matches.reversed() { - result.replaceSubrange(match.range, with: replacement) - } - matches = result.matches(of: regex) - } - } - for (regex, replacement) in cleanup { - result.replace(regex, with: replacement) - } - while result.first?.isWhitespace == true { - result.removeFirst() - } - return T(stringLiteral: result) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension Regex: @retroactive Equatable { - public static func == (lhs: Regex, rhs: Regex) -> Bool { - false - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension Regex: @retroactive Hashable { - public func hash(into hasher: inout Hasher) { - } -} \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift new file mode 100644 index 0000000..d43d66f --- /dev/null +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift @@ -0,0 +1,57 @@ + +extension RunLengthEncoding { + public func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { + if alwaysIncludeRunCount { + return { (arg) in + let (run, runByte) = arg + closure(UInt8(191 + run)) + closure(runByte) + } + } else { + return { (arg) in + let (run, runByte) = arg + if runByte <= 191 && run < minRun { + for byte in Array(repeating: runByte, count: run) { + closure(byte) + } + } else { + closure(UInt8(191 + run)) + closure(runByte) + } + } + } + } + + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - minRun: Minimum run count required to compress identical sequential bytes. + /// - closure: Logic to execute for a run. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress(data: some Sequence, closure: (CompressClosureParameters) -> Void) -> UInt8? { + var run = 0 + var runByte:UInt8? = nil + data.withContiguousStorageIfAvailable { p in + for index in 0.., closure: (UInt8) -> Void) { + let count = data.count + var index = 0 + var run:UInt8 = 0 + var character:UInt8 = 0 + while index < count { + run = data[index] + if run > 191 { + run -= 191 + character = data[index+1] + index += 2 + for _ in 0.. RunLengthEncoding { + return RunLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) + } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/AnyCompressor.swift b/Sources/SwiftCompressionUtilities/AnyCompressor.swift index 133ed5f..cde221c 100644 --- a/Sources/SwiftCompressionUtilities/AnyCompressor.swift +++ b/Sources/SwiftCompressionUtilities/AnyCompressor.swift @@ -1,6 +1,6 @@ // MARK: AnyCompressor -public protocol AnyCompressor: Sendable { +public protocol AnyCompressor: Sendable, ~Copyable { /// Compression algorithm this compressor uses. var algorithm: CompressionAlgorithm { get } diff --git a/Sources/SwiftCompressionUtilities/AnyDecompressor.swift b/Sources/SwiftCompressionUtilities/AnyDecompressor.swift index 04721c7..a322606 100644 --- a/Sources/SwiftCompressionUtilities/AnyDecompressor.swift +++ b/Sources/SwiftCompressionUtilities/AnyDecompressor.swift @@ -1,6 +1,6 @@ // MARK: AnyDecompressor -public protocol AnyDecompressor: Sendable { +public protocol AnyDecompressor: Sendable, ~Copyable { /// Decompression algorithm this decompressor uses. var algorithm: CompressionAlgorithm { get } diff --git a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift index b04a5ba..1d97976 100644 --- a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift +++ b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift @@ -15,7 +15,6 @@ public enum CompressionAlgorithm: Hashable, Sendable { case bwt case deflate case huffmanCoding - case json case lz4 case lz77(windowSize: Int, bufferSize: Int, offsetBitWidth: Int) case lz78 @@ -62,9 +61,6 @@ public enum CompressionAlgorithm: Hashable, Sendable { // Apple /// iWork Archive (Pages, Keynote, Numbers) case iwa(version: IWAVersion) - - // Code - case programmingLanguage(ProgrammingLanguage) } // MARK: RawValue @@ -72,57 +68,54 @@ extension CompressionAlgorithm { /// Case name of the algorithm. public var rawValue: String { switch self { - case .unknown: return "unknown" + case .unknown: "unknown" - case .aac: return "aac" - case .mp3: return "mp3" - - case .arithmetic: return "arithmetic" - case .brotli: return "brotli" - - case .bwt: return "bwt" - case .deflate: return "deflate" - case .huffmanCoding: return "huffmanCoding" - case .json: return "json" - case .lz4: return "lz4" - case .lz77: return "lz77" - case .lz78: return "lz78" - case .lzw: return "lzw" - case .mtf: return "mtf" - case .runLengthEncoding: return "runLengthEncoding" - case .snappy: return "snappy" - case .snappyFramed: return "snappyFramed" - case .zstd: return "zstd" - - case ._7z: return "_7z" - case .bzip2: return "bzip2" - case .gzip: return "gzip" - case .rar: return "rar" + case .aac: "aac" + case .mp3: "mp3" + + case .arithmetic: "arithmetic" + case .brotli: "brotli" + + case .bwt: "bwt" + case .deflate: "deflate" + case .huffmanCoding: "huffmanCoding" + case .lz4: "lz4" + case .lz77: "lz77" + case .lz78: "lz78" + case .lzw: "lzw" + case .mtf: "mtf" + case .runLengthEncoding: "runLengthEncoding" + case .snappy: "snappy" + case .snappyFramed: "snappyFramed" + case .zstd: "zstd" + + case ._7z: "_7z" + case .bzip2: "bzip2" + case .gzip: "gzip" + case .rar: "rar" - case .h264: return "h264" - case .h265: return "h265" - case .jpeg: return "jpeg" - case .jpeg2000: return "jpeg2000" - - case .eliasDelta: return "eliasDelta" - case .eliasGamma: return "eliasGamma" - case .eliasOmega: return "eliasOmega" - case .fibonacci: return "fibonacci" + case .h264: "h264" + case .h265: "h265" + case .jpeg: "jpeg" + case .jpeg2000: "jpeg2000" - case .dnaBinaryEncoding: return "dnaBinaryEncoding" - case .dnaSingleBlockEncoding: return "dnaSingleBlockEncoding" + case .eliasDelta: "eliasDelta" + case .eliasGamma: "eliasGamma" + case .eliasOmega: "eliasOmega" + case .fibonacci: "fibonacci" - case .boringSSL: return "boringSSL" + case .dnaBinaryEncoding: "dnaBinaryEncoding" + case .dnaSingleBlockEncoding: "dnaSingleBlockEncoding" - case .av1: return "av1" - case .dirac: return "dirac" - case .mpeg: return "mpeg" + case .boringSSL: "boringSSL" - case .iwa: return "iwa" + case .av1: "av1" + case .dirac: "dirac" + case .mpeg: "mpeg" - case .programmingLanguage: return "programmingLanguage" + case .iwa: "iwa" - @unknown default: return "unknown" + @unknown default: "unknown" } } } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift index 4124a8d..113d9df 100644 --- a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift +++ b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift @@ -49,4 +49,39 @@ extension CompressionTechnique { } return table } -} \ No newline at end of file +} + +#if compiler(>=6.2) +// MARK: Inline Frequency tables +extension CompressionTechnique { + /// Creates a universal frequency table from a sequence of raw bytes. + /// + /// - Parameters: + /// - data: Sequence of raw bytes. + /// - Returns: A universal frequency table. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public static func buildInlineFrequencyTable(data: some Sequence) -> [255 of Int] { + var table = [255 of Int](repeating: 0) + for byte in data { + table[Int(byte)] += 1 + } + return table + } + + /// Creates a universal frequency table from a character frequency dictionary. + /// + /// - Parameters: + /// - chars: A frequency table that represents how many times a character is present. + /// - Returns: A universal frequency table. + /// - Complexity: O(_n_) where _n_ is the sum of the `Character` utf8 lengths in `chars`. + public static func buildInlineFrequencyTable(chars: [Character:Int]) -> [255 of Int] { + var table = [255 of Int](repeating: 0) + for (char, freq) in chars { + for byte in char.utf8 { + table[Int(byte)] = freq + } + } + return table + } +} +#endif \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/Compressor.swift b/Sources/SwiftCompressionUtilities/Compressor.swift index 08e92ef..63c0c92 100644 --- a/Sources/SwiftCompressionUtilities/Compressor.swift +++ b/Sources/SwiftCompressionUtilities/Compressor.swift @@ -1,8 +1,8 @@ // MARK: Compressor -public protocol Compressor: AnyCompressor { +public protocol Compressor: AnyCompressor, ~Copyable { associatedtype CompressClosureParameters - func compressClosure(closure: @escaping @Sendable (UInt8) -> Void) -> @Sendable (CompressClosureParameters) -> Void + func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void func compress( data: some Collection, @@ -65,7 +65,7 @@ extension Compressor { } } extension Compressor where CompressClosureParameters == UInt8 { - public func compressClosure(closure: @escaping @Sendable (UInt8) -> Void) -> @Sendable (CompressClosureParameters) -> Void { + public func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { closure } } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/Decompressor.swift b/Sources/SwiftCompressionUtilities/Decompressor.swift index 484f9ad..ed131ba 100644 --- a/Sources/SwiftCompressionUtilities/Decompressor.swift +++ b/Sources/SwiftCompressionUtilities/Decompressor.swift @@ -1,6 +1,6 @@ // MARK: Decompressor -public protocol Decompressor: AnyDecompressor { +public protocol Decompressor: AnyDecompressor, ~Copyable { associatedtype DecompressClosureParameters /// Decompress a collection of bytes using this technique. diff --git a/Sources/SwiftCompressionUtilities/ProgrammingLanguage.swift b/Sources/SwiftCompressionUtilities/ProgrammingLanguage.swift deleted file mode 100644 index d81493f..0000000 --- a/Sources/SwiftCompressionUtilities/ProgrammingLanguage.swift +++ /dev/null @@ -1,7 +0,0 @@ - -/// List of supported programming languages with compression techniques. -public enum ProgrammingLanguage: Sendable { - case css - case javascript - case swift -} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/errors/DecompressionError.swift b/Sources/SwiftCompressionUtilities/errors/DecompressionError.swift index 95da849..12af7d5 100644 --- a/Sources/SwiftCompressionUtilities/errors/DecompressionError.swift +++ b/Sources/SwiftCompressionUtilities/errors/DecompressionError.swift @@ -2,5 +2,5 @@ /// SwiftCompression errors that can be thrown when decompressing data. public enum DecompressionError: Error { case malformedInput - case unsupportedOperation(String = "") + case unsupportedOperation } \ No newline at end of file diff --git a/Tests/CSSTests/CSSTests.swift b/Tests/CSSTests/CSSTests.swift deleted file mode 100644 index 63386f1..0000000 --- a/Tests/CSSTests/CSSTests.swift +++ /dev/null @@ -1,96 +0,0 @@ - -#if compiler(>=6.0) - -import Testing -@testable import CompressionCSS -@testable import SwiftCompressionUtilities - -// MARK: CSSTests -struct CSSTests { -} - -// MARK: CSSMinifyTests -struct CSSMinifyTests { - private func minify(_ string: String) -> String { - return String(decoding: CompressionTechnique.css.minify(data: [UInt8](string.utf8), reserveCapacity: 0), as: UTF8.self) - } - @Test func minifyCSSNative() { - #expect(minify(""" - * { - box-sizing: border-box; - } - """) == "*{box-sizing:border-box;}") - } - - @Test func minifyCSSCommaSeparatedInputs() { - #expect(minify(""" - input[type=email], input[type=password], input[type=text], input[type=number], input[type=date], input[type=time] { - border: 1px solid #ccc; - border-radius: 3px; - } - """) == "input[type=email],input[type=password],input[type=text],input[type=number],input[type=date],input[type=time]{border:1px solid #ccc;border-radius:3px;}") - } - - @Test func minifyCSSNestedClass() { - #expect(minify(""" - div#notification_area .notification_popup .notification_popup_content { - } - """) == "div#notification_area .notification_popup .notification_popup_content{}") - } - - @Test func minifyCSSComment() { - #expect(minify(""" - div#notification_area { - position: fixed; - overflow-x: scroll; - max-height: 100%; - bottom: 10%; - right: 5%; - width: 25%; - z-index: 96; /* 1 above the footer */ - } - """) == "div#notification_area{position:fixed;overflow-x:scroll;max-height:100%;bottom:10%;right:5%;width:25%;z-index:96;}") - } - - @Test func minifyCSSKeyframeSpin() { - #expect(minify(""" - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } - """) == "@keyframes spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}") - } - - @Test func minifyCSSAnimation1() { - #expect(minify(""" - .fade_750ms { - animation: fadeEffect 0.75s; - } - """) == ".fade_750ms{animation:fadeEffect 0.75s;}") - } - - @Test func minifyCSSAnimation2() { - #expect(minify(""" - @keyframes slidein { - 0% { - translate: 0px 2.5vw; - opacity: 0; - } - 100% { - translate: 0px 0px; - opacity: 1; - } - } - """) == "@keyframes slidein{0%{translate:0px 2.5vw;opacity:0;}100%{translate:0px 0px;opacity:1;}}") - } - - @Test func minifyCSSCalc1() { - #expect(minify(""" - header { - height: calc(100vh - 40px); - } - """) == "header{height:calc(100vh - 40px);}") - } -} - -#endif \ No newline at end of file diff --git a/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift b/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift index ae484e4..ec1a310 100644 --- a/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift +++ b/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift @@ -14,7 +14,7 @@ import Testing struct DNASingleBlockEncodingTests { static let sequence:String = "TACTTGNCTAAAAGTACNATTGNCTAAGANTACACCGGCA" static let data:[UInt8] = [UInt8](sequence.utf8) - static let binary:[UInt8:[UInt8]] = CompressionTechnique.DNASingleBlockEncoding.compressBinary(data: data) + static let binary:[UInt8:[UInt8]] = DNASingleBlockEncoding.compressBinary(data: data) @Test func compressDNACSingleBlockEncodingPhase1() { #expect(Self.binary == [ @@ -30,7 +30,7 @@ struct DNASingleBlockEncodingTests { } @Test func compressDNACSingleBlockEncodingPhase2() { - let (result, controlBits):([UInt8], [UInt8]) = CompressionTechnique.DNASingleBlockEncoding.compressSBE(binaryData: Self.binary[65]!.prefix(7)) + let (result, controlBits):([UInt8], [UInt8]) = DNASingleBlockEncoding.compressSBE(binaryData: Self.binary[65]!.prefix(7)) #expect(result == [0, 1, 0]) #expect(controlBits == [0]) } diff --git a/Tests/JavaScriptTests/JavaScriptTests.swift b/Tests/JavaScriptTests/JavaScriptTests.swift deleted file mode 100644 index 2ef461a..0000000 --- a/Tests/JavaScriptTests/JavaScriptTests.swift +++ /dev/null @@ -1,217 +0,0 @@ - -#if compiler(>=6.0) - -import Testing -@testable import CompressionJavaScript -@testable import SwiftCompressionUtilities - -// MARK: JavaScriptTests -struct JavaScriptTests { -} - -// MARK: JavaScriptMinifyTests -struct JavaScriptMinifyTests { - private func minify(_ string: String) -> String { - return String(decoding: CompressionTechnique.javascript.minify(data: [UInt8](string.utf8)), as: UTF8.self) - } - - @Test func minifyJSVar() { - #expect(minify(""" - var i = 0; - var j = "easy clap"; - var k = false; - """) == """ - var i=0;var j="easy clap";var k=false; - """) - } - - @Test func minifyJSConst() { - #expect(minify(""" - const i = 0; - const j = "easy clap"; - const k = false; - """) == """ - const i=0;const j="easy clap";const k=false; - """) - } - - @Test func minifyJSForNumber() { - #expect(minify(""" - for (var i = 0; i < starting_time_elements.length - 1; i++) { - } - """) == """ - for(var i=0;i
Name " + division_name + " | Day of Week " + day_of_week_html + "
") - """) == """ - divisions_list_html+=("
Name "+division_name+" | Day of Week "+day_of_week_html+"
") - """) - } - @Test func minifyJSStringToken2() { - #expect(minify(""" - function get_cookie(name) { - return document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''; - } - """) == "function get_cookie(name){return document.cookie.match('(^|;)\\s*'+name+'\\s*=\\s*([^;]+)')?.pop()||'';}") - } - - @Test func minifyJSStringToken3() { - #expect(minify(""" - var string = ` - - - - League Schedule Diagnostics: %schedule_id% - - - - - - -

League Schedule Diagnostics: %schedule_id%

- `; - """) == """ - var string=` - - - - League Schedule Diagnostics: %schedule_id% - - - - - - -

League Schedule Diagnostics: %schedule_id%

- `; - """) - } - - @Test func minifyJSSwitchCaseString() { - #expect(minify(""" - switch (key) { - case "opponents": - break; - case "home_away": - break; - case "time_slots": - break; - case "matchup_slots": - break; - default: - break; - } - """) == """ - switch(key){case"opponents":break;case"home_away":break;case"time_slots":break;case"matchup_slots":break;default:break;} - """) - } - - @Test func minifyJSSwitchCaseNumber() { - #expect(minify(""" - switch (brackets.size) { - case 2: - bracket_names.push("Winners Bracket"); - bracket_names.push("Losers Bracket"); - break; - case 3: - bracket_names.push("Winners Bracket"); - bracket_names.push("Losers Bracket"); - bracket_names.push("Elimination Bracket"); - break; - default: - for (var i = 0; i < brackets.size; i++) { - bracket_names.push("Bracket " + (i + 1).toString()); - } - break; - } - """) == """ - switch(brackets.size){case 2:bracket_names.push("Winners Bracket");bracket_names.push("Losers Bracket");break;case 3:bracket_names.push("Winners Bracket");bracket_names.push("Losers Bracket");bracket_names.push("Elimination Bracket");break;default:for(var i=0;i = CompressionTechnique.lz77(windowSize: 10, bufferSize: 6) + static let lz77:LZ77 = CompressionTechnique.lz77(windowSize: 10, bufferSize: 6) static let compressed:[UInt8] = try! lz77.compress(data: [UInt8](string.utf8)).data @Test func compressLZ77() { diff --git a/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift b/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift index 7344f31..af70abc 100644 --- a/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift +++ b/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift @@ -2,37 +2,37 @@ #if compiler(>=6.0) import Testing -@testable import SwiftCompressionUtilities +@testable import SwiftCompression // MARK: Compress struct RunLengthEncodingTests { @Test func compressRLE() throws(CompressionError) { var data:[UInt8] = .init("AAAAABBBBBCCCCC".utf8) - var compressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + var compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data var expected_result:[UInt8] = [196, 65, 196, 66, 196, 67] #expect(compressed == expected_result) - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 5, alwaysIncludeRunCount: true).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 5, alwaysIncludeRunCount: true).compress(data: data).data #expect(compressed == expected_result) - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: true).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: true).compress(data: data).data #expect(compressed == expected_result) - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: false).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: false).compress(data: data).data expected_result = data #expect(compressed == expected_result) data = .init(String(repeating: "A", count: 66).utf8) - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data expected_result = [255, 65, 193, 65] #expect(compressed == expected_result) data = [190, 191, 192] - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: false).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: false).compress(data: data).data expected_result = [190, 191, 192, 192] #expect(compressed == expected_result) - compressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data expected_result = [192, 190, 192, 191, 192, 192] #expect(compressed == expected_result) } @@ -127,8 +127,8 @@ extension RunLengthEncodingTests { @Test func decompressRLE() throws { let string = "AAAAABBBBBCCCCC" let data:[UInt8] = .init(string.utf8) - let compressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data - let decompressed = try CompressionTechnique.RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).decompress(data: compressed) + let compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + let decompressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).decompress(data: compressed) #expect(decompressed == data) } } diff --git a/Tests/SwiftCompressionTests/SwiftCompressionTests.swift b/Tests/SwiftCompressionTests/SwiftCompressionTests.swift deleted file mode 100644 index 5998e0b..0000000 --- a/Tests/SwiftCompressionTests/SwiftCompressionTests.swift +++ /dev/null @@ -1,10 +0,0 @@ - -#if compiler(>=6.0) - -import Testing -@testable import SwiftCompression - -struct SwiftCompressionTests { -} - -#endif \ No newline at end of file diff --git a/Tests/SwiftCompressionTests/programmingLanguage/SwiftLangTests.swift b/Tests/SwiftCompressionTests/programmingLanguage/SwiftLangTests.swift deleted file mode 100644 index 8d3987a..0000000 --- a/Tests/SwiftCompressionTests/programmingLanguage/SwiftLangTests.swift +++ /dev/null @@ -1,34 +0,0 @@ - -#if compiler(>=6.0) - -import Testing -@testable import SwiftCompression -@testable import SwiftCompressionUtilities - -struct SwiftLangTests { - - @Test - func minifySwiftCode() throws { - let code = """ - // - // AnyCompressor.swift - // - // - // Created by Evan Anderson on 12/26/24. - // - - // MARK: AnyCompressor - public protocol AnyCompressor: Sendable { - /// Compression algorithm this compressor uses. - public var algorithm: CompressionAlgorithm { get } - - /// Quality of the compressed data. - var quality: CompressionQuality { get } - } - """ - let result = try CompressionTechnique.swift.minify(swiftSourceCode: code) - #expect(result == "public protocol AnyCompressor:Sendable{public var algorithm:CompressionAlgorithm{get};var quality:CompressionQuality{get}}") - } -} - -#endif \ No newline at end of file From 39d1138034ccfff66a0ca5467d40400caf7c7a3c Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 15:49:13 -0500 Subject: [PATCH 02/22] add deflate support --- Package.swift | 15 ++ Sources/LZ/deflate/DEFLATE+Compress.swift | 29 ---- Sources/LZ/deflate/DEFLATE+Decompress.swift | 10 -- Sources/LZ/deflate/DEFLATE.swift | 139 ------------------ Sources/Zlib/deflate/DEFLATE+Compress.swift | 34 +++++ Sources/Zlib/deflate/DEFLATE+Decompress.swift | 32 ++++ Sources/Zlib/deflate/DEFLATE.swift | 30 ++++ Sources/ZlibShim/module.modulemap | 5 + Tests/ZlibTests/DeflateTests.swift | 16 ++ 9 files changed, 132 insertions(+), 178 deletions(-) delete mode 100644 Sources/LZ/deflate/DEFLATE+Compress.swift delete mode 100644 Sources/LZ/deflate/DEFLATE+Decompress.swift delete mode 100644 Sources/LZ/deflate/DEFLATE.swift create mode 100644 Sources/Zlib/deflate/DEFLATE+Compress.swift create mode 100644 Sources/Zlib/deflate/DEFLATE+Decompress.swift create mode 100644 Sources/Zlib/deflate/DEFLATE.swift create mode 100644 Sources/ZlibShim/module.modulemap create mode 100644 Tests/ZlibTests/DeflateTests.swift diff --git a/Package.swift b/Package.swift index fd50978..6930890 100644 --- a/Package.swift +++ b/Package.swift @@ -43,6 +43,15 @@ let package = Package( ), // MARK: Techniques + .systemLibrary(name: "ZlibShim"), + .target( + name: "Zlib", + dependencies: [ + "SwiftCompressionUtilities", + "ZlibShim" + ] + ), + .target( name: "CompressionDNA", dependencies: [ @@ -89,6 +98,12 @@ let package = Package( .testTarget( name: "SnappyTests", dependencies: ["CompressionSnappy"] + ), + .testTarget( + name: "ZlibTests", + dependencies: [ + "Zlib" + ] ) ] ) diff --git a/Sources/LZ/deflate/DEFLATE+Compress.swift b/Sources/LZ/deflate/DEFLATE+Compress.swift deleted file mode 100644 index 152caab..0000000 --- a/Sources/LZ/deflate/DEFLATE+Compress.swift +++ /dev/null @@ -1,29 +0,0 @@ - -import SwiftCompressionUtilities - -extension Deflate { - public static func compress( - data: some Sequence, - flags: borrowing Flags = .init(flags: 0), - mtime: (UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0), - quality: borrowing Quality = .default, - os: borrowing FileSystem = .unknown - ) -> CompressionResult<[UInt8]>? { - var result = [UInt8]() - - // gzip header - result.append(contentsOf: [ - 0x1F, // ID1 - 0x8B, // ID2 - 0x08, // CM (compression method; deflate=8) - flags.bits, // FLG (flags) - mtime.0.littleEndian, // MTIME byte 1 (modification time) - mtime.1.littleEndian, // MTIME byte 2 (modification time) - mtime.2.littleEndian, // MTIME byte 3 (modification time) - mtime.3.littleEndian, // MTIME byte 4 (modification time) - quality.rawValue.littleEndian // XFL (extra flags) - ]) - // TODO: finish - return .init(data: result) - } -} \ No newline at end of file diff --git a/Sources/LZ/deflate/DEFLATE+Decompress.swift b/Sources/LZ/deflate/DEFLATE+Decompress.swift deleted file mode 100644 index 852c797..0000000 --- a/Sources/LZ/deflate/DEFLATE+Decompress.swift +++ /dev/null @@ -1,10 +0,0 @@ - -import SwiftCompressionUtilities - -extension Deflate { - public static func decompress( - data: [UInt8] - ) -> [UInt8] { - return data - } -} \ No newline at end of file diff --git a/Sources/LZ/deflate/DEFLATE.swift b/Sources/LZ/deflate/DEFLATE.swift deleted file mode 100644 index e7303cf..0000000 --- a/Sources/LZ/deflate/DEFLATE.swift +++ /dev/null @@ -1,139 +0,0 @@ - -import SwiftCompressionUtilities - -// https://en.wikipedia.org/wiki/Gzip -// https://www.rfc-editor.org/rfc/rfc1952#page-5 - -/// The Deflate compression technique. -/// -/// https://en.wikipedia.org/wiki/Deflate -public enum Deflate { - struct Block: Sendable { - let head:Head - } -} - -// MARK: Head -extension Deflate { - struct Head: Sendable { - let bits:UInt8 - - var isLastBlockInStream: Bool { - bits & 1 > 0 - } - - var encoding: Encoding { - switch (bits & 0b00000011) { - case 0: .stored - case 1: .staticHuffman - case 2: .dynamicHuffman - default: .reserved - } - } - } -} - -// MARK: Encoding -extension Deflate { - public enum Encoding: ~Copyable, Sendable { - /// A stored (a.k.a. raw or literal) section, between 0 and 65,535 bytes in length - case stored - - /// A _static Huffman_ compressed block, using a pre-agreed [Huffman tree](https://en.wikipedia.org/wiki/Huffman_coding) defined in the RFC - case staticHuffman - - /// A _dynamic Huffman_ compressed block, complete with the Huffman table supplied - case dynamicHuffman - - /// Reserved: don't use - case reserved - } -} - -// MARK: Flags -extension Deflate { - public struct Flags: ~Copyable, Sendable { - let bits:UInt8 - - public init( - FTEXT: Bool, - FHCRC: Bool, - FEXTRA: Bool, - FNAME: Bool, - FCOMMENT: Bool - ) { - bits = (FTEXT ? 1 : 0) - | (FHCRC ? 2 : 0) - | (FEXTRA ? 4 : 0) - | (FNAME ? 8 : 0) - | (FCOMMENT ? 16 : 0) - } - - /// - Warning: Make sure `flags` is in little-endian! - public init(flags: UInt8) { - self.bits = flags - } - } -} - -// MARK: Quality -extension Deflate { - public enum Quality: ~Copyable, Sendable { - case `default` - - /// Best compression (level 9) - case best - - /// Fastest compression (level 1) - case fastest - - var rawValue: UInt8 { - switch self { - case .default: 0 - case .best: 2 - case .fastest: 4 - } - } - } -} - -// MARK: FileSystem -extension Deflate { - public enum FileSystem: ~Copyable, Sendable { - case fat - case amiga - case openVMS - case unix - case vmCMS - case atariTOS - case hpfs - case macintosh - case zsystem - case cpm - case tops20 - case ntfs - case qdos - case acornRISCOS - case unknown - - var rawValue: UInt8 { - switch self { - case .fat: 0 - case .amiga: 1 - case .openVMS: 2 - case .unix: 3 - case .vmCMS: 4 - case .atariTOS: 5 - case .hpfs: 6 - case .macintosh: 7 - case .zsystem: 8 - case .cpm: 9 - case .tops20: 10 - case .ntfs: 11 - case .qdos: 12 - case .acornRISCOS: 13 - case .unknown: 255 - } - } - } -} \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift new file mode 100644 index 0000000..3333cad --- /dev/null +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -0,0 +1,34 @@ + +import SwiftCompressionUtilities +import ZlibShim + +extension Deflate { + public func compress( + data: some Collection, + reserveCapacity: Int = 1024 + ) -> CompressionResult<[UInt8]>? { + var stream = z_stream() + let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) + guard status == Z_OK else { return nil } + defer { deflateEnd(&stream) } + + var compressed = [UInt8]() + compressed.reserveCapacity(reserveCapacity) + var buffer = [UInt8](repeating: 0, count: bufferSize) + data.withContiguousStorageIfAvailable { rawBuffer in + stream.next_in = UnsafeMutablePointer(mutating: rawBuffer.baseAddress) + stream.avail_in = UInt32(data.count) + buffer.withUnsafeMutableBufferPointer { b in + while stream.avail_out == 0 { + stream.next_out = b.baseAddress + stream.avail_out = UInt32(bufferSize) + deflate(&stream, Z_FINISH) + + let count = bufferSize - Int(stream.avail_out) + compressed.append(contentsOf: b[0.. + ) -> [UInt8]? { + var stream = z_stream() + let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) + guard status == Z_OK else { return nil } + defer { inflateEnd(&stream) } + + var decompressedData = [UInt8]() + var buffer = [UInt8](repeating: 0, count: bufferSize) + data.withContiguousStorageIfAvailable { rawBuffer in + stream.next_in = UnsafeMutablePointer(mutating: rawBuffer.baseAddress) + stream.avail_in = UInt32(data.count) + + buffer.withUnsafeMutableBufferPointer { b in + while stream.avail_out == 0 { + stream.next_out = b.baseAddress + stream.avail_out = UInt32(bufferSize) + inflate(&stream, Z_NO_FLUSH) + + let count = bufferSize - Int(stream.avail_out) + decompressedData.append(contentsOf: b[0.. Date: Tue, 7 Apr 2026 16:30:02 -0500 Subject: [PATCH 03/22] refactor compressor/decompressor protocols --- .../binary/DNABinaryEncoding+Compress.swift | 24 ++++++- .../binary/DNABinaryEncoding+Decompress.swift | 22 +++++- Sources/DNA/binary/DNABinaryEncoding.swift | 2 +- .../DNASingleBlockEncoding+Compress.swift | 38 ++++------ .../DNASingleBlockEncoding+Decompress.swift | 19 +++-- .../singleBlock/DNASingleBlockEncoding.swift | 5 +- Sources/LZ/brotli/Brotli.swift | 2 +- Sources/LZ/lz77/LZ77+Compress.swift | 37 ++++++++-- Sources/LZ/lz77/LZ77+Decompress.swift | 21 +++++- Sources/LZ/lz77/LZ77.swift | 2 +- Sources/Snappy/IWA.swift | 51 -------------- Sources/Snappy/Snappy.swift | 2 +- Sources/Snappy/framed/SnappyFramed.swift | 2 +- Sources/Snappy/iwa/IWA+Compress.swift | 15 ++++ Sources/Snappy/iwa/IWA+Decompress.swift | 16 +++++ Sources/Snappy/iwa/IWA.swift | 37 ++++++++++ .../SwiftCompression/SwiftCompression.swift | 9 ++- .../RunLengthEncoding+Compress.swift | 32 +++++++-- .../RunLengthEncoding+Decompress.swift | 22 +++++- .../runLength/RunLengthEncoding.swift | 2 +- .../CompressionQuality.swift | 2 +- .../CompressionTechnique.swift | 2 +- .../Compressor.swift | 69 ++----------------- .../Decompressor.swift | 54 ++------------- .../SwiftCompressionUtilities.swift | 7 +- Sources/Zlib/deflate/DEFLATE+Compress.swift | 26 +++++-- Sources/Zlib/deflate/DEFLATE+Decompress.swift | 26 +++++-- Tests/DNATests/DNABinaryEncodingTests.swift | 10 +-- .../DNASingleBlockEncodingTestsTests.swift | 6 +- Tests/LZTests/LZ77Tests.swift | 4 +- .../RunLengthEncodingTests.swift | 20 +++--- Tests/ZlibTests/DeflateTests.swift | 4 +- 32 files changed, 340 insertions(+), 250 deletions(-) delete mode 100644 Sources/Snappy/IWA.swift create mode 100644 Sources/Snappy/iwa/IWA+Compress.swift create mode 100644 Sources/Snappy/iwa/IWA+Decompress.swift create mode 100644 Sources/Snappy/iwa/IWA.swift diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift index 8f962c1..399c114 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -1,7 +1,19 @@ import SwiftCompressionUtilities -extension DNABinaryEncoding { +extension DNABinaryEncoding: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8] + + public func compress( + data: some Collection, + configuration: CompressionConfiguration + ) -> CompressionResult { + var result = CompressionResult() + let validBitsInLastByte = compress(data: data, closure: { result.append($0) }) + return result + } + /// Compress a collection of bytes using the DNA binary encoding technique. /// /// - Parameters: @@ -10,7 +22,7 @@ extension DNABinaryEncoding { /// - closure: Logic to execute when a byte was encoded. /// - Returns: Valid bits for the last byte, if necessary. /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress( + private func compress( data: some Collection, closure: (UInt8) -> Void ) -> UInt8? { @@ -28,4 +40,12 @@ extension DNABinaryEncoding { closure(byte) return validBits } +} + +// MARK: Configuration +extension DNABinaryEncoding { + public struct CompressConfiguration: Sendable { + public init() { + } + } } \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift index 4ab0644..42178ad 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift @@ -1,5 +1,10 @@ -extension DNABinaryEncoding { +import SwiftCompressionUtilities + +extension DNABinaryEncoding: Decompressor { + public typealias DecompressionConfiguration = CompressConfiguration + public typealias DecompressionResult = [UInt8] + /// Decompress a collection of bytes using the DNA binary encoding technique. /// /// - Parameters: @@ -7,6 +12,21 @@ extension DNABinaryEncoding { /// - closure: Logic to execute when a given base nucleotide is found. /// - Complexity: O(_n_) where _n_ is the length of `data`. public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { + var result = DecompressionResult() + decompress(data: data, closure: { result.append($0) }) + return result + } + + /// Decompress a collection of bytes using the DNA binary encoding technique. + /// + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a given base nucleotide is found. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + private func decompress( data: some Collection, closure: (UInt8) -> Void ) { diff --git a/Sources/DNA/binary/DNABinaryEncoding.swift b/Sources/DNA/binary/DNABinaryEncoding.swift index 3f7dca7..d3cf371 100644 --- a/Sources/DNA/binary/DNABinaryEncoding.swift +++ b/Sources/DNA/binary/DNABinaryEncoding.swift @@ -2,7 +2,7 @@ import SwiftCompressionUtilities /// The DNA binary encoding compression technique. -public struct DNABinaryEncoding: Compressor, Decompressor { +public struct DNABinaryEncoding: Sendable { public let baseBits:[UInt8:[Bool]] public init(baseBits: [UInt8:[Bool]] = [ diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift index b90f7ec..6cbbbba 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift @@ -1,26 +1,9 @@ import SwiftCompressionUtilities -extension DNASingleBlockEncoding { - public func compress(data: some Collection, closure: (UInt8) -> Void) throws(CompressionError) -> UInt8? { // TODO: fix - return nil - } - - /// Compress a sequence of bytes using the DNA single block encoding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_ + (_m_ log _m_)) where _n_ is the length of `data` and _m_ is the number of unique bytes in `data`. - public static func compress( - data: some Collection, - reserveCapacity: Int - ) -> CompressionResult<[UInt8]>? { // TODO: finish - let results = compressBinary(data: data) - for (base, _) in results { - print("base=\(Character(Unicode.Scalar(base)));result=\(results[base]!.debugDescription)") - } - return nil - } +extension DNASingleBlockEncoding: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8:[UInt8]] } extension DNASingleBlockEncoding { @@ -29,9 +12,10 @@ extension DNASingleBlockEncoding { /// - Parameters: /// - data: Sequence of bytes to compress. /// - Complexity: O(_n_ + (_m_ log _m_)) where _n_ is the length of `data` and _m_ is the number of unique bytes in `data`. - static func compressBinary( - data: some Sequence - ) -> [UInt8:[UInt8]] { + public func compress( + data: some Sequence, + configuration: CompressConfiguration + ) -> CompressionResult { let frequencyTable:[UInt8:Int] = CompressionTechnique.buildFrequencyTable(data: data) var sortedFrequencyTable = frequencyTable.sorted(by: { guard $0.value != $1.value else { return $0.key < $1.key } @@ -131,4 +115,12 @@ extension DNASingleBlockEncoding { } return (compressed, controlBits) } +} + +// MARK: Configuration +extension DNASingleBlockEncoding { + public struct CompressConfiguration: Sendable { + public init() { + } + } } \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift index 26b4ce6..f405f94 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift @@ -1,15 +1,26 @@ import SwiftCompressionUtilities -extension DNASingleBlockEncoding { // TODO: finish - public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { - } +extension DNASingleBlockEncoding: Decompressor { // TODO: finish + public typealias DecompressionConfiguration = DecompressConfiguration + public typealias DecompressionResult = [UInt8] /// Decompress a sequence of bytes using the DNA single block encoding technique. /// /// - Parameters: /// - data: Sequence of bytes to decompress. - public static func decompress(data: [UInt8]) -> [UInt8] { + public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { return [] } +} + +// MARK: Configuration +extension DNASingleBlockEncoding { + public struct DecompressConfiguration: Sendable { + public init() { + } + } } \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift index 62a0418..d8bb853 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift @@ -4,7 +4,7 @@ import SwiftCompressionUtilities /// The DNA single block encoding compression technique. /// /// https://www.mdpi.com/1999-4893/13/4/99 -public struct DNASingleBlockEncoding: Compressor, Decompressor { +public struct DNASingleBlockEncoding: Sendable { public var algorithm: CompressionAlgorithm { .dnaSingleBlockEncoding } @@ -12,6 +12,9 @@ public struct DNASingleBlockEncoding: Compressor, Decompressor { public var quality: CompressionQuality { .lossless } + + public init() { + } } extension CompressionTechnique { diff --git a/Sources/LZ/brotli/Brotli.swift b/Sources/LZ/brotli/Brotli.swift index 458fa84..a07d640 100644 --- a/Sources/LZ/brotli/Brotli.swift +++ b/Sources/LZ/brotli/Brotli.swift @@ -1,7 +1,7 @@ import SwiftCompressionUtilities -public struct Brotli: Compressor, Decompressor { // TODO: finish +public struct Brotli: Sendable { // TODO: finish /// Size of the window. public let windowSize:Int diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift index 7842609..5293cc4 100644 --- a/Sources/LZ/lz77/LZ77+Compress.swift +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -1,17 +1,36 @@ import SwiftCompressionUtilities -extension LZ77 { - /// Compress a collection of bytes using the LZ77 technique. +extension LZ77: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8] + + // Compress a collection of bytes using the LZ77 technique. /// /// - Parameters: /// - data: Collection of bytes to compress. /// - closure: Logic to execute when a byte is compressed. /// - Complexity: O(_n_) where _n_ is the length of `data`. public func compress( + data: some Collection, + configuration: CompressionConfiguration + ) -> CompressionResult { + var result = CompressionResult() + result.reserveCapacity(configuration.reserveCapacity) + compress(data: data, closure: { result.append($0) }) + return result + } + + /// Compress a collection of bytes using the LZ77 technique. + /// + /// - Parameters: + /// - data: Collection of bytes to compress. + /// - closure: Logic to execute when a byte is compressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + private func compress( data: some Collection, closure: (UInt8) -> Void - ) -> UInt8? { + ) { let count = data.count var index = 0 while index < count { @@ -51,6 +70,16 @@ extension LZ77 { closure(byte) index += bestLength + 1 } - return nil + } +} + +// MARK: Configuration +extension LZ77 { + public struct CompressConfiguration: Sendable { + public let reserveCapacity:Int + + public init(reserveCapacity: Int) { + self.reserveCapacity = reserveCapacity + } } } \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77+Decompress.swift b/Sources/LZ/lz77/LZ77+Decompress.swift index 06a5a24..c81b447 100644 --- a/Sources/LZ/lz77/LZ77+Decompress.swift +++ b/Sources/LZ/lz77/LZ77+Decompress.swift @@ -1,7 +1,10 @@ import SwiftCompressionUtilities -extension LZ77 { +extension LZ77: Decompressor { + public typealias DecompressionConfiguration = CompressionConfiguration + public typealias DecompressionResult = [UInt8] + /// Decompress a collection of bytes using the LZ77 technique. /// /// - Parameters: @@ -9,6 +12,22 @@ extension LZ77 { /// - closure: Logic to execute when a byte was decompressed. /// - Complexity: O(_n_) where _n_ is the length of `data`. public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { + var result = DecompressionResult() + decompress(data: data, closure: { result.append($0) }) + result.reserveCapacity(configuration.reserveCapacity) + return result + } + + /// Decompress a collection of bytes using the LZ77 technique. + /// + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte was decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + private func decompress( data: some Collection, closure: (UInt8) -> Void ) { diff --git a/Sources/LZ/lz77/LZ77.swift b/Sources/LZ/lz77/LZ77.swift index 02f9dbc..c2ea44f 100644 --- a/Sources/LZ/lz77/LZ77.swift +++ b/Sources/LZ/lz77/LZ77.swift @@ -1,7 +1,7 @@ import SwiftCompressionUtilities -public struct LZ77: Compressor, Decompressor { +public struct LZ77: Sendable { /// Size of the window. public let windowSize:Int diff --git a/Sources/Snappy/IWA.swift b/Sources/Snappy/IWA.swift deleted file mode 100644 index 65c0a2f..0000000 --- a/Sources/Snappy/IWA.swift +++ /dev/null @@ -1,51 +0,0 @@ - -import SwiftCompressionUtilities - -extension CompressionTechnique { - /// The iWork Archive (iwa) compression technique. - /// - /// https://en.wikipedia.org/wiki/IWork - public static func iwa(version: IWAVersion) -> IWA { - return IWA(version: version) - } - - public struct IWA: Compressor, Decompressor { - /// Version of the iWork Archive to use. - public let version:IWAVersion - - public init(version: IWAVersion) { - self.version = version - } - - public var algorithm: CompressionAlgorithm { - .iwa(version: version) - } - - public var quality: CompressionQuality { - .lossless - } - } -} - -// MARK: Compress -extension CompressionTechnique.IWA { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - return nil - } -} - -// MARK: Decompress -extension CompressionTechnique.IWA { // TODO: finish - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - } -} \ No newline at end of file diff --git a/Sources/Snappy/Snappy.swift b/Sources/Snappy/Snappy.swift index 06b6230..92e9721 100644 --- a/Sources/Snappy/Snappy.swift +++ b/Sources/Snappy/Snappy.swift @@ -1,7 +1,7 @@ import SwiftCompressionUtilities -public struct Snappy: Compressor, Decompressor { +public struct Snappy: Sendable { /// Size of the window. public let windowSize:Int diff --git a/Sources/Snappy/framed/SnappyFramed.swift b/Sources/Snappy/framed/SnappyFramed.swift index b6a335c..5de4ab6 100644 --- a/Sources/Snappy/framed/SnappyFramed.swift +++ b/Sources/Snappy/framed/SnappyFramed.swift @@ -1,7 +1,7 @@ import SwiftCompressionUtilities -public struct SnappyFramed: Compressor, Decompressor { +public struct SnappyFramed: Sendable { public var algorithm: CompressionAlgorithm { .snappyFramed } diff --git a/Sources/Snappy/iwa/IWA+Compress.swift b/Sources/Snappy/iwa/IWA+Compress.swift new file mode 100644 index 0000000..3e21a85 --- /dev/null +++ b/Sources/Snappy/iwa/IWA+Compress.swift @@ -0,0 +1,15 @@ + +extension IWA { // TODO: finish + public typealias CompressionConfiguration = Configuration + public typealias CompressionResult = [UInt8] + + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress( + data: some Collection, + configuration: CompressionConfiguration + ) -> CompressionResult { + return .init() + } +} \ No newline at end of file diff --git a/Sources/Snappy/iwa/IWA+Decompress.swift b/Sources/Snappy/iwa/IWA+Decompress.swift new file mode 100644 index 0000000..44cbaeb --- /dev/null +++ b/Sources/Snappy/iwa/IWA+Decompress.swift @@ -0,0 +1,16 @@ + +extension IWA { // TODO: finish + public typealias DecompressionConfiguration = Configuration + public typealias DecompressionResult = [UInt8] + + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte is decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { + return .init() + } +} \ No newline at end of file diff --git a/Sources/Snappy/iwa/IWA.swift b/Sources/Snappy/iwa/IWA.swift new file mode 100644 index 0000000..2b79666 --- /dev/null +++ b/Sources/Snappy/iwa/IWA.swift @@ -0,0 +1,37 @@ + +import SwiftCompressionUtilities + +/// iWork Archive compressor/decompressor. +public struct IWA: Compressor, Decompressor { + /// Version of the iWork Archive to use. + public let version:IWAVersion + + public init(version: IWAVersion) { + self.version = version + } + + public var algorithm: CompressionAlgorithm { + .iwa(version: version) + } + + public var quality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The iWork Archive (iwa) compression technique. + /// + /// https://en.wikipedia.org/wiki/IWork + public static func iwa(version: IWAVersion) -> IWA { + return IWA(version: version) + } +} + +// MARK: Configuration +extension IWA { + public struct Configuration: Sendable { + public init() { + } + } +} \ No newline at end of file diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index 5ec2376..42da654 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -15,7 +15,8 @@ extension CompressionAlgorithm { case .arithmetic: return nil case .brotli(let windowSize): - return CompressionTechnique.brotli(windowSize: windowSize) + //return CompressionTechnique.brotli(windowSize: windowSize) + return nil case .bwt: return nil case .deflate: return nil case .huffmanCoding: return nil @@ -45,9 +46,11 @@ extension CompressionAlgorithm { case .runLengthEncoding(let minRun, let alwaysIncludeRunCount): return CompressionTechnique.runLength(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) case .snappy(let windowSize): - return CompressionTechnique.snappy(windowSize: windowSize) + //return CompressionTechnique.snappy(windowSize: windowSize) + return nil case .snappyFramed: - return CompressionTechnique.snappyFramed + //return CompressionTechnique.snappyFramed + return nil case .zstd: return nil case ._7z: return nil diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift index d43d66f..9d73ad0 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift @@ -1,6 +1,20 @@ -extension RunLengthEncoding { - public func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { +import SwiftCompressionUtilities + +extension RunLengthEncoding: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8] + + public func compress( + data: some Collection, + configuration: CompressionConfiguration + ) -> CompressionResult { + var result = CompressionResult() + compress(data: data, closure: compressClosure(closure: { result.append($0) })) + return result + } + + private func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { if alwaysIncludeRunCount { return { (arg) in let (run, runByte) = arg @@ -27,7 +41,10 @@ extension RunLengthEncoding { /// - minRun: Minimum run count required to compress identical sequential bytes. /// - closure: Logic to execute for a run. /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Sequence, closure: (CompressClosureParameters) -> Void) -> UInt8? { + private func compress( + data: some Sequence, + closure: (CompressClosureParameters) -> Void + ) { var run = 0 var runByte:UInt8? = nil data.withContiguousStorageIfAvailable { p in @@ -52,6 +69,13 @@ extension RunLengthEncoding { if let runByte { closure((run, runByte)) } - return nil + } +} + +// MARK: Configuration +extension RunLengthEncoding { + public struct CompressConfiguration: Sendable { + public init() { + } } } \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift index 01f1876..6db11fe 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift @@ -1,10 +1,28 @@ -extension RunLengthEncoding { +import SwiftCompressionUtilities + +extension RunLengthEncoding: Decompressor { + public typealias DecompressionConfiguration = CompressConfiguration + public typealias DecompressionResult = [UInt8] + + /// - Parameters: + /// - data: Sequence of bytes to decompress. + /// - closure: Logic to execute for a run. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { + var result = DecompressionResult() + decompress(data: data, closure: { result.append($0) }) + return result + } + /// - Parameters: /// - data: Sequence of bytes to decompress. /// - closure: Logic to execute for a run. /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress(data: some Collection, closure: (UInt8) -> Void) { + private func decompress(data: some Collection, closure: (UInt8) -> Void) { let count = data.count var index = 0 var run:UInt8 = 0 diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift index f473ec8..506e7b7 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift @@ -4,7 +4,7 @@ import SwiftCompressionUtilities /// The Run-length encoding compression technique. /// /// https://en.wikipedia.org/wiki/Run-length_encoding -public struct RunLengthEncoding: Compressor, Decompressor { +public struct RunLengthEncoding: Sendable { public typealias CompressClosureParameters = (run: Int, byte: UInt8) /// Minimum run count required to compress identical sequential bytes. diff --git a/Sources/SwiftCompressionUtilities/CompressionQuality.swift b/Sources/SwiftCompressionUtilities/CompressionQuality.swift index 588fb8c..79028fe 100644 --- a/Sources/SwiftCompressionUtilities/CompressionQuality.swift +++ b/Sources/SwiftCompressionUtilities/CompressionQuality.swift @@ -2,7 +2,7 @@ /// Quality of the compressed data a compression algorithm uses. /// /// https://en.wikipedia.org/wiki/Data_compression -public enum CompressionQuality { +public enum CompressionQuality: Sendable { /// Data is compressed/decompressed without losing any information. case lossless diff --git a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift index 113d9df..d6f5628 100644 --- a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift +++ b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift @@ -1,7 +1,7 @@ // MARK: CompressionTechnique /// Collection of well-known and useful compression and decompression technique implementations. -public enum CompressionTechnique { +public enum CompressionTechnique: Sendable { } // MARK: Frequency tables diff --git a/Sources/SwiftCompressionUtilities/Compressor.swift b/Sources/SwiftCompressionUtilities/Compressor.swift index 63c0c92..51aa4db 100644 --- a/Sources/SwiftCompressionUtilities/Compressor.swift +++ b/Sources/SwiftCompressionUtilities/Compressor.swift @@ -1,71 +1,12 @@ // MARK: Compressor public protocol Compressor: AnyCompressor, ~Copyable { - associatedtype CompressClosureParameters - func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void + associatedtype CompressionConfiguration + associatedtype CompressionResult + associatedtype CompressionErrorType:Error = CompressionError func compress( data: some Collection, - reserveCapacity: Int - ) throws -> CompressionResult<[UInt8]> - - /// Compress a collection of bytes using this technique. - /// - /// - Parameters: - /// - data: Collection of bytes to compress. - /// - closure: Logic to execute when a byte is compressed. - /// - Returns: The number of valid bits in the last byte. - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Snappy: O(_n_) - func compress( - data: some Collection, - closure: (CompressClosureParameters) -> Void - ) throws(CompressionError) -> UInt8? -} - -// MARK: Compress -extension Compressor { - /// Compress a collection of bytes using this technique. - /// - /// - Parameters: - /// - data: Collection of bytes to compress. - /// - reserveCapacity: Space to reserve for the compressed result. - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Snappy: O(_n_) - public func compress( - data: some Collection, - reserveCapacity: Int = 1024 - ) throws(CompressionError) -> CompressionResult<[UInt8]> { - var compressed = [UInt8]() - compressed.reserveCapacity(reserveCapacity) - let validBitsInLastByte:UInt8 = try compress(data: data, closure: compressClosure { compressed.append($0) }) ?? 8 // TODO: fix Swift 6 error - return CompressionResult(data: compressed, validBitsInLastByte: validBitsInLastByte) - } - - /// Compress a collection of bytes into a stream using this technique. - /// - /// - Parameters: - /// - data: Collection of bytes to compress. - /// - continuation: The `AsyncStream.Continuation`. - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Snappy: O(_n_) - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func compress( - data: some Collection, - continuation: AsyncStream.Continuation - ) throws { - // TODO: finish - let _:UInt8 = try compress(data: data, closure: compressClosure { continuation.yield($0) }) ?? 8 - } -} -extension Compressor where CompressClosureParameters == UInt8 { - public func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { - closure - } + configuration: CompressionConfiguration + ) throws(CompressionErrorType) -> CompressionResult } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/Decompressor.swift b/Sources/SwiftCompressionUtilities/Decompressor.swift index ed131ba..68eeac3 100644 --- a/Sources/SwiftCompressionUtilities/Decompressor.swift +++ b/Sources/SwiftCompressionUtilities/Decompressor.swift @@ -1,61 +1,15 @@ // MARK: Decompressor public protocol Decompressor: AnyDecompressor, ~Copyable { - associatedtype DecompressClosureParameters + associatedtype DecompressionConfiguration + associatedtype DecompressionResult /// Decompress a collection of bytes using this technique. /// /// - Parameters: /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Run-length encoding: O(_n_) - /// - Snappy: O(_n_) func decompress( data: some Collection, - closure: (DecompressClosureParameters) -> Void - ) throws(DecompressionError) -} - -// MARK: Decompress -extension Decompressor where DecompressClosureParameters == UInt8 { - /// Decompress a collection of bytes using this technique. - /// - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - reserveCapacity: Space to reserve for the decompressed result (if no length was decompressed). - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Run-length encoding: O(_n_) - /// - Snappy: O(_n_) - public func decompress( - data: some Collection, - reserveCapacity: Int = 1024 - ) throws(DecompressionError) -> [UInt8] { - var decompressed = [UInt8]() - decompressed.reserveCapacity(reserveCapacity) - try decompress(data: data) { decompressed.append($0) } - return decompressed - } - - /// Decompress a collection of bytes into a stream using this technique. - /// - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - continuation: The `AsyncThrowingStream.Continuation`. - /// - Complexity: where _n_ is the length of `data` - /// - DNA binary encoding: O(_n_) - /// - LZ77: O(_n_) - /// - Run-length encoding: O(_n_) - /// - Snappy: O(_n_) - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func decompress( - data: some Collection, - continuation: AsyncThrowingStream.Continuation - ) throws(DecompressionError) { - try decompress(data: data) { continuation.yield($0) } - } + configuration: DecompressionConfiguration + ) throws(DecompressionError) -> DecompressionResult } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/SwiftCompressionUtilities.swift b/Sources/SwiftCompressionUtilities/SwiftCompressionUtilities.swift index c7834ec..745a33b 100644 --- a/Sources/SwiftCompressionUtilities/SwiftCompressionUtilities.swift +++ b/Sources/SwiftCompressionUtilities/SwiftCompressionUtilities.swift @@ -1,4 +1,7 @@ + +/* + // MARK: Collection extension Collection where Element == UInt8 { /// Compress a copy of this data using the specified technique(s). @@ -133,4 +136,6 @@ extension String { self = try String(data: Data(data.compressed(using: technique).data), encoding: encoding) ?? "" } } -#endif \ No newline at end of file +#endif + +*/ \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift index 3333cad..7debe86 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -2,18 +2,21 @@ import SwiftCompressionUtilities import ZlibShim -extension Deflate { +extension Deflate: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8]? + public func compress( data: some Collection, - reserveCapacity: Int = 1024 - ) -> CompressionResult<[UInt8]>? { + configuration: CompressionConfiguration + ) -> CompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } defer { deflateEnd(&stream) } var compressed = [UInt8]() - compressed.reserveCapacity(reserveCapacity) + compressed.reserveCapacity(configuration.reserveCapacity) var buffer = [UInt8](repeating: 0, count: bufferSize) data.withContiguousStorageIfAvailable { rawBuffer in stream.next_in = UnsafeMutablePointer(mutating: rawBuffer.baseAddress) @@ -29,6 +32,19 @@ extension Deflate { } } } - return .init(data: compressed) + return compressed + } +} + +// MARK: Configuration +extension Deflate { + public struct CompressConfiguration: Sendable { + public let reserveCapacity:Int + + public init( + reserveCapacity: Int + ) { + self.reserveCapacity = reserveCapacity + } } } \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress.swift b/Sources/Zlib/deflate/DEFLATE+Decompress.swift index cc18a94..6341399 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress.swift @@ -1,10 +1,15 @@ +import SwiftCompressionUtilities import ZlibShim -extension Deflate { - func decompress( - data: some Collection - ) -> [UInt8]? { +extension Deflate: Decompressor { + public typealias DecompressionConfiguration = DecompressConfiguration + public typealias DecompressionResult = [UInt8]? + + public func decompress( + data: some Collection, + configuration: DecompressionConfiguration + ) -> DecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } @@ -29,4 +34,17 @@ extension Deflate { } return decompressedData } +} + +// MARK: Configuration +extension Deflate { + public struct DecompressConfiguration: Sendable { + public let reserveCapacity:Int + + public init( + reserveCapacity: Int + ) { + self.reserveCapacity = reserveCapacity + } + } } \ No newline at end of file diff --git a/Tests/DNATests/DNABinaryEncodingTests.swift b/Tests/DNATests/DNABinaryEncodingTests.swift index bbfd678..e78ce81 100644 --- a/Tests/DNATests/DNABinaryEncodingTests.swift +++ b/Tests/DNATests/DNABinaryEncodingTests.swift @@ -6,16 +6,16 @@ import Testing @testable import SwiftCompressionUtilities struct DNABinaryEncodingTests { - static let sequence:String = "TACTTGCTAAAAGTACATTGCTAAGATACACCGGCA" - static let data:[UInt8] = [UInt8](sequence.utf8) - static let compressed:CompressionResult = try! CompressionTechnique.dnaBinaryEncoding().compress(data: data) + static let sequence = "TACTTGCTAAAAGTACATTGCTAAGATACACCGGCA" + static let data = [UInt8](sequence.utf8) + static let compressed = CompressionTechnique.dnaBinaryEncoding().compress(data: data, configuration: .init()) @Test func compressDNABinaryEncoding() { - #expect(Self.compressed.data == [199, 231, 0, 177, 62, 112, 140, 69, 164]) + #expect(Self.compressed == [199, 231, 0, 177, 62, 112, 140, 69, 164]) } @Test func decompressDNABinaryEncoding() throws(DecompressionError) { - let result:[UInt8] = try CompressionTechnique.dnaBinaryEncoding().decompress(data: Self.compressed.data) + let result = CompressionTechnique.dnaBinaryEncoding().decompress(data: Self.compressed, configuration: .init()) #expect(result == Self.data) } } diff --git a/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift b/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift index ec1a310..dd45699 100644 --- a/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift +++ b/Tests/DNATests/DNASingleBlockEncodingTestsTests.swift @@ -12,9 +12,9 @@ import Testing @testable import SwiftCompressionUtilities struct DNASingleBlockEncodingTests { - static let sequence:String = "TACTTGNCTAAAAGTACNATTGNCTAAGANTACACCGGCA" - static let data:[UInt8] = [UInt8](sequence.utf8) - static let binary:[UInt8:[UInt8]] = DNASingleBlockEncoding.compressBinary(data: data) + static let sequence = "TACTTGNCTAAAAGTACNATTGNCTAAGANTACACCGGCA" + static let data = [UInt8](sequence.utf8) + static let binary = DNASingleBlockEncoding().compress(data: data, configuration: .init()) @Test func compressDNACSingleBlockEncodingPhase1() { #expect(Self.binary == [ diff --git a/Tests/LZTests/LZ77Tests.swift b/Tests/LZTests/LZ77Tests.swift index 6f1543f..8438485 100644 --- a/Tests/LZTests/LZ77Tests.swift +++ b/Tests/LZTests/LZ77Tests.swift @@ -14,7 +14,7 @@ import Testing struct LZ77Tests { static let string:String = "abracadabra abracadabra" static let lz77:LZ77 = CompressionTechnique.lz77(windowSize: 10, bufferSize: 6) - static let compressed:[UInt8] = try! lz77.compress(data: [UInt8](string.utf8)).data + static let compressed = lz77.compress(data: [UInt8](string.utf8), configuration: .init(reserveCapacity: 1024)) @Test func compressLZ77() { #expect(Self.compressed == [ @@ -22,7 +22,7 @@ struct LZ77Tests { ]) } @Test func decompressLZ77() throws(DecompressionError) { - let result:[UInt8] = try Self.lz77.decompress(data: Self.compressed) + let result = Self.lz77.decompress(data: Self.compressed, configuration: .init(reserveCapacity: 1024)) #expect(result == [UInt8](Self.string.utf8)) } } diff --git a/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift b/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift index af70abc..3acd9c0 100644 --- a/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift +++ b/Tests/SwiftCompressionTests/RunLengthEncodingTests.swift @@ -6,33 +6,33 @@ import Testing // MARK: Compress struct RunLengthEncodingTests { - @Test func compressRLE() throws(CompressionError) { + @Test func compressRLE() { var data:[UInt8] = .init("AAAAABBBBBCCCCC".utf8) - var compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + var compressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) var expected_result:[UInt8] = [196, 65, 196, 66, 196, 67] #expect(compressed == expected_result) - compressed = try RunLengthEncoding(minRun: 5, alwaysIncludeRunCount: true).compress(data: data).data + compressed = RunLengthEncoding(minRun: 5, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) #expect(compressed == expected_result) - compressed = try RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: true).compress(data: data).data + compressed = RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) #expect(compressed == expected_result) - compressed = try RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: false).compress(data: data).data + compressed = RunLengthEncoding(minRun: 6, alwaysIncludeRunCount: false).compress(data: data, configuration: .init()) expected_result = data #expect(compressed == expected_result) data = .init(String(repeating: "A", count: 66).utf8) - compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + compressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) expected_result = [255, 65, 193, 65] #expect(compressed == expected_result) data = [190, 191, 192] - compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: false).compress(data: data).data + compressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: false).compress(data: data, configuration: .init()) expected_result = [190, 191, 192, 192] #expect(compressed == expected_result) - compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data + compressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) expected_result = [192, 190, 192, 191, 192, 192] #expect(compressed == expected_result) } @@ -127,8 +127,8 @@ extension RunLengthEncodingTests { @Test func decompressRLE() throws { let string = "AAAAABBBBBCCCCC" let data:[UInt8] = .init(string.utf8) - let compressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data).data - let decompressed = try RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).decompress(data: compressed) + let compressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).compress(data: data, configuration: .init()) + let decompressed = RunLengthEncoding(minRun: 3, alwaysIncludeRunCount: true).decompress(data: compressed, configuration: .init()) #expect(decompressed == data) } } diff --git a/Tests/ZlibTests/DeflateTests.swift b/Tests/ZlibTests/DeflateTests.swift index b198e13..3c933b3 100644 --- a/Tests/ZlibTests/DeflateTests.swift +++ b/Tests/ZlibTests/DeflateTests.swift @@ -8,8 +8,8 @@ struct DeflateTests { let deflate = Deflate() let bro = "What in tarnation fornication trepidation what what what what what the the the the" let broData = bro.data(using: .utf8)! - var compressed = deflate.compress(data: [UInt8](broData))! - var decompressed = deflate.decompress(data: compressed.data)! + var compressed = deflate.compress(data: [UInt8](broData), configuration: .init(reserveCapacity: 1024))! + var decompressed = deflate.decompress(data: compressed, configuration: .init(reserveCapacity: 1024))! decompressed.append(0) #expect(String(cString: decompressed) == bro) } From cdad381872ee4a8aa0417a55acd263e347da02f2 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 16:36:01 -0500 Subject: [PATCH 04/22] update readme and package.swift --- Package.swift | 8 +++- README.md | 39 ++++++++----------- .../SwiftCompression/SwiftCompression.swift | 4 +- .../CompressionAlgorithm.swift | 2 +- Sources/Zlib/deflate/DEFLATE.swift | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/Package.swift b/Package.swift index 6930890..0babf76 100644 --- a/Package.swift +++ b/Package.swift @@ -25,6 +25,11 @@ let package = Package( name: "SwiftCompressionSnappy", targets: ["CompressionSnappy"] ), + + .library( + name: "Zlib", + targets: ["Zlib"] + ), ], // MARK: Targets targets: [ @@ -38,7 +43,8 @@ let package = Package( "SwiftCompressionUtilities", "CompressionDNA", "CompressionLZ", - "CompressionSnappy" + "CompressionSnappy", + "Zlib" ] ), diff --git a/README.md b/README.md index 08501b0..a5feb67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Swift Compression -Requires at least Swift 5.9 Apache 2.0 License +Requires at least Swift 5.9 Apache 2.0 License Standalone compression & decompression library. @@ -8,26 +8,20 @@ Standalone compression & decompression library. ### Compression and decompression -- [Brotli](https://github.com/google/brotli) -- [DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) -- [DNA Binary Encoding](https://en.wikipedia.org/wiki/DNA_digital_data_storage) -- [DNA Single-Block Encoding](https://www.mdpi.com/1999-4893/13/4/99) -- [Huffman Coding](https://en.wikipedia.org/wiki/Huffman_coding) -- [iWork Archive](https://en.wikipedia.org/wiki/IWork) -- [LZ77 & LZ78](https://en.wikipedia.org/wiki/LZ77_and_LZ78) -- [Move-to-front](https://en.wikipedia.org/wiki/Move-to-front_transform) -- [Run-Length Encoding](https://en.wikipedia.org/wiki/Run-length_encoding) -- [Snappy](https://github.com/google/snappy) -- [Snappy Framed](https://github.com/google/snappy/blob/main/framing_format.txt) -- [Zstd](https://github.com/facebook/zstd) - -### Minification - -- CSS -- JavaScript -- Swift - -## Getting starting +- [ ] [Brotli](https://github.com/google/brotli) +- [x] [DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) +- [x] [DNA Binary Encoding](https://en.wikipedia.org/wiki/DNA_digital_data_storage) +- [ ] [DNA Single-Block Encoding](https://www.mdpi.com/1999-4893/13/4/99) +- [x] [Huffman Coding](https://en.wikipedia.org/wiki/Huffman_coding) +- [ ] [iWork Archive](https://en.wikipedia.org/wiki/IWork) +- [ ] [LZ77 & LZ78](https://en.wikipedia.org/wiki/LZ77_and_LZ78) +- [x] [Move-to-front](https://en.wikipedia.org/wiki/Move-to-front_transform) +- [x] [Run-Length Encoding](https://en.wikipedia.org/wiki/Run-length_encoding) +- [ ] [Snappy](https://github.com/google/snappy) +- [ ] [Snappy Framed](https://github.com/google/snappy/blob/main/framing_format.txt) +- [ ] [Zstd](https://github.com/facebook/zstd) + +## Getting started coming soon... @@ -36,11 +30,10 @@ coming soon... The products are broken up into their respective algorithms, but the __SwiftCompression__ product includes all of them and is the recommended one to use in your projects. - SwiftCompression -- SwiftCompressionCSS - SwiftCompressionDNA -- SwiftCompressionJavaScript - SwiftCompressionLZ - SwiftCompressionSnappy +- Zlib ## Contributing diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index 42da654..4e6b9f8 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -2,6 +2,7 @@ @_exported import CompressionDNA @_exported import CompressionLZ @_exported import CompressionSnappy +@_exported import Zlib @_exported import SwiftCompressionUtilities // MARK: Technique @@ -18,7 +19,8 @@ extension CompressionAlgorithm { //return CompressionTechnique.brotli(windowSize: windowSize) return nil case .bwt: return nil - case .deflate: return nil + case .deflate(let bufferSize, let level): + return Deflate(bufferSize: bufferSize, level: level) case .huffmanCoding: return nil case .lz4: return nil case .lz77(let windowSize, let bufferSize, let offsetBitWidth): diff --git a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift index 1d97976..09e2229 100644 --- a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift +++ b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift @@ -13,7 +13,7 @@ public enum CompressionAlgorithm: Hashable, Sendable { case brotli(windowSize: Int) /// Burrows–Wheeler transform case bwt - case deflate + case deflate(bufferSize: Int, level: Int32) case huffmanCoding case lz4 case lz77(windowSize: Int, bufferSize: Int, offsetBitWidth: Int) diff --git a/Sources/Zlib/deflate/DEFLATE.swift b/Sources/Zlib/deflate/DEFLATE.swift index 1636540..2262e3d 100644 --- a/Sources/Zlib/deflate/DEFLATE.swift +++ b/Sources/Zlib/deflate/DEFLATE.swift @@ -21,7 +21,7 @@ public struct Deflate: Sendable { } public var algorithm: CompressionAlgorithm { - .deflate + .deflate(bufferSize: bufferSize, level: level) } public var quality: CompressionQuality { From 00e581547069314e975cc4ccad12cc9317929125 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 16:46:20 -0500 Subject: [PATCH 05/22] add `libdeflate` to readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5feb67..ef087a7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ Standalone compression & decompression library. ### Compression and decompression - [ ] [Brotli](https://github.com/google/brotli) -- [x] [DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) +- [ ] [DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) + - [x] via zlib + - [ ] via libdeflate - [x] [DNA Binary Encoding](https://en.wikipedia.org/wiki/DNA_digital_data_storage) - [ ] [DNA Single-Block Encoding](https://www.mdpi.com/1999-4893/13/4/99) - [x] [Huffman Coding](https://en.wikipedia.org/wiki/Huffman_coding) From af60619c4cae3212779f6c3159b8ccd717e5c768 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 18:02:45 -0500 Subject: [PATCH 06/22] support gzip --- .../SwiftCompression/SwiftCompression.swift | 3 +- .../CompressionAlgorithm.swift | 2 +- Sources/Zlib/deflate/DEFLATE+Compress.swift | 12 ++++- Sources/Zlib/deflate/DEFLATE+Decompress.swift | 12 ++++- Sources/Zlib/deflate/DEFLATE.swift | 3 +- Sources/Zlib/gzip/Gzip+Compress.swift | 46 +++++++++++++++++++ Sources/Zlib/gzip/Gzip+Decompress.swift | 44 ++++++++++++++++++ Sources/Zlib/gzip/Gzip.swift | 35 ++++++++++++++ Tests/ZlibTests/DeflateTests.swift | 7 ++- Tests/ZlibTests/GzipTests.swift | 21 +++++++++ 10 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 Sources/Zlib/gzip/Gzip+Compress.swift create mode 100644 Sources/Zlib/gzip/Gzip+Decompress.swift create mode 100644 Sources/Zlib/gzip/Gzip.swift create mode 100644 Tests/ZlibTests/GzipTests.swift diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index 4e6b9f8..d2a5e1f 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -57,7 +57,8 @@ extension CompressionAlgorithm { case ._7z: return nil case .bzip2: return nil - case .gzip: return nil + case .gzip(let bufferSize, let level, let memLevel, let strategy): + return Gzip(bufferSize: bufferSize, level: level, memLevel: memLevel, strategy: strategy) case .rar: return nil case .h264: return nil diff --git a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift index 09e2229..107ddfa 100644 --- a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift +++ b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift @@ -31,7 +31,7 @@ public enum CompressionAlgorithm: Hashable, Sendable { // files case _7z case bzip2 - case gzip + case gzip(bufferSize: Int, level: Int32, memLevel: Int32, strategy: Int32) case rar // image diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift index 7debe86..bd50e07 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -8,11 +8,19 @@ extension Deflate: Compressor { public func compress( data: some Collection, - configuration: CompressionConfiguration + configuration: CompressionConfiguration = .init() ) -> CompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } + return compress(data: data, configuration: configuration, stream: &stream) + } + + func compress( + data: some Collection, + configuration: CompressionConfiguration, + stream: inout z_stream + ) -> CompressionResult { defer { deflateEnd(&stream) } var compressed = [UInt8]() @@ -42,7 +50,7 @@ extension Deflate { public let reserveCapacity:Int public init( - reserveCapacity: Int + reserveCapacity: Int = 1024 ) { self.reserveCapacity = reserveCapacity } diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress.swift b/Sources/Zlib/deflate/DEFLATE+Decompress.swift index 6341399..1f3aa6f 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress.swift @@ -8,11 +8,19 @@ extension Deflate: Decompressor { public func decompress( data: some Collection, - configuration: DecompressionConfiguration + configuration: DecompressionConfiguration = .init() ) -> DecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } + return decompress(data: data, configuration: configuration, stream: &stream) + } + + func decompress( + data: some Collection, + configuration: DecompressionConfiguration, + stream: inout z_stream + ) -> DecompressionResult { defer { inflateEnd(&stream) } var decompressedData = [UInt8]() @@ -42,7 +50,7 @@ extension Deflate { public let reserveCapacity:Int public init( - reserveCapacity: Int + reserveCapacity: Int = 1024 ) { self.reserveCapacity = reserveCapacity } diff --git a/Sources/Zlib/deflate/DEFLATE.swift b/Sources/Zlib/deflate/DEFLATE.swift index 2262e3d..488cc6d 100644 --- a/Sources/Zlib/deflate/DEFLATE.swift +++ b/Sources/Zlib/deflate/DEFLATE.swift @@ -2,8 +2,7 @@ import SwiftCompressionUtilities import ZlibShim -// https://en.wikipedia.org/wiki/Gzip -// https://www.rfc-editor.org/rfc/rfc1952#page-5 +// https://www.rfc-editor.org/rfc/rfc1951 /// The Deflate compression technique. /// diff --git a/Sources/Zlib/gzip/Gzip+Compress.swift b/Sources/Zlib/gzip/Gzip+Compress.swift new file mode 100644 index 0000000..0e8d8bb --- /dev/null +++ b/Sources/Zlib/gzip/Gzip+Compress.swift @@ -0,0 +1,46 @@ + +import SwiftCompressionUtilities +import ZlibShim + +extension Gzip: Compressor { + public typealias CompressionConfiguration = CompressConfiguration + public typealias CompressionResult = [UInt8]? + + public func compress( + data: some Collection, + configuration: CompressionConfiguration = .init() + ) -> CompressionResult { + var stream = z_stream() + let windowBits:Int32 = 15 + 16 + let status = deflateInit2_( + &stream, + level, + Z_DEFLATED, + windowBits, + memLevel, + strategy, + ZLIB_VERSION, + Int32(MemoryLayout.size) + ) + guard status == Z_OK else { return nil } + return Deflate( + bufferSize: bufferSize, + level: level + ).compress( + data: data, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } +} + +// MARK: Configuration +extension Gzip { + public struct CompressConfiguration: Sendable { + public let reserveCapacity:Int + + public init(reserveCapacity: Int = 1024) { + self.reserveCapacity = reserveCapacity + } + } +} \ No newline at end of file diff --git a/Sources/Zlib/gzip/Gzip+Decompress.swift b/Sources/Zlib/gzip/Gzip+Decompress.swift new file mode 100644 index 0000000..b49a35c --- /dev/null +++ b/Sources/Zlib/gzip/Gzip+Decompress.swift @@ -0,0 +1,44 @@ + +import SwiftCompressionUtilities +import ZlibShim + +extension Gzip: Decompressor { + public typealias DecompressionConfiguration = DecompressConfiguration + public typealias DecompressionResult = [UInt8]? + + public func decompress( + data: some Collection, + configuration: DecompressConfiguration = .init() + ) -> DecompressionResult { + var stream = z_stream() + let windowBits:Int32 = 15 + 16 + let status = inflateInit2_( + &stream, + windowBits, + ZLIB_VERSION, + Int32(MemoryLayout.size) + ) + guard status == Z_OK else { return nil } + return Deflate( + bufferSize: bufferSize, + level: level + ).decompress( + data: data, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } +} + +// MARK: Configuration +extension Gzip { + public struct DecompressConfiguration: Sendable { + public let reserveCapacity:Int + + public init( + reserveCapacity: Int = 1024 + ) { + self.reserveCapacity = reserveCapacity + } + } +} \ No newline at end of file diff --git a/Sources/Zlib/gzip/Gzip.swift b/Sources/Zlib/gzip/Gzip.swift new file mode 100644 index 0000000..c5fb969 --- /dev/null +++ b/Sources/Zlib/gzip/Gzip.swift @@ -0,0 +1,35 @@ + +import SwiftCompressionUtilities +import ZlibShim + +// https://www.rfc-editor.org/rfc/rfc1952 + +/// The Gzip compression technique. +/// +/// https://en.wikipedia.org/wiki/Gzip +public struct Gzip: Sendable { + public let bufferSize:Int + public let level:Int32 + public let memLevel:Int32 + public let strategy:Int32 + + public init( + bufferSize: Int = 1024, + level: Int32 = Z_DEFAULT_COMPRESSION, + memLevel: Int32 = 8, + strategy: Int32 = Z_DEFAULT_STRATEGY + ) { + self.bufferSize = bufferSize + self.level = level + self.memLevel = memLevel + self.strategy = strategy + } + + public var algorithm: CompressionAlgorithm { + .gzip(bufferSize: bufferSize, level: level, memLevel: memLevel, strategy: strategy) + } + + public var quality: CompressionQuality { + .lossless + } +} \ No newline at end of file diff --git a/Tests/ZlibTests/DeflateTests.swift b/Tests/ZlibTests/DeflateTests.swift index 3c933b3..e448615 100644 --- a/Tests/ZlibTests/DeflateTests.swift +++ b/Tests/ZlibTests/DeflateTests.swift @@ -1,4 +1,7 @@ +#if canImport(FoundationEssentials) + +import FoundationEssentials import Testing @testable import Zlib @@ -13,4 +16,6 @@ struct DeflateTests { decompressed.append(0) #expect(String(cString: decompressed) == bro) } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Tests/ZlibTests/GzipTests.swift b/Tests/ZlibTests/GzipTests.swift new file mode 100644 index 0000000..7314cbd --- /dev/null +++ b/Tests/ZlibTests/GzipTests.swift @@ -0,0 +1,21 @@ + +#if canImport(FoundationEssentials) + +import FoundationEssentials +import Testing +@testable import Zlib + +struct GzipTests { + @Test + func gzipTest() { + let gzip = Gzip() + let bro = "What in tarnation fornication trepidation what what what what what the the the the" + let broData = bro.data(using: .utf8)! + var compressed = gzip.compress(data: [UInt8](broData))! + var decompressed = gzip.decompress(data: compressed)! + decompressed.append(0) + #expect(String(cString: decompressed) == bro) + } +} + +#endif \ No newline at end of file From e5254ec17a672fc817357c09f4c97209c54a13e4 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 7 Apr 2026 18:46:32 -0500 Subject: [PATCH 07/22] support compressing/decompressing `Span` for zlib --- .../Zlib/deflate/DEFLATE+Compress+Span.swift | 16 +++++++++ Sources/Zlib/deflate/DEFLATE+Compress.swift | 31 +++++++++-------- .../deflate/DEFLATE+Decompress+Span.swift | 16 +++++++++ Sources/Zlib/deflate/DEFLATE+Decompress.swift | 33 +++++++++--------- Sources/Zlib/gzip/Gzip+Compress+Span.swift | 34 +++++++++++++++++++ Sources/Zlib/gzip/Gzip+Compress.swift | 19 ++++++----- Sources/Zlib/gzip/Gzip+Decompress+Span.swift | 30 ++++++++++++++++ Sources/Zlib/gzip/Gzip+Decompress.swift | 19 ++++++----- 8 files changed, 151 insertions(+), 47 deletions(-) create mode 100644 Sources/Zlib/deflate/DEFLATE+Compress+Span.swift create mode 100644 Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift create mode 100644 Sources/Zlib/gzip/Gzip+Compress+Span.swift create mode 100644 Sources/Zlib/gzip/Gzip+Decompress+Span.swift diff --git a/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift new file mode 100644 index 0000000..030e74f --- /dev/null +++ b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift @@ -0,0 +1,16 @@ + +import ZlibShim + +extension Deflate { + public func compress( + span: Span, + configuration: CompressionConfiguration = .init() + ) -> CompressionResult { + var stream = z_stream() + let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) + guard status == Z_OK else { return nil } + return span.withUnsafeBufferPointer { + return compress(baseAddress: $0.baseAddress, count: $0.count, configuration: configuration, stream: &stream) + } + } +} \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift index bd50e07..91f654b 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -13,11 +13,14 @@ extension Deflate: Compressor { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } - return compress(data: data, configuration: configuration, stream: &stream) + return data.withContiguousStorageIfAvailable { rawBuffer in + return compress(baseAddress: rawBuffer.baseAddress, count: data.count, configuration: configuration, stream: &stream) + } ?? nil } - func compress( - data: some Collection, + package func compress( + baseAddress: UnsafePointer?, + count: Int, configuration: CompressionConfiguration, stream: inout z_stream ) -> CompressionResult { @@ -26,18 +29,16 @@ extension Deflate: Compressor { var compressed = [UInt8]() compressed.reserveCapacity(configuration.reserveCapacity) var buffer = [UInt8](repeating: 0, count: bufferSize) - data.withContiguousStorageIfAvailable { rawBuffer in - stream.next_in = UnsafeMutablePointer(mutating: rawBuffer.baseAddress) - stream.avail_in = UInt32(data.count) - buffer.withUnsafeMutableBufferPointer { b in - while stream.avail_out == 0 { - stream.next_out = b.baseAddress - stream.avail_out = UInt32(bufferSize) - deflate(&stream, Z_FINISH) - - let count = bufferSize - Int(stream.avail_out) - compressed.append(contentsOf: b[0.., + configuration: DecompressionConfiguration = .init() + ) -> DecompressionResult { + var stream = z_stream() + let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) + guard status == Z_OK else { return nil } + return span.withUnsafeBufferPointer { + return decompress(baseAddress: $0.baseAddress, count: $0.count, configuration: configuration, stream: &stream) + } ?? nil + } +} \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress.swift b/Sources/Zlib/deflate/DEFLATE+Decompress.swift index 1f3aa6f..c23138f 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress.swift @@ -13,11 +13,14 @@ extension Deflate: Decompressor { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } - return decompress(data: data, configuration: configuration, stream: &stream) + return data.withContiguousStorageIfAvailable { + return decompress(baseAddress: $0.baseAddress, count: $0.count, configuration: configuration, stream: &stream) + } ?? nil } - func decompress( - data: some Collection, + package func decompress( + baseAddress: UnsafePointer?, + count: Int, configuration: DecompressionConfiguration, stream: inout z_stream ) -> DecompressionResult { @@ -25,19 +28,17 @@ extension Deflate: Decompressor { var decompressedData = [UInt8]() var buffer = [UInt8](repeating: 0, count: bufferSize) - data.withContiguousStorageIfAvailable { rawBuffer in - stream.next_in = UnsafeMutablePointer(mutating: rawBuffer.baseAddress) - stream.avail_in = UInt32(data.count) - - buffer.withUnsafeMutableBufferPointer { b in - while stream.avail_out == 0 { - stream.next_out = b.baseAddress - stream.avail_out = UInt32(bufferSize) - inflate(&stream, Z_NO_FLUSH) - - let count = bufferSize - Int(stream.avail_out) - decompressedData.append(contentsOf: b[0.., + configuration: CompressionConfiguration = .init() + ) -> CompressionResult { + var stream = z_stream() + let windowBits:Int32 = 15 + 16 + let status = deflateInit2_( + &stream, + level, + Z_DEFLATED, + windowBits, + memLevel, + strategy, + ZLIB_VERSION, + Int32(MemoryLayout.size) + ) + guard status == Z_OK else { return nil } + return span.withUnsafeBufferPointer { + return Deflate( + bufferSize: bufferSize, + level: level + ).compress( + baseAddress: $0.baseAddress, + count: $0.count, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } ?? nil + } +} \ No newline at end of file diff --git a/Sources/Zlib/gzip/Gzip+Compress.swift b/Sources/Zlib/gzip/Gzip+Compress.swift index 0e8d8bb..db2b02a 100644 --- a/Sources/Zlib/gzip/Gzip+Compress.swift +++ b/Sources/Zlib/gzip/Gzip+Compress.swift @@ -23,14 +23,17 @@ extension Gzip: Compressor { Int32(MemoryLayout.size) ) guard status == Z_OK else { return nil } - return Deflate( - bufferSize: bufferSize, - level: level - ).compress( - data: data, - configuration: .init(reserveCapacity: configuration.reserveCapacity), - stream: &stream - ) + return data.withContiguousStorageIfAvailable { + return Deflate( + bufferSize: bufferSize, + level: level + ).compress( + baseAddress: $0.baseAddress, + count: $0.count, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } ?? nil } } diff --git a/Sources/Zlib/gzip/Gzip+Decompress+Span.swift b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift new file mode 100644 index 0000000..5c2af9e --- /dev/null +++ b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift @@ -0,0 +1,30 @@ + +import ZlibShim + +extension Gzip { + public func decompress( + span: Span, + configuration: DecompressConfiguration = .init() + ) -> DecompressionResult { + var stream = z_stream() + let windowBits:Int32 = 15 + 16 + let status = inflateInit2_( + &stream, + windowBits, + ZLIB_VERSION, + Int32(MemoryLayout.size) + ) + guard status == Z_OK else { return nil } + return span.withUnsafeBufferPointer { + return Deflate( + bufferSize: bufferSize, + level: level + ).decompress( + baseAddress: $0.baseAddress, + count: $0.count, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } ?? nil + } +} \ No newline at end of file diff --git a/Sources/Zlib/gzip/Gzip+Decompress.swift b/Sources/Zlib/gzip/Gzip+Decompress.swift index b49a35c..5f25be5 100644 --- a/Sources/Zlib/gzip/Gzip+Decompress.swift +++ b/Sources/Zlib/gzip/Gzip+Decompress.swift @@ -19,14 +19,17 @@ extension Gzip: Decompressor { Int32(MemoryLayout.size) ) guard status == Z_OK else { return nil } - return Deflate( - bufferSize: bufferSize, - level: level - ).decompress( - data: data, - configuration: .init(reserveCapacity: configuration.reserveCapacity), - stream: &stream - ) + return data.withContiguousStorageIfAvailable { + return Deflate( + bufferSize: bufferSize, + level: level + ).decompress( + baseAddress: $0.baseAddress, + count: $0.count, + configuration: .init(reserveCapacity: configuration.reserveCapacity), + stream: &stream + ) + } ?? nil } } From 14b9b7de3f28eee582c101b66765f4c87d460308 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Wed, 8 Apr 2026 16:46:51 -0500 Subject: [PATCH 08/22] stuff - add static `default` variable to `Compressor` and `Decompressor` - rename `CompressionConfiguration` to `ConcreteCompressionConfiguration` - add and adopt `CompressionConfiguration` & `DecompressionConfiguration` protocols - added span compression --- .../DNABinaryEncoding+Compress+Span.swift | 15 ++++++ .../binary/DNABinaryEncoding+Compress.swift | 18 +++++--- .../binary/DNABinaryEncoding+Decompress.swift | 4 +- ...DNASingleBlockEncoding+Compress+Span.swift | 11 +++++ .../DNASingleBlockEncoding+Compress.swift | 19 ++++++-- .../DNASingleBlockEncoding+Decompress.swift | 8 ++-- Sources/LZ/lz77/LZ77+Compress+Span.swift | 22 +++++++++ Sources/LZ/lz77/LZ77+Compress.swift | 32 +++++++------ Sources/LZ/lz77/LZ77+Decompress.swift | 4 +- Sources/Snappy/iwa/IWA+Compress.swift | 11 ++++- Sources/Snappy/iwa/IWA+Decompress.swift | 4 +- Sources/Snappy/iwa/IWA.swift | 4 +- .../RunLengthEncoding+Compress+Span.swift | 15 ++++++ .../RunLengthEncoding+Compress.swift | 46 ++++++++++--------- .../RunLengthEncoding+Decompress.swift | 4 +- .../CompressionConfiguration.swift | 4 ++ .../CompressionTechnique.swift | 14 ++++++ .../Compressor.swift | 13 ++++-- .../DecompressionConfiguration.swift | 4 ++ .../Decompressor.swift | 4 +- .../Zlib/deflate/DEFLATE+Compress+Span.swift | 2 +- Sources/Zlib/deflate/DEFLATE+Compress.swift | 10 ++-- .../deflate/DEFLATE+Decompress+Span.swift | 2 +- Sources/Zlib/deflate/DEFLATE+Decompress.swift | 10 ++-- Sources/Zlib/gzip/Gzip+Compress+Span.swift | 2 +- Sources/Zlib/gzip/Gzip+Compress.swift | 12 +++-- Sources/Zlib/gzip/Gzip+Decompress+Span.swift | 2 +- Sources/Zlib/gzip/Gzip+Decompress.swift | 8 ++-- 28 files changed, 218 insertions(+), 86 deletions(-) create mode 100644 Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift create mode 100644 Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift create mode 100644 Sources/LZ/lz77/LZ77+Compress+Span.swift create mode 100644 Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift create mode 100644 Sources/SwiftCompressionUtilities/CompressionConfiguration.swift create mode 100644 Sources/SwiftCompressionUtilities/DecompressionConfiguration.swift diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift new file mode 100644 index 0000000..d4b8ab1 --- /dev/null +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift @@ -0,0 +1,15 @@ + +import SwiftCompressionUtilities + +extension DNABinaryEncoding { + public func compress( + span: Span, + configuration: ConcreteCompressionConfiguration + ) -> CompressionResult { + var result = CompressionResult() + let validBitsInLastByte = span.withUnsafeBufferPointer { + compress(buffer: $0, closure: { result.append($0) }) + } + return result + } +} \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift index 399c114..10e3377 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -2,15 +2,17 @@ import SwiftCompressionUtilities extension DNABinaryEncoding: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8] public func compress( data: some Collection, - configuration: CompressionConfiguration + configuration: ConcreteCompressionConfiguration ) -> CompressionResult { var result = CompressionResult() - let validBitsInLastByte = compress(data: data, closure: { result.append($0) }) + let validBitsInLastByte = data.withContiguousStorageIfAvailable { + compress(buffer: $0, closure: { result.append($0) }) + } return result } @@ -22,12 +24,12 @@ extension DNABinaryEncoding: Compressor { /// - closure: Logic to execute when a byte was encoded. /// - Returns: Valid bits for the last byte, if necessary. /// - Complexity: O(_n_) where _n_ is the length of `data`. - private func compress( - data: some Collection, + func compress( + buffer: UnsafeBufferPointer, closure: (UInt8) -> Void ) -> UInt8? { var bitWriter = ByteBuilder() - for base in data { + for base in buffer { if let bits = baseBits[base] { for bit in bits { if let wrote = bitWriter.write(bit: bit) { @@ -44,7 +46,9 @@ extension DNABinaryEncoding: Compressor { // MARK: Configuration extension DNABinaryEncoding { - public struct CompressConfiguration: Sendable { + public struct CompressConfiguration: CompressionConfiguration, DecompressionConfiguration { + public static var `default`: Self { .init() } + public init() { } } diff --git a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift index 42178ad..2fb3f41 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift @@ -2,7 +2,7 @@ import SwiftCompressionUtilities extension DNABinaryEncoding: Decompressor { - public typealias DecompressionConfiguration = CompressConfiguration + public typealias ConcreteDecompressionConfiguration = CompressConfiguration public typealias DecompressionResult = [UInt8] /// Decompress a collection of bytes using the DNA binary encoding technique. @@ -13,7 +13,7 @@ extension DNABinaryEncoding: Decompressor { /// - Complexity: O(_n_) where _n_ is the length of `data`. public func decompress( data: some Collection, - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) -> DecompressionResult { var result = DecompressionResult() decompress(data: data, closure: { result.append($0) }) diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift new file mode 100644 index 0000000..112c218 --- /dev/null +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift @@ -0,0 +1,11 @@ + +import SwiftCompressionUtilities + +extension DNASingleBlockEncoding { + public func compress( + span: Span, + configuration: ConcreteCompressionConfiguration = .default + ) -> CompressionResult { + return span.withUnsafeBufferPointer { compress(buffer: $0, configuration: configuration) } + } +} \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift index 6cbbbba..19b33ec 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift @@ -2,7 +2,7 @@ import SwiftCompressionUtilities extension DNASingleBlockEncoding: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8:[UInt8]] } @@ -13,10 +13,17 @@ extension DNASingleBlockEncoding { /// - data: Sequence of bytes to compress. /// - Complexity: O(_n_ + (_m_ log _m_)) where _n_ is the length of `data` and _m_ is the number of unique bytes in `data`. public func compress( - data: some Sequence, + data: some Collection, configuration: CompressConfiguration ) -> CompressionResult { - let frequencyTable:[UInt8:Int] = CompressionTechnique.buildFrequencyTable(data: data) + return data.withContiguousStorageIfAvailable { compress(buffer: $0, configuration: configuration) } ?? .init() + } + + func compress( + buffer: UnsafeBufferPointer, + configuration: CompressConfiguration + ) -> CompressionResult { + let frequencyTable:[UInt8:Int] = CompressionTechnique.buildFrequencyTable(buffer: buffer) var sortedFrequencyTable = frequencyTable.sorted(by: { guard $0.value != $1.value else { return $0.key < $1.key } return $0.value > $1.value @@ -28,7 +35,7 @@ extension DNASingleBlockEncoding { results[key] = [] sortedIndexes[key] = index } - for byte in data { + for byte in buffer { let sortedIndex = sortedIndexes[byte] ?? sortedIndexes.count for i in 0.., - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) -> DecompressionResult { return [] } @@ -19,7 +19,9 @@ extension DNASingleBlockEncoding: Decompressor { // TODO: finish // MARK: Configuration extension DNASingleBlockEncoding { - public struct DecompressConfiguration: Sendable { + public struct DecompressConfiguration: DecompressionConfiguration { + public static var `default`: Self { .init() } + public init() { } } diff --git a/Sources/LZ/lz77/LZ77+Compress+Span.swift b/Sources/LZ/lz77/LZ77+Compress+Span.swift new file mode 100644 index 0000000..e5d897f --- /dev/null +++ b/Sources/LZ/lz77/LZ77+Compress+Span.swift @@ -0,0 +1,22 @@ + +import SwiftCompressionUtilities + +extension LZ77 { + // Compress a collection of bytes using the LZ77 technique. + /// + /// - Parameters: + /// - data: Collection of bytes to compress. + /// - closure: Logic to execute when a byte is compressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress( + span: Span, + configuration: ConcreteCompressionConfiguration + ) -> CompressionResult { + var result = CompressionResult() + result.reserveCapacity(configuration.reserveCapacity) + span.withUnsafeBufferPointer { + compress(buffer: $0, closure: { result.append($0) }) + } + return result + } +} \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift index 5293cc4..e20da6e 100644 --- a/Sources/LZ/lz77/LZ77+Compress.swift +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -2,7 +2,7 @@ import SwiftCompressionUtilities extension LZ77: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8] // Compress a collection of bytes using the LZ77 technique. @@ -13,11 +13,13 @@ extension LZ77: Compressor { /// - Complexity: O(_n_) where _n_ is the length of `data`. public func compress( data: some Collection, - configuration: CompressionConfiguration + configuration: ConcreteCompressionConfiguration ) -> CompressionResult { var result = CompressionResult() result.reserveCapacity(configuration.reserveCapacity) - compress(data: data, closure: { result.append($0) }) + data.withContiguousStorageIfAvailable { + compress(buffer: $0, closure: { result.append($0) }) + } return result } @@ -27,20 +29,20 @@ extension LZ77: Compressor { /// - data: Collection of bytes to compress. /// - closure: Logic to execute when a byte is compressed. /// - Complexity: O(_n_) where _n_ is the length of `data`. - private func compress( - data: some Collection, + func compress( + buffer: UnsafeBufferPointer, closure: (UInt8) -> Void ) { - let count = data.count + let count = buffer.count var index = 0 while index < count { let bufferEndIndex = min(index + bufferSize, count) guard index < bufferEndIndex else { break } let bufferCount = bufferEndIndex - index - let bufferRange = data.index(data.startIndex, offsetBy: index).., - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) -> DecompressionResult { var result = DecompressionResult() decompress(data: data, closure: { result.append($0) }) diff --git a/Sources/Snappy/iwa/IWA+Compress.swift b/Sources/Snappy/iwa/IWA+Compress.swift index 3e21a85..6c92f70 100644 --- a/Sources/Snappy/iwa/IWA+Compress.swift +++ b/Sources/Snappy/iwa/IWA+Compress.swift @@ -1,6 +1,6 @@ extension IWA { // TODO: finish - public typealias CompressionConfiguration = Configuration + public typealias ConcreteCompressionConfiguration = Configuration public typealias CompressionResult = [UInt8] /// - Parameters: @@ -8,8 +8,15 @@ extension IWA { // TODO: finish /// - Complexity: O(_n_) where _n_ is the length of `data`. public func compress( data: some Collection, - configuration: CompressionConfiguration + configuration: ConcreteCompressionConfiguration ) -> CompressionResult { return .init() } + + public func compress( + span: Span, + configuration: Configuration + ) throws(Never) -> [UInt8] { + return .init() + } } \ No newline at end of file diff --git a/Sources/Snappy/iwa/IWA+Decompress.swift b/Sources/Snappy/iwa/IWA+Decompress.swift index 44cbaeb..efa078d 100644 --- a/Sources/Snappy/iwa/IWA+Decompress.swift +++ b/Sources/Snappy/iwa/IWA+Decompress.swift @@ -1,6 +1,6 @@ extension IWA { // TODO: finish - public typealias DecompressionConfiguration = Configuration + public typealias ConcreteDecompressionConfiguration = Configuration public typealias DecompressionResult = [UInt8] /// - Parameters: @@ -9,7 +9,7 @@ extension IWA { // TODO: finish /// - Complexity: O(_n_) where _n_ is the length of `data`. public func decompress( data: some Collection, - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) -> DecompressionResult { return .init() } diff --git a/Sources/Snappy/iwa/IWA.swift b/Sources/Snappy/iwa/IWA.swift index 2b79666..8b2e0bd 100644 --- a/Sources/Snappy/iwa/IWA.swift +++ b/Sources/Snappy/iwa/IWA.swift @@ -30,7 +30,9 @@ extension CompressionTechnique { // MARK: Configuration extension IWA { - public struct Configuration: Sendable { + public struct Configuration: CompressionConfiguration, DecompressionConfiguration { + public static var `default`: Self { .init() } + public init() { } } diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift new file mode 100644 index 0000000..b91c5f3 --- /dev/null +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift @@ -0,0 +1,15 @@ + +import SwiftCompressionUtilities + +extension RunLengthEncoding { + public func compress( + span: Span, + configuration: CompressConfiguration + ) throws(Never) -> CompressionResult { + var result = CompressionResult() + span.withUnsafeBufferPointer { + compress(buffer: $0, closure: compressClosure(closure: { result.append($0) })) + } + return result + } +} \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift index 9d73ad0..2fc6827 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift @@ -2,19 +2,21 @@ import SwiftCompressionUtilities extension RunLengthEncoding: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8] public func compress( data: some Collection, - configuration: CompressionConfiguration + configuration: ConcreteCompressionConfiguration ) -> CompressionResult { var result = CompressionResult() - compress(data: data, closure: compressClosure(closure: { result.append($0) })) + data.withContiguousStorageIfAvailable { + compress(buffer: $0, closure: compressClosure(closure: { result.append($0) })) + } return result } - private func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { + func compressClosure(closure: @escaping (UInt8) -> Void) -> (CompressClosureParameters) -> Void { if alwaysIncludeRunCount { return { (arg) in let (run, runByte) = arg @@ -41,29 +43,27 @@ extension RunLengthEncoding: Compressor { /// - minRun: Minimum run count required to compress identical sequential bytes. /// - closure: Logic to execute for a run. /// - Complexity: O(_n_) where _n_ is the length of `data`. - private func compress( - data: some Sequence, + func compress( + buffer: UnsafeBufferPointer, closure: (CompressClosureParameters) -> Void ) { var run = 0 var runByte:UInt8? = nil - data.withContiguousStorageIfAvailable { p in - for index in 0.., - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) -> DecompressionResult { var result = DecompressionResult() decompress(data: data, closure: { result.append($0) }) diff --git a/Sources/SwiftCompressionUtilities/CompressionConfiguration.swift b/Sources/SwiftCompressionUtilities/CompressionConfiguration.swift new file mode 100644 index 0000000..630af89 --- /dev/null +++ b/Sources/SwiftCompressionUtilities/CompressionConfiguration.swift @@ -0,0 +1,4 @@ + +public protocol CompressionConfiguration: Sendable, ~Copyable { + static var `default`: Self { get } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift index d6f5628..2d84bcc 100644 --- a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift +++ b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift @@ -34,6 +34,20 @@ extension CompressionTechnique { return table } + /// Creates a lookup frequency table from a sequence of raw bytes. + /// + /// - Parameters: + /// - data: Sequence of raw bytes. + /// - Returns: A lookup frequency table. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public static func buildFrequencyTable(buffer: UnsafeBufferPointer)-> [UInt8:Int] { + var table = [UInt8:Int]() + for byte in buffer { + table[byte, default: 0] += 1 + } + return table + } + /// Creates a universal frequency table from a character frequency dictionary. /// /// - Parameters: diff --git a/Sources/SwiftCompressionUtilities/Compressor.swift b/Sources/SwiftCompressionUtilities/Compressor.swift index 51aa4db..fb22288 100644 --- a/Sources/SwiftCompressionUtilities/Compressor.swift +++ b/Sources/SwiftCompressionUtilities/Compressor.swift @@ -1,12 +1,17 @@ // MARK: Compressor public protocol Compressor: AnyCompressor, ~Copyable { - associatedtype CompressionConfiguration + associatedtype ConcreteCompressionConfiguration:CompressionConfiguration associatedtype CompressionResult - associatedtype CompressionErrorType:Error = CompressionError + associatedtype ConcreteCompressionError:Error = CompressionError func compress( data: some Collection, - configuration: CompressionConfiguration - ) throws(CompressionErrorType) -> CompressionResult + configuration: ConcreteCompressionConfiguration + ) throws(ConcreteCompressionError) -> CompressionResult + + func compress( + span: Span, + configuration: ConcreteCompressionConfiguration + ) throws(ConcreteCompressionError) -> CompressionResult } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/DecompressionConfiguration.swift b/Sources/SwiftCompressionUtilities/DecompressionConfiguration.swift new file mode 100644 index 0000000..35d5464 --- /dev/null +++ b/Sources/SwiftCompressionUtilities/DecompressionConfiguration.swift @@ -0,0 +1,4 @@ + +public protocol DecompressionConfiguration: Sendable, ~Copyable { + static var `default`: Self { get } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/Decompressor.swift b/Sources/SwiftCompressionUtilities/Decompressor.swift index 68eeac3..109b3d9 100644 --- a/Sources/SwiftCompressionUtilities/Decompressor.swift +++ b/Sources/SwiftCompressionUtilities/Decompressor.swift @@ -1,7 +1,7 @@ // MARK: Decompressor public protocol Decompressor: AnyDecompressor, ~Copyable { - associatedtype DecompressionConfiguration + associatedtype ConcreteDecompressionConfiguration:DecompressionConfiguration associatedtype DecompressionResult /// Decompress a collection of bytes using this technique. @@ -10,6 +10,6 @@ public protocol Decompressor: AnyDecompressor, ~Copyable { /// - data: Collection of bytes to decompress. func decompress( data: some Collection, - configuration: DecompressionConfiguration + configuration: ConcreteDecompressionConfiguration ) throws(DecompressionError) -> DecompressionResult } \ No newline at end of file diff --git a/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift index 030e74f..5ed5e4f 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift @@ -4,7 +4,7 @@ import ZlibShim extension Deflate { public func compress( span: Span, - configuration: CompressionConfiguration = .init() + configuration: ConcreteCompressionConfiguration = .default ) -> CompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift index 91f654b..a91d694 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -3,12 +3,12 @@ import SwiftCompressionUtilities import ZlibShim extension Deflate: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8]? public func compress( data: some Collection, - configuration: CompressionConfiguration = .init() + configuration: ConcreteCompressionConfiguration = .default ) -> CompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) @@ -21,7 +21,7 @@ extension Deflate: Compressor { package func compress( baseAddress: UnsafePointer?, count: Int, - configuration: CompressionConfiguration, + configuration: ConcreteCompressionConfiguration, stream: inout z_stream ) -> CompressionResult { defer { deflateEnd(&stream) } @@ -47,7 +47,9 @@ extension Deflate: Compressor { // MARK: Configuration extension Deflate { - public struct CompressConfiguration: Sendable { + public struct CompressConfiguration: CompressionConfiguration { + public static var `default`: Self { .init() } + public let reserveCapacity:Int public init( diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift b/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift index a501169..877ec86 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift @@ -4,7 +4,7 @@ import ZlibShim extension Deflate { public func decompress( span: Span, - configuration: DecompressionConfiguration = .init() + configuration: ConcreteDecompressionConfiguration = .default ) -> DecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress.swift b/Sources/Zlib/deflate/DEFLATE+Decompress.swift index c23138f..82bc07c 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress.swift @@ -3,12 +3,12 @@ import SwiftCompressionUtilities import ZlibShim extension Deflate: Decompressor { - public typealias DecompressionConfiguration = DecompressConfiguration + public typealias ConcreteDecompressionConfiguration = DecompressConfiguration public typealias DecompressionResult = [UInt8]? public func decompress( data: some Collection, - configuration: DecompressionConfiguration = .init() + configuration: ConcreteDecompressionConfiguration = .default ) -> DecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) @@ -21,7 +21,7 @@ extension Deflate: Decompressor { package func decompress( baseAddress: UnsafePointer?, count: Int, - configuration: DecompressionConfiguration, + configuration: ConcreteDecompressionConfiguration, stream: inout z_stream ) -> DecompressionResult { defer { inflateEnd(&stream) } @@ -47,7 +47,9 @@ extension Deflate: Decompressor { // MARK: Configuration extension Deflate { - public struct DecompressConfiguration: Sendable { + public struct DecompressConfiguration: DecompressionConfiguration { + public static var `default`: Self { .init() } + public let reserveCapacity:Int public init( diff --git a/Sources/Zlib/gzip/Gzip+Compress+Span.swift b/Sources/Zlib/gzip/Gzip+Compress+Span.swift index d815c1c..6d28a10 100644 --- a/Sources/Zlib/gzip/Gzip+Compress+Span.swift +++ b/Sources/Zlib/gzip/Gzip+Compress+Span.swift @@ -4,7 +4,7 @@ import ZlibShim extension Gzip { public func compress( span: Span, - configuration: CompressionConfiguration = .init() + configuration: ConcreteCompressionConfiguration = .default ) -> CompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 diff --git a/Sources/Zlib/gzip/Gzip+Compress.swift b/Sources/Zlib/gzip/Gzip+Compress.swift index db2b02a..015dd37 100644 --- a/Sources/Zlib/gzip/Gzip+Compress.swift +++ b/Sources/Zlib/gzip/Gzip+Compress.swift @@ -3,12 +3,12 @@ import SwiftCompressionUtilities import ZlibShim extension Gzip: Compressor { - public typealias CompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias CompressionResult = [UInt8]? public func compress( data: some Collection, - configuration: CompressionConfiguration = .init() + configuration: ConcreteCompressionConfiguration = .default ) -> CompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 @@ -39,10 +39,14 @@ extension Gzip: Compressor { // MARK: Configuration extension Gzip { - public struct CompressConfiguration: Sendable { + public struct CompressConfiguration: CompressionConfiguration { + public static var `default`: Self { .init() } + public let reserveCapacity:Int - public init(reserveCapacity: Int = 1024) { + public init( + reserveCapacity: Int = 1024 + ) { self.reserveCapacity = reserveCapacity } } diff --git a/Sources/Zlib/gzip/Gzip+Decompress+Span.swift b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift index 5c2af9e..b58da44 100644 --- a/Sources/Zlib/gzip/Gzip+Decompress+Span.swift +++ b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift @@ -4,7 +4,7 @@ import ZlibShim extension Gzip { public func decompress( span: Span, - configuration: DecompressConfiguration = .init() + configuration: DecompressConfiguration = .default ) -> DecompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 diff --git a/Sources/Zlib/gzip/Gzip+Decompress.swift b/Sources/Zlib/gzip/Gzip+Decompress.swift index 5f25be5..4fb03bc 100644 --- a/Sources/Zlib/gzip/Gzip+Decompress.swift +++ b/Sources/Zlib/gzip/Gzip+Decompress.swift @@ -3,12 +3,12 @@ import SwiftCompressionUtilities import ZlibShim extension Gzip: Decompressor { - public typealias DecompressionConfiguration = DecompressConfiguration + public typealias ConcreteDecompressionConfiguration = DecompressConfiguration public typealias DecompressionResult = [UInt8]? public func decompress( data: some Collection, - configuration: DecompressConfiguration = .init() + configuration: ConcreteDecompressionConfiguration = .default ) -> DecompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 @@ -35,7 +35,9 @@ extension Gzip: Decompressor { // MARK: Configuration extension Gzip { - public struct DecompressConfiguration: Sendable { + public struct DecompressConfiguration: DecompressionConfiguration { + public static var `default`: Self { .init() } + public let reserveCapacity:Int public init( From 5f4cda1a48e267929f3a38f0bea54e541365f52e Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Wed, 8 Apr 2026 19:25:37 -0500 Subject: [PATCH 09/22] rename `CompressionResult` typealias to `ConcreteCompressionResult` - make `Huffman` conform to `Compressor` --- .../DNABinaryEncoding+Compress+Span.swift | 4 +- .../binary/DNABinaryEncoding+Compress.swift | 6 +- ...DNASingleBlockEncoding+Compress+Span.swift | 2 +- .../DNASingleBlockEncoding+Compress.swift | 6 +- Sources/LZ/lz77/LZ77+Compress+Span.swift | 4 +- Sources/LZ/lz77/LZ77+Compress.swift | 6 +- Sources/Snappy/iwa/IWA+Compress.swift | 4 +- .../RunLengthEncoding+Compress+Span.swift | 4 +- .../RunLengthEncoding+Compress.swift | 6 +- .../CompressionResult.swift | 4 +- .../Compressor.swift | 7 +- .../huffman/Huffman+Compress+Span.swift | 20 ++++ .../techniques/huffman/Huffman+Compress.swift | 96 +++++++++++++++ .../techniques/{ => huffman}/Huffman.swift | 109 ++++-------------- .../Zlib/deflate/DEFLATE+Compress+Span.swift | 2 +- Sources/Zlib/deflate/DEFLATE+Compress.swift | 6 +- Sources/Zlib/gzip/Gzip+Compress+Span.swift | 2 +- Sources/Zlib/gzip/Gzip+Compress.swift | 4 +- 18 files changed, 171 insertions(+), 121 deletions(-) create mode 100644 Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift create mode 100644 Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift rename Sources/SwiftCompressionUtilities/techniques/{ => huffman}/Huffman.swift (66%) diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift index d4b8ab1..baee96d 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress+Span.swift @@ -5,8 +5,8 @@ extension DNABinaryEncoding { public func compress( span: Span, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { - var result = CompressionResult() + ) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() let validBitsInLastByte = span.withUnsafeBufferPointer { compress(buffer: $0, closure: { result.append($0) }) } diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift index 10e3377..b64dedb 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -3,13 +3,13 @@ import SwiftCompressionUtilities extension DNABinaryEncoding: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8] + public typealias ConcreteCompressionResult = [UInt8] public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { - var result = CompressionResult() + ) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() let validBitsInLastByte = data.withContiguousStorageIfAvailable { compress(buffer: $0, closure: { result.append($0) }) } diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift index 112c218..12708b5 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress+Span.swift @@ -5,7 +5,7 @@ extension DNASingleBlockEncoding { public func compress( span: Span, configuration: ConcreteCompressionConfiguration = .default - ) -> CompressionResult { + ) -> ConcreteCompressionResult { return span.withUnsafeBufferPointer { compress(buffer: $0, configuration: configuration) } } } \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift index 19b33ec..5553244 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension DNASingleBlockEncoding: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8:[UInt8]] + public typealias ConcreteCompressionResult = [UInt8:[UInt8]] } extension DNASingleBlockEncoding { @@ -15,14 +15,14 @@ extension DNASingleBlockEncoding { public func compress( data: some Collection, configuration: CompressConfiguration - ) -> CompressionResult { + ) -> ConcreteCompressionResult { return data.withContiguousStorageIfAvailable { compress(buffer: $0, configuration: configuration) } ?? .init() } func compress( buffer: UnsafeBufferPointer, configuration: CompressConfiguration - ) -> CompressionResult { + ) -> ConcreteCompressionResult { let frequencyTable:[UInt8:Int] = CompressionTechnique.buildFrequencyTable(buffer: buffer) var sortedFrequencyTable = frequencyTable.sorted(by: { guard $0.value != $1.value else { return $0.key < $1.key } diff --git a/Sources/LZ/lz77/LZ77+Compress+Span.swift b/Sources/LZ/lz77/LZ77+Compress+Span.swift index e5d897f..ad21730 100644 --- a/Sources/LZ/lz77/LZ77+Compress+Span.swift +++ b/Sources/LZ/lz77/LZ77+Compress+Span.swift @@ -11,8 +11,8 @@ extension LZ77 { public func compress( span: Span, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { - var result = CompressionResult() + ) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() result.reserveCapacity(configuration.reserveCapacity) span.withUnsafeBufferPointer { compress(buffer: $0, closure: { result.append($0) }) diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift index e20da6e..045e776 100644 --- a/Sources/LZ/lz77/LZ77+Compress.swift +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension LZ77: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8] + public typealias ConcreteCompressionResult = [UInt8] // Compress a collection of bytes using the LZ77 technique. /// @@ -14,8 +14,8 @@ extension LZ77: Compressor { public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { - var result = CompressionResult() + ) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() result.reserveCapacity(configuration.reserveCapacity) data.withContiguousStorageIfAvailable { compress(buffer: $0, closure: { result.append($0) }) diff --git a/Sources/Snappy/iwa/IWA+Compress.swift b/Sources/Snappy/iwa/IWA+Compress.swift index 6c92f70..d13ccfe 100644 --- a/Sources/Snappy/iwa/IWA+Compress.swift +++ b/Sources/Snappy/iwa/IWA+Compress.swift @@ -1,7 +1,7 @@ extension IWA { // TODO: finish public typealias ConcreteCompressionConfiguration = Configuration - public typealias CompressionResult = [UInt8] + public typealias ConcreteCompressionResult = [UInt8] /// - Parameters: /// - data: Sequence of bytes to compress. @@ -9,7 +9,7 @@ extension IWA { // TODO: finish public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { + ) -> ConcreteCompressionResult { return .init() } diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift index b91c5f3..e4a32db 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress+Span.swift @@ -5,8 +5,8 @@ extension RunLengthEncoding { public func compress( span: Span, configuration: CompressConfiguration - ) throws(Never) -> CompressionResult { - var result = CompressionResult() + ) throws(Never) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() span.withUnsafeBufferPointer { compress(buffer: $0, closure: compressClosure(closure: { result.append($0) })) } diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift index 2fc6827..0dece7e 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift @@ -3,13 +3,13 @@ import SwiftCompressionUtilities extension RunLengthEncoding: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8] + public typealias ConcreteCompressionResult = [UInt8] public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration - ) -> CompressionResult { - var result = CompressionResult() + ) -> ConcreteCompressionResult { + var result = ConcreteCompressionResult() data.withContiguousStorageIfAvailable { compress(buffer: $0, closure: compressClosure(closure: { result.append($0) })) } diff --git a/Sources/SwiftCompressionUtilities/CompressionResult.swift b/Sources/SwiftCompressionUtilities/CompressionResult.swift index 83f4722..65a20ed 100644 --- a/Sources/SwiftCompressionUtilities/CompressionResult.swift +++ b/Sources/SwiftCompressionUtilities/CompressionResult.swift @@ -1,13 +1,13 @@ public struct CompressionResult: Sendable { public var data:T - public var rootNode:CompressionTechnique.Huffman.Node? + public var rootNode:Huffman.Node? public var frequencyTable:[Int]? public var validBitsInLastByte:UInt8 public init( data: T, - rootNode: CompressionTechnique.Huffman.Node? = nil, + rootNode: Huffman.Node? = nil, frequencyTable: [Int]? = nil, validBitsInLastByte: UInt8 = 8 ) { diff --git a/Sources/SwiftCompressionUtilities/Compressor.swift b/Sources/SwiftCompressionUtilities/Compressor.swift index fb22288..d6be942 100644 --- a/Sources/SwiftCompressionUtilities/Compressor.swift +++ b/Sources/SwiftCompressionUtilities/Compressor.swift @@ -1,17 +1,16 @@ -// MARK: Compressor public protocol Compressor: AnyCompressor, ~Copyable { associatedtype ConcreteCompressionConfiguration:CompressionConfiguration - associatedtype CompressionResult + associatedtype ConcreteCompressionResult associatedtype ConcreteCompressionError:Error = CompressionError func compress( data: some Collection, configuration: ConcreteCompressionConfiguration - ) throws(ConcreteCompressionError) -> CompressionResult + ) throws(ConcreteCompressionError) -> ConcreteCompressionResult func compress( span: Span, configuration: ConcreteCompressionConfiguration - ) throws(ConcreteCompressionError) -> CompressionResult + ) throws(ConcreteCompressionError) -> ConcreteCompressionResult } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift new file mode 100644 index 0000000..bf45fed --- /dev/null +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift @@ -0,0 +1,20 @@ + +extension Huffman { + public func compress( + span: Span, + configuration: CompressConfiguration + ) throws(Never) -> ConcreteCompressionResult { + return span.withUnsafeBufferPointer { buffer in + return compress(buffer: buffer, closure: ({ frequencies, codes, root in + var compressed:[UInt8] = [8] + var vBitsInLastByte:UInt8 = 8 + if let (lastByte, validBitsInLastByte) = translate(buffer: buffer, codes: codes, closure: { compressed.append($0) }) { + compressed[0] = validBitsInLastByte + compressed.append(lastByte) + vBitsInLastByte = validBitsInLastByte + } + return .init(data: compressed, rootNode: root, frequencyTable: frequencies, validBitsInLastByte: vBitsInLastByte) + })) + } + } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift new file mode 100644 index 0000000..6c44223 --- /dev/null +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift @@ -0,0 +1,96 @@ + +extension Huffman: Compressor { + public typealias ConcreteCompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionResult = CompressionResult<[UInt8]>? + + /// Compress a sequence of bytes using the Huffman Coding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to compress. + public func compress( + data: some Collection, + configuration: ConcreteCompressionConfiguration + ) -> ConcreteCompressionResult { + return data.withContiguousStorageIfAvailable { buffer in + return compress(buffer: buffer, closure: ({ frequencies, codes, root in + var compressed:[UInt8] = [8] + var vBitsInLastByte:UInt8 = 8 + if let (lastByte, validBitsInLastByte) = translate(buffer: buffer, codes: codes, closure: { compressed.append($0) }) { + compressed[0] = validBitsInLastByte + compressed.append(lastByte) + vBitsInLastByte = validBitsInLastByte + } + return .init(data: compressed, rootNode: root, frequencyTable: frequencies, validBitsInLastByte: vBitsInLastByte) + })) + } ?? nil + } + + /// Compress a sequence of bytes to a stream using the Huffman Coding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - continuation: The `AsyncStream.Continuation`. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func compress( + data: some Collection, + bufferingPolicy limit: AsyncStream.Continuation.BufferingPolicy = .unbounded + ) -> CompressionResult>? { + // TODO: fix + return data.withContiguousStorageIfAvailable { buffer in + return compress(buffer: buffer) { frequencies, codes, root in + var vBitsInLastByte:UInt8 = 8 + let stream = AsyncStream(bufferingPolicy: limit) { continuation in + if let (lastByte, validBitsInLastByte) = translate(buffer: buffer, codes: codes, closure: { continuation.yield($0) }) { + continuation.yield(lastByte) + vBitsInLastByte = validBitsInLastByte + } + continuation.finish() + } + return CompressionResult(data: stream, rootNode: root, frequencyTable: frequencies, validBitsInLastByte: vBitsInLastByte) + } + } ?? nil + } + + func compress( + buffer: UnsafeBufferPointer, + closure: ([Int], [UInt8:String], Node) -> T + ) -> T? { + var frequencies = Array(repeating: 0, count: Int(UInt8.max-1)) + for byte in buffer { + frequencies[Int(byte)] += 1 + } + guard let root = buildTree(frequencies: frequencies) else { return nil } + var codes = [UInt8:String]() + generateCodes(node: root, codes: &codes) + return closure(frequencies, codes, root) + } + + /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the sum of the code lengths. + func translate( + buffer: UnsafeBufferPointer, + codes: [UInt8:String], + closure: (UInt8) -> Void + ) -> (lastByte: UInt8, validBits: UInt8)? { + var builder = ByteBuilder() + for byte in buffer { + if let tree = codes[byte] { + for char in tree { + if let wrote = builder.write(bit: char == "1") { + closure(wrote) + } + } + } + } + return builder.flush() + } +} + +// MARK: Configuration +extension Huffman { + public struct CompressConfiguration: CompressionConfiguration { + public static var `default`: Self { .init() } + + public init() { + } + } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/techniques/Huffman.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift similarity index 66% rename from Sources/SwiftCompressionUtilities/techniques/Huffman.swift rename to Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift index cb83312..bf8fc24 100644 --- a/Sources/SwiftCompressionUtilities/techniques/Huffman.swift +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift @@ -1,91 +1,26 @@ -extension CompressionTechnique { - /// The Huffman coding compression technique. - /// - /// https://en.wikipedia.org/wiki/Huffman_coding - public enum Huffman { - } -} - -// MARK: Compress -extension CompressionTechnique.Huffman { - /// Compress a sequence of bytes using the Huffman Coding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to compress. - public static func compress(data: some Sequence) -> CompressionResult<[UInt8]>? { - return compress(data: data) { frequencies, codes, root in - var compressed:[UInt8] = [8] - var vBitsInLastByte:UInt8 = 8 - if let (lastByte, validBitsInLastByte) = translate(data: data, codes: codes, closure: { compressed.append($0) }) { - compressed[0] = validBitsInLastByte - compressed.append(lastByte) - vBitsInLastByte = validBitsInLastByte - } - return CompressionResult(data: compressed, rootNode: root, frequencyTable: frequencies, validBitsInLastByte: vBitsInLastByte) - } +/// The Huffman coding compression technique. +/// +/// https://en.wikipedia.org/wiki/Huffman_coding +public enum Huffman: Sendable { + public var algorithm: CompressionAlgorithm { + .huffmanCoding } - /// Compress a sequence of bytes to a stream using the Huffman Coding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - continuation: The `AsyncStream.Continuation`. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public static func compress( - data: some Sequence, - bufferingPolicy limit: AsyncStream.Continuation.BufferingPolicy = .unbounded - ) -> CompressionResult>? { - // TODO: fix - return compress(data: data) { frequencies, codes, root in - var vBitsInLastByte:UInt8 = 8 - let stream = AsyncStream(bufferingPolicy: limit) { continuation in - if let (lastByte, validBitsInLastByte) = translate(data: data, codes: codes, closure: { continuation.yield($0) }) { - continuation.yield(lastByte) - vBitsInLastByte = validBitsInLastByte - } - continuation.finish() - } - return CompressionResult(data: stream, rootNode: root, frequencyTable: frequencies, validBitsInLastByte: vBitsInLastByte) - } - } - - public static func compress(data: some Sequence, closure: ([Int], [UInt8:String], Node) -> T) -> T? { - var frequencies = Array(repeating: 0, count: Int(UInt8.max-1)) - for byte in data { - frequencies[Int(byte)] += 1 - } - guard let root = buildTree(frequencies: frequencies) else { return nil } - var codes = [UInt8:String]() - generateCodes(node: root, codes: &codes) - return closure(frequencies, codes, root) - } - - /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the sum of the code lengths. - public static func translate(data: some Sequence, codes: [UInt8:String], closure: (UInt8) -> Void) -> (lastByte: UInt8, validBits: UInt8)? { - var builder = ByteBuilder() - for byte in data { - if let tree = codes[byte] { - for char in tree { - if let wrote = builder.write(bit: char == "1") { - closure(wrote) - } - } - } - } - return builder.flush() + public var quality: CompressionQuality { + .lossless } } // MARK: Decompress -extension CompressionTechnique.Huffman { +extension Huffman { /// Decompress a sequence of bytes using the Huffman Coding technique. /// /// - Parameters: /// - data: Sequence of bytes to decompress. /// - root: The root Huffman Node. /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func decompress(data: [UInt8], root: Node?) -> [UInt8] { + public func decompress(data: [UInt8], root: Node?) -> [UInt8] { var result = [UInt8]() decompress(data: data, root: root) { result.append($0) } return result @@ -99,7 +34,7 @@ extension CompressionTechnique.Huffman { /// - continuation: The `AsyncStream.Continuation`. /// - Complexity: O(_n_) where _n_ is the length of `data`. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public static func decompress( + public func decompress( data: [UInt8], root: Node?, continuation: AsyncStream.Continuation @@ -110,7 +45,7 @@ extension CompressionTechnique.Huffman { /// Decompress a sequence of bytes using the Huffman Coding technique. /// /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func decompress(data: [UInt8], root: Node?, closure: (UInt8) -> Void) { + public func decompress(data: [UInt8], root: Node?, closure: (UInt8) -> Void) { let countMinusOne = data.count-1 var node = root var index = 1 @@ -145,14 +80,14 @@ extension CompressionTechnique.Huffman { } } -extension CompressionTechnique.Huffman { +extension Huffman { /// Decompress a sequence of bytes using the Huffman Coding technique. /// /// - Parameters: /// - data: Sequence of bytes to decompress. /// - frequencyTable: A Huffman frequency table of characters. // /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the length of `frequencyTable`. // TODO: FIX - public static func decompress(data: [UInt8], frequencyTable: [Int]) -> [UInt8] { + public func decompress(data: [UInt8], frequencyTable: [Int]) -> [UInt8] { guard let root = buildTree(frequencies: frequencyTable) else { return data } return decompress(data: data, root: root) } @@ -165,7 +100,7 @@ extension CompressionTechnique.Huffman { /// - continuation: The `AsyncStream.Continuation`. // /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the length of `frequencyTable`. // TODO: FIX @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public static func decompress( + public func decompress( data: [UInt8], frequencyTable: [Int], continuation: AsyncStream.Continuation @@ -174,12 +109,12 @@ extension CompressionTechnique.Huffman { decompress(data: data, root: root, continuation: continuation) } - public static func decompress(data: [UInt8], frequencyTable: [Int], closure: (UInt8) -> Void) { + public func decompress(data: [UInt8], frequencyTable: [Int], closure: (UInt8) -> Void) { guard let root = buildTree(frequencies: frequencyTable) else { return } decompress(data: data, root: root, closure: closure) } - public static func decompress(data: [UInt8], codes: [[Bool]:UInt8], closure: (UInt8) -> Void) { + public func decompress(data: [UInt8], codes: [[Bool]:UInt8], closure: (UInt8) -> Void) { var code = [Bool]() code.reserveCapacity(3) for bit in data { @@ -193,7 +128,7 @@ extension CompressionTechnique.Huffman { } // MARK: Node -extension CompressionTechnique.Huffman { +extension Huffman { /// A Huffman Node. public final class Node: Comparable, Hashable, Sendable { public static func < (left: Node, right: Node) -> Bool { @@ -230,7 +165,7 @@ extension CompressionTechnique.Huffman { } // MARK: PriorityQueue -extension CompressionTechnique.Huffman { +extension Huffman { public struct PriorityQueue { public var heap:[T] @@ -292,14 +227,14 @@ extension CompressionTechnique.Huffman { } // MARK: Logic -extension CompressionTechnique.Huffman { +extension Huffman { /// Builds a Huffman tree. /// /// - Parameters: /// - frequencies: A universal frequency table. /// - Returns: The root node of the Huffman tree. /// - Complexity: O(?) - static func buildTree(frequencies: [Int]) -> Node? { + func buildTree(frequencies: [Int]) -> Node? { var queue = PriorityQueue() for (char, freq) in frequencies.enumerated() { if freq != 0 { @@ -318,7 +253,7 @@ extension CompressionTechnique.Huffman { /// Generates the binary codes for a node. /// /// - Complexity: O(1). - static func generateCodes(node: Node?, code: String = "", codes: inout [UInt8:String]) { + func generateCodes(node: Node?, code: String = "", codes: inout [UInt8:String]) { guard let node else { return } if let char = node.character { codes[char] = code diff --git a/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift index 5ed5e4f..ca9c44d 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress+Span.swift @@ -5,7 +5,7 @@ extension Deflate { public func compress( span: Span, configuration: ConcreteCompressionConfiguration = .default - ) -> CompressionResult { + ) -> ConcreteCompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } diff --git a/Sources/Zlib/deflate/DEFLATE+Compress.swift b/Sources/Zlib/deflate/DEFLATE+Compress.swift index a91d694..6c7fc5f 100644 --- a/Sources/Zlib/deflate/DEFLATE+Compress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Compress.swift @@ -4,12 +4,12 @@ import ZlibShim extension Deflate: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8]? + public typealias ConcreteCompressionResult = [UInt8]? public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration = .default - ) -> CompressionResult { + ) -> ConcreteCompressionResult { var stream = z_stream() let status = deflateInit_(&stream, level, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } @@ -23,7 +23,7 @@ extension Deflate: Compressor { count: Int, configuration: ConcreteCompressionConfiguration, stream: inout z_stream - ) -> CompressionResult { + ) -> ConcreteCompressionResult { defer { deflateEnd(&stream) } var compressed = [UInt8]() diff --git a/Sources/Zlib/gzip/Gzip+Compress+Span.swift b/Sources/Zlib/gzip/Gzip+Compress+Span.swift index 6d28a10..56d5605 100644 --- a/Sources/Zlib/gzip/Gzip+Compress+Span.swift +++ b/Sources/Zlib/gzip/Gzip+Compress+Span.swift @@ -5,7 +5,7 @@ extension Gzip { public func compress( span: Span, configuration: ConcreteCompressionConfiguration = .default - ) -> CompressionResult { + ) -> ConcreteCompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 let status = deflateInit2_( diff --git a/Sources/Zlib/gzip/Gzip+Compress.swift b/Sources/Zlib/gzip/Gzip+Compress.swift index 015dd37..10c5a90 100644 --- a/Sources/Zlib/gzip/Gzip+Compress.swift +++ b/Sources/Zlib/gzip/Gzip+Compress.swift @@ -4,12 +4,12 @@ import ZlibShim extension Gzip: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias CompressionResult = [UInt8]? + public typealias ConcreteCompressionResult = [UInt8]? public func compress( data: some Collection, configuration: ConcreteCompressionConfiguration = .default - ) -> CompressionResult { + ) -> ConcreteCompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 let status = deflateInit2_( From f4721875de422b612a79363ac7c16b1d91d59c07 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Wed, 8 Apr 2026 19:31:10 -0500 Subject: [PATCH 10/22] rename `DecompressionResult` typealias to `ConcreteDecompressionResult` - `Huffman` now conforms to `Decompressor` --- .../binary/DNABinaryEncoding+Decompress.swift | 6 +- .../DNASingleBlockEncoding+Decompress.swift | 4 +- Sources/LZ/lz77/LZ77+Decompress.swift | 6 +- Sources/Snappy/iwa/IWA+Decompress.swift | 4 +- .../RunLengthEncoding+Decompress.swift | 6 +- .../Decompressor.swift | 4 +- .../techniques/huffman/Huffman+Compress.swift | 2 +- .../huffman/Huffman+Decompress.swift | 142 ++++++++++++++++++ .../techniques/huffman/Huffman.swift | 115 -------------- .../deflate/DEFLATE+Decompress+Span.swift | 2 +- Sources/Zlib/deflate/DEFLATE+Decompress.swift | 6 +- Sources/Zlib/gzip/Gzip+Decompress+Span.swift | 2 +- Sources/Zlib/gzip/Gzip+Decompress.swift | 4 +- 13 files changed, 165 insertions(+), 138 deletions(-) create mode 100644 Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift diff --git a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift index 2fb3f41..f60d7e6 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Decompress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension DNABinaryEncoding: Decompressor { public typealias ConcreteDecompressionConfiguration = CompressConfiguration - public typealias DecompressionResult = [UInt8] + public typealias ConcreteDecompressionResult = [UInt8] /// Decompress a collection of bytes using the DNA binary encoding technique. /// @@ -14,8 +14,8 @@ extension DNABinaryEncoding: Decompressor { public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) -> DecompressionResult { - var result = DecompressionResult() + ) -> ConcreteDecompressionResult { + var result = ConcreteDecompressionResult() decompress(data: data, closure: { result.append($0) }) return result } diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift index fab7841..e884086 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Decompress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension DNASingleBlockEncoding: Decompressor { // TODO: finish public typealias ConcreteDecompressionConfiguration = DecompressConfiguration - public typealias DecompressionResult = [UInt8] + public typealias ConcreteDecompressionResult = [UInt8] /// Decompress a sequence of bytes using the DNA single block encoding technique. /// @@ -12,7 +12,7 @@ extension DNASingleBlockEncoding: Decompressor { // TODO: finish public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { return [] } } diff --git a/Sources/LZ/lz77/LZ77+Decompress.swift b/Sources/LZ/lz77/LZ77+Decompress.swift index b4cc417..363b355 100644 --- a/Sources/LZ/lz77/LZ77+Decompress.swift +++ b/Sources/LZ/lz77/LZ77+Decompress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension LZ77: Decompressor { public typealias ConcreteDecompressionConfiguration = CompressConfiguration - public typealias DecompressionResult = [UInt8] + public typealias ConcreteDecompressionResult = [UInt8] /// Decompress a collection of bytes using the LZ77 technique. /// @@ -14,8 +14,8 @@ extension LZ77: Decompressor { public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) -> DecompressionResult { - var result = DecompressionResult() + ) -> ConcreteDecompressionResult { + var result = ConcreteDecompressionResult() decompress(data: data, closure: { result.append($0) }) result.reserveCapacity(configuration.reserveCapacity) return result diff --git a/Sources/Snappy/iwa/IWA+Decompress.swift b/Sources/Snappy/iwa/IWA+Decompress.swift index efa078d..9635dd8 100644 --- a/Sources/Snappy/iwa/IWA+Decompress.swift +++ b/Sources/Snappy/iwa/IWA+Decompress.swift @@ -1,7 +1,7 @@ extension IWA { // TODO: finish public typealias ConcreteDecompressionConfiguration = Configuration - public typealias DecompressionResult = [UInt8] + public typealias ConcreteDecompressionResult = [UInt8] /// - Parameters: /// - data: Collection of bytes to decompress. @@ -10,7 +10,7 @@ extension IWA { // TODO: finish public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { return .init() } } \ No newline at end of file diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift index 20a3c1d..1c43dea 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Decompress.swift @@ -3,7 +3,7 @@ import SwiftCompressionUtilities extension RunLengthEncoding: Decompressor { public typealias ConcreteDecompressionConfiguration = CompressConfiguration - public typealias DecompressionResult = [UInt8] + public typealias ConcreteDecompressionResult = [UInt8] /// - Parameters: /// - data: Sequence of bytes to decompress. @@ -12,8 +12,8 @@ extension RunLengthEncoding: Decompressor { public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) -> DecompressionResult { - var result = DecompressionResult() + ) -> ConcreteDecompressionResult { + var result = ConcreteDecompressionResult() decompress(data: data, closure: { result.append($0) }) return result } diff --git a/Sources/SwiftCompressionUtilities/Decompressor.swift b/Sources/SwiftCompressionUtilities/Decompressor.swift index 109b3d9..c88f47c 100644 --- a/Sources/SwiftCompressionUtilities/Decompressor.swift +++ b/Sources/SwiftCompressionUtilities/Decompressor.swift @@ -2,7 +2,7 @@ // MARK: Decompressor public protocol Decompressor: AnyDecompressor, ~Copyable { associatedtype ConcreteDecompressionConfiguration:DecompressionConfiguration - associatedtype DecompressionResult + associatedtype ConcreteDecompressionResult /// Decompress a collection of bytes using this technique. /// @@ -11,5 +11,5 @@ public protocol Decompressor: AnyDecompressor, ~Copyable { func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration - ) throws(DecompressionError) -> DecompressionResult + ) throws(DecompressionError) -> ConcreteDecompressionResult } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift index 6c44223..d01ce46 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift @@ -87,7 +87,7 @@ extension Huffman: Compressor { // MARK: Configuration extension Huffman { - public struct CompressConfiguration: CompressionConfiguration { + public struct CompressConfiguration: CompressionConfiguration, DecompressionConfiguration { public static var `default`: Self { .init() } public init() { diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift new file mode 100644 index 0000000..5336fe3 --- /dev/null +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift @@ -0,0 +1,142 @@ + +extension Huffman: Decompressor { + public typealias ConcreteDecompressionConfiguration = DecompressConfiguration + public typealias ConcreteDecompressionResult = [UInt8] + + /// Decompress a sequence of bytes using the Huffman Coding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to decompress. + /// - root: The root Huffman Node. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + configuration: ConcreteDecompressionConfiguration + ) -> ConcreteDecompressionResult { + var result = [UInt8]() + decompress(data: data, root: configuration.root) { result.append($0) } + return result + } + + /// Decompress a sequence of bytes into a stream using the Huffman Coding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to decompress. + /// - root: The root Huffman Node. + /// - continuation: The `AsyncStream.Continuation`. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func decompress( + data: [UInt8], + root: Node?, + continuation: AsyncStream.Continuation + ) { + decompress(data: data, root: root) { continuation.yield($0) } + } + + /// Decompress a sequence of bytes using the Huffman Coding technique. + /// + /// - Complexity: O(_n_) where _n_ is the length of `data`. + func decompress( + data: some Collection, + root: Node?, + closure: (UInt8) -> Void + ) { + let countMinusOne = data.count-1 + var node = root + var index = 1 + while index < countMinusOne { + let bits = data[index].bits + for bit in 0..<8 { + if bits[bit] { + node = node?.right + } else { + node = node?.left + } + if let char = node?.character { + closure(char) + node = root + } + } + index += 1 + } + let validBitsInLastByte = data[0] + let lastBits = data[countMinusOne].bits + for bit in 0.., + frequencyTable: [Int] + ) -> [UInt8] { + guard let root = buildTree(frequencies: frequencyTable) else { return [UInt8](data) } + return decompress(data: data, configuration: .init(root: root)) + } + + /// Decompress a sequence of bytes into a stream using the Huffman Coding technique. + /// + /// - Parameters: + /// - data: Sequence of bytes to decompress. + /// - frequencyTable: A Huffman frequency table of characters. + /// - continuation: The `AsyncStream.Continuation`. + // /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the length of `frequencyTable`. // TODO: FIX + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func decompress( + data: [UInt8], + frequencyTable: [Int], + continuation: AsyncStream.Continuation + ) { + guard let root = buildTree(frequencies: frequencyTable) else { return } + decompress(data: data, root: root, continuation: continuation) + } + + public func decompress(data: [UInt8], frequencyTable: [Int], closure: (UInt8) -> Void) { + guard let root = buildTree(frequencies: frequencyTable) else { return } + decompress(data: data, root: root, closure: closure) + } + + public func decompress(data: [UInt8], codes: [[Bool]:UInt8], closure: (UInt8) -> Void) { + var code = [Bool]() + code.reserveCapacity(3) + for bit in data { + code.append(bit == 1) + if let char = codes[code] { + closure(char) + code.removeAll(keepingCapacity: true) + } + } + } +} + +// MARK: Configuration +extension Huffman { + public struct DecompressConfiguration: DecompressionConfiguration { + public static var `default`: Self { .init() } + + public let root:Node? + + public init( + root: Node? = nil + ) { + self.root = root + } + } +} \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift index bf8fc24..7a546a2 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift @@ -12,121 +12,6 @@ public enum Huffman: Sendable { } } -// MARK: Decompress -extension Huffman { - /// Decompress a sequence of bytes using the Huffman Coding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to decompress. - /// - root: The root Huffman Node. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress(data: [UInt8], root: Node?) -> [UInt8] { - var result = [UInt8]() - decompress(data: data, root: root) { result.append($0) } - return result - } - - /// Decompress a sequence of bytes into a stream using the Huffman Coding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to decompress. - /// - root: The root Huffman Node. - /// - continuation: The `AsyncStream.Continuation`. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func decompress( - data: [UInt8], - root: Node?, - continuation: AsyncStream.Continuation - ) { - decompress(data: data, root: root) { continuation.yield($0) } - } - - /// Decompress a sequence of bytes using the Huffman Coding technique. - /// - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress(data: [UInt8], root: Node?, closure: (UInt8) -> Void) { - let countMinusOne = data.count-1 - var node = root - var index = 1 - while index < countMinusOne { - let bits = data[index].bits - for bit in 0..<8 { - if bits[bit] { - node = node?.right - } else { - node = node?.left - } - if let char = node?.character { - closure(char) - node = root - } - } - index += 1 - } - let validBitsInLastByte = data[0] - let lastBits = data[countMinusOne].bits - for bit in 0.. [UInt8] { - guard let root = buildTree(frequencies: frequencyTable) else { return data } - return decompress(data: data, root: root) - } - - /// Decompress a sequence of bytes into a stream using the Huffman Coding technique. - /// - /// - Parameters: - /// - data: Sequence of bytes to decompress. - /// - frequencyTable: A Huffman frequency table of characters. - /// - continuation: The `AsyncStream.Continuation`. - // /// - Complexity: O(_n_ + _m_) where _n_ is the length of `data` and _m_ is the length of `frequencyTable`. // TODO: FIX - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func decompress( - data: [UInt8], - frequencyTable: [Int], - continuation: AsyncStream.Continuation - ) { - guard let root = buildTree(frequencies: frequencyTable) else { return } - decompress(data: data, root: root, continuation: continuation) - } - - public func decompress(data: [UInt8], frequencyTable: [Int], closure: (UInt8) -> Void) { - guard let root = buildTree(frequencies: frequencyTable) else { return } - decompress(data: data, root: root, closure: closure) - } - - public func decompress(data: [UInt8], codes: [[Bool]:UInt8], closure: (UInt8) -> Void) { - var code = [Bool]() - code.reserveCapacity(3) - for bit in data { - code.append(bit == 1) - if let char = codes[code] { - closure(char) - code.removeAll(keepingCapacity: true) - } - } - } -} - // MARK: Node extension Huffman { /// A Huffman Node. diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift b/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift index 877ec86..b24d891 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress+Span.swift @@ -5,7 +5,7 @@ extension Deflate { public func decompress( span: Span, configuration: ConcreteDecompressionConfiguration = .default - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } diff --git a/Sources/Zlib/deflate/DEFLATE+Decompress.swift b/Sources/Zlib/deflate/DEFLATE+Decompress.swift index 82bc07c..7df7546 100644 --- a/Sources/Zlib/deflate/DEFLATE+Decompress.swift +++ b/Sources/Zlib/deflate/DEFLATE+Decompress.swift @@ -4,12 +4,12 @@ import ZlibShim extension Deflate: Decompressor { public typealias ConcreteDecompressionConfiguration = DecompressConfiguration - public typealias DecompressionResult = [UInt8]? + public typealias ConcreteDecompressionResult = [UInt8]? public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration = .default - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { var stream = z_stream() let status = inflateInit_(&stream, ZLIB_VERSION, Int32(MemoryLayout.size)) guard status == Z_OK else { return nil } @@ -23,7 +23,7 @@ extension Deflate: Decompressor { count: Int, configuration: ConcreteDecompressionConfiguration, stream: inout z_stream - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { defer { inflateEnd(&stream) } var decompressedData = [UInt8]() diff --git a/Sources/Zlib/gzip/Gzip+Decompress+Span.swift b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift index b58da44..0e009ec 100644 --- a/Sources/Zlib/gzip/Gzip+Decompress+Span.swift +++ b/Sources/Zlib/gzip/Gzip+Decompress+Span.swift @@ -5,7 +5,7 @@ extension Gzip { public func decompress( span: Span, configuration: DecompressConfiguration = .default - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 let status = inflateInit2_( diff --git a/Sources/Zlib/gzip/Gzip+Decompress.swift b/Sources/Zlib/gzip/Gzip+Decompress.swift index 4fb03bc..e4b808f 100644 --- a/Sources/Zlib/gzip/Gzip+Decompress.swift +++ b/Sources/Zlib/gzip/Gzip+Decompress.swift @@ -4,12 +4,12 @@ import ZlibShim extension Gzip: Decompressor { public typealias ConcreteDecompressionConfiguration = DecompressConfiguration - public typealias DecompressionResult = [UInt8]? + public typealias ConcreteDecompressionResult = [UInt8]? public func decompress( data: some Collection, configuration: ConcreteDecompressionConfiguration = .default - ) -> DecompressionResult { + ) -> ConcreteDecompressionResult { var stream = z_stream() let windowBits:Int32 = 15 + 16 let status = inflateInit2_( From 011fbe27e8657234d0d9913905900327ed36a348 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Wed, 8 Apr 2026 20:41:17 -0500 Subject: [PATCH 11/22] add Brotli support and... - rename `CompressionSnappy` module to `Snappy` - rename `quality` variable to `compressionQuality` --- Package.swift | 34 ++++++++++--- Sources/Brotli/Brotli+Compress+Span.swift | 28 ++++++++++ Sources/Brotli/Brotli+Compress.swift | 45 ++++++++++++++++ Sources/Brotli/Brotli+Decompress.swift | 51 +++++++++++++++++++ Sources/Brotli/Brotli.swift | 45 ++++++++++++++++ Sources/BrotliShim/module.modulemap | 6 +++ Sources/DNA/binary/DNABinaryEncoding.swift | 2 +- .../singleBlock/DNASingleBlockEncoding.swift | 2 +- Sources/LZ/brotli/Brotli+Compress.swift | 11 ---- Sources/LZ/brotli/Brotli+Decompress.swift | 14 ----- Sources/LZ/brotli/Brotli.swift | 33 ------------ Sources/LZ/lz77/LZ77.swift | 2 +- Sources/Snappy/Snappy.swift | 2 +- Sources/Snappy/framed/SnappyFramed.swift | 2 +- Sources/Snappy/iwa/IWA.swift | 2 +- .../SwiftCompression/SwiftCompression.swift | 8 +-- .../runLength/RunLengthEncoding.swift | 2 +- .../AnyCompressor.swift | 2 +- .../AnyDecompressor.swift | 2 +- .../CompressionAlgorithm.swift | 2 +- .../techniques/huffman/Huffman.swift | 7 ++- Sources/Zlib/deflate/DEFLATE.swift | 2 +- Sources/Zlib/gzip/Gzip.swift | 2 +- Tests/BrotliTests/BrotliTests.swift | 24 +++++++++ Tests/SnappyTests/SnappyTests.swift | 2 +- .../SwiftCompressionTests/HuffmanTests.swift | 6 +-- 26 files changed, 251 insertions(+), 87 deletions(-) create mode 100644 Sources/Brotli/Brotli+Compress+Span.swift create mode 100644 Sources/Brotli/Brotli+Compress.swift create mode 100644 Sources/Brotli/Brotli+Decompress.swift create mode 100644 Sources/Brotli/Brotli.swift create mode 100644 Sources/BrotliShim/module.modulemap delete mode 100644 Sources/LZ/brotli/Brotli+Compress.swift delete mode 100644 Sources/LZ/brotli/Brotli+Decompress.swift delete mode 100644 Sources/LZ/brotli/Brotli.swift create mode 100644 Tests/BrotliTests/BrotliTests.swift diff --git a/Package.swift b/Package.swift index 0babf76..51c05f1 100644 --- a/Package.swift +++ b/Package.swift @@ -16,14 +16,19 @@ let package = Package( targets: ["CompressionDNA"] ), + .library( + name: "Brotli", + targets: ["Brotli"] + ), + .library( name: "SwiftCompressionLZ", targets: ["CompressionLZ"] ), .library( - name: "SwiftCompressionSnappy", - targets: ["CompressionSnappy"] + name: "Snappy", + targets: ["Snappy"] ), .library( @@ -40,15 +45,27 @@ let package = Package( .target( name: "SwiftCompression", dependencies: [ + "Brotli", "SwiftCompressionUtilities", "CompressionDNA", "CompressionLZ", - "CompressionSnappy", + "Snappy", "Zlib" ] ), // MARK: Techniques + .systemLibrary(name: "BrotliShim"), + .target( + name: "Brotli", + dependencies: [ + "SwiftCompressionUtilities", + "BrotliShim" + ], + linkerSettings: [ + .unsafeFlags(["-lbrotlienc", "-lbrotlidec"]), + ] + ), .systemLibrary(name: "ZlibShim"), .target( name: "Zlib", @@ -73,11 +90,10 @@ let package = Package( path: "Sources/LZ" ), .target( - name: "CompressionSnappy", + name: "Snappy", dependencies: [ "SwiftCompressionUtilities" - ], - path: "Sources/Snappy" + ] ), // MARK: Run @@ -93,6 +109,10 @@ let package = Package( name: "SwiftCompressionTests", dependencies: ["SwiftCompression"] ), + .testTarget( + name: "BrotliTests", + dependencies: ["Brotli"] + ), .testTarget( name: "DNATests", dependencies: ["CompressionDNA"] @@ -103,7 +123,7 @@ let package = Package( ), .testTarget( name: "SnappyTests", - dependencies: ["CompressionSnappy"] + dependencies: ["Snappy"] ), .testTarget( name: "ZlibTests", diff --git a/Sources/Brotli/Brotli+Compress+Span.swift b/Sources/Brotli/Brotli+Compress+Span.swift new file mode 100644 index 0000000..6722ce0 --- /dev/null +++ b/Sources/Brotli/Brotli+Compress+Span.swift @@ -0,0 +1,28 @@ + +import BrotliShim + +extension Brotli { + public func compress( + span: Span, + configuration: CompressConfiguration + ) -> ConcreteCompressionResult { + var outLength = span.count + let maxSize = BrotliEncoderMaxCompressedSize(outLength) + let outBuffer = UnsafeMutableBufferPointer.allocate(capacity: maxSize) + outBuffer.initialize(repeating: 0) + defer { outBuffer.deallocate() } + + let success = span.withUnsafeBytes { + return BrotliEncoderCompress( + quality, + windowSize, + BrotliEncoderMode(rawValue: mode), + outLength, + $0.baseAddress, + &outLength, + outBuffer.baseAddress + ) + } + return success == BROTLI_TRUE ? [UInt8](outBuffer.prefix(outLength)) : nil + } +} \ No newline at end of file diff --git a/Sources/Brotli/Brotli+Compress.swift b/Sources/Brotli/Brotli+Compress.swift new file mode 100644 index 0000000..16c3bc0 --- /dev/null +++ b/Sources/Brotli/Brotli+Compress.swift @@ -0,0 +1,45 @@ + +import BrotliShim +import SwiftCompressionUtilities + +extension Brotli: Compressor { + public typealias ConcreteCompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionResult = [UInt8]? + + /// - Parameters: + /// - data: Sequence of bytes to compress. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func compress( + data: some Collection, + configuration: ConcreteCompressionConfiguration + ) -> ConcreteCompressionResult { + var outLength = data.count + let maxSize = BrotliEncoderMaxCompressedSize(outLength) + let outBuffer = UnsafeMutableBufferPointer.allocate(capacity: maxSize) + outBuffer.initialize(repeating: 0) + defer { outBuffer.deallocate() } + + let success = data.withContiguousStorageIfAvailable { + return BrotliEncoderCompress( + quality, + windowSize, + BrotliEncoderMode(rawValue: mode), + outLength, + $0.baseAddress, + &outLength, + outBuffer.baseAddress + ) + } ?? BROTLI_FALSE + return success == BROTLI_TRUE ? [UInt8](outBuffer.prefix(outLength)) : nil + } +} + +// MARK: Configuration +extension Brotli { + public struct CompressConfiguration: CompressionConfiguration, DecompressionConfiguration { + public static var `default`: Self { .init() } + + public init() { + } + } +} \ No newline at end of file diff --git a/Sources/Brotli/Brotli+Decompress.swift b/Sources/Brotli/Brotli+Decompress.swift new file mode 100644 index 0000000..9cbd88b --- /dev/null +++ b/Sources/Brotli/Brotli+Decompress.swift @@ -0,0 +1,51 @@ + +import BrotliShim +import SwiftCompressionUtilities + +extension Brotli: Decompressor { + public typealias ConcreteDecompressionConfiguration = DecompressConfiguration + public typealias ConcreteDecompressionResult = [UInt8]? + + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - closure: Logic to execute when a byte is decompressed. + /// - Complexity: O(_n_) where _n_ is the length of `data`. + public func decompress( + data: some Collection, + configuration: ConcreteDecompressionConfiguration + ) -> ConcreteDecompressionResult { + let compressedCount = data.count + var decodedSize = configuration.estimatedSize + let outBuffer = UnsafeMutableBufferPointer.allocate(capacity: decodedSize) + defer { outBuffer.deallocate() } + let result = data.withContiguousStorageIfAvailable { inPtr in + BrotliDecoderDecompress( + compressedCount, + inPtr.baseAddress!, + &decodedSize, + outBuffer.baseAddress + ) + } ?? BROTLI_DECODER_RESULT_ERROR + if result == BROTLI_DECODER_RESULT_SUCCESS { + return [UInt8](outBuffer.prefix(decodedSize)) + } else if result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT { + return decompress(data: data, configuration: .init(estimatedSize: configuration.estimatedSize * 2)) + } + return nil + } +} + +// MARK: Configuration +extension Brotli { + public struct DecompressConfiguration: DecompressionConfiguration { + public static var `default`: Self { .init(estimatedSize: 32768) } + + public let estimatedSize:Int + + public init( + estimatedSize: Int + ) { + self.estimatedSize = estimatedSize + } + } +} \ No newline at end of file diff --git a/Sources/Brotli/Brotli.swift b/Sources/Brotli/Brotli.swift new file mode 100644 index 0000000..84a95d3 --- /dev/null +++ b/Sources/Brotli/Brotli.swift @@ -0,0 +1,45 @@ + +import BrotliShim +import SwiftCompressionUtilities + +public struct Brotli: Sendable { + public let quality:Int32 + + /// Size of the window. + public let windowSize:Int32 + + public let mode:UInt32 + + public init( + quality: Int32 = BROTLI_DEFAULT_QUALITY, + windowSize: Int32 = BROTLI_DEFAULT_WINDOW, + mode: UInt32 = BROTLI_MODE_GENERIC.rawValue + ) { + self.quality = quality + self.windowSize = windowSize + self.mode = mode + } + + public var algorithm: CompressionAlgorithm { + .brotli(quality: quality, windowSize: windowSize, mode: mode) + } + + public var compressionQuality: CompressionQuality { + .lossless + } +} + +extension CompressionTechnique { + /// The Brotli compression technique. + /// + /// https://github.com/google/brotli + /// + /// https://en.wikipedia.org/wiki/Brotli + public static func brotli( + quality: Int32 = BROTLI_DEFAULT_QUALITY, + windowSize: Int32 = BROTLI_DEFAULT_WINDOW, + mode: UInt32 = BROTLI_MODE_GENERIC.rawValue + ) -> Brotli { + return Brotli(quality: quality, windowSize: windowSize, mode: mode) + } +} \ No newline at end of file diff --git a/Sources/BrotliShim/module.modulemap b/Sources/BrotliShim/module.modulemap new file mode 100644 index 0000000..30f3508 --- /dev/null +++ b/Sources/BrotliShim/module.modulemap @@ -0,0 +1,6 @@ +module BrotliShim [system] { + header "/usr/include/brotli/decode.h" + header "/usr/include/brotli/encode.h" + header "/usr/include/brotli/types.h" + export * +} \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding.swift b/Sources/DNA/binary/DNABinaryEncoding.swift index d3cf371..55edaca 100644 --- a/Sources/DNA/binary/DNABinaryEncoding.swift +++ b/Sources/DNA/binary/DNABinaryEncoding.swift @@ -18,7 +18,7 @@ public struct DNABinaryEncoding: Sendable { .dnaBinaryEncoding(baseBits: baseBits) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift index d8bb853..c1d3c32 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift @@ -9,7 +9,7 @@ public struct DNASingleBlockEncoding: Sendable { .dnaSingleBlockEncoding } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } diff --git a/Sources/LZ/brotli/Brotli+Compress.swift b/Sources/LZ/brotli/Brotli+Compress.swift deleted file mode 100644 index 992e74a..0000000 --- a/Sources/LZ/brotli/Brotli+Compress.swift +++ /dev/null @@ -1,11 +0,0 @@ - -import SwiftCompressionUtilities - -extension Brotli { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - return nil - } -} \ No newline at end of file diff --git a/Sources/LZ/brotli/Brotli+Decompress.swift b/Sources/LZ/brotli/Brotli+Decompress.swift deleted file mode 100644 index c16de79..0000000 --- a/Sources/LZ/brotli/Brotli+Decompress.swift +++ /dev/null @@ -1,14 +0,0 @@ - -import SwiftCompressionUtilities - -extension Brotli { // TODO: finish - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - closure: Logic to execute when a byte is decompressed. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress( - data: some Collection, - closure: (UInt8) -> Void - ) { - } -} \ No newline at end of file diff --git a/Sources/LZ/brotli/Brotli.swift b/Sources/LZ/brotli/Brotli.swift deleted file mode 100644 index a07d640..0000000 --- a/Sources/LZ/brotli/Brotli.swift +++ /dev/null @@ -1,33 +0,0 @@ - -import SwiftCompressionUtilities - -public struct Brotli: Sendable { // TODO: finish - /// Size of the window. - public let windowSize:Int - - /// Predefined dictionary to use. - //public let dictionary:[String:String] - - public init(windowSize: Int = 32768) { - self.windowSize = windowSize - } - - public var algorithm: CompressionAlgorithm { - .brotli(windowSize: windowSize) - } - - public var quality: CompressionQuality { - .lossless - } -} - -extension CompressionTechnique { - /// The Brotli compression technique. - /// - /// https://github.com/google/brotli - /// - /// https://en.wikipedia.org/wiki/Brotli - public static func brotli(windowSize: Int = 32768) -> Brotli { - return Brotli(windowSize: windowSize) - } -} \ No newline at end of file diff --git a/Sources/LZ/lz77/LZ77.swift b/Sources/LZ/lz77/LZ77.swift index c2ea44f..4119cde 100644 --- a/Sources/LZ/lz77/LZ77.swift +++ b/Sources/LZ/lz77/LZ77.swift @@ -17,7 +17,7 @@ public struct LZ77: Sendable { .lz77(windowSize: windowSize, bufferSize: bufferSize, offsetBitWidth: T.bitWidth) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/Snappy/Snappy.swift b/Sources/Snappy/Snappy.swift index 92e9721..427ebe8 100644 --- a/Sources/Snappy/Snappy.swift +++ b/Sources/Snappy/Snappy.swift @@ -14,7 +14,7 @@ public struct Snappy: Sendable { .snappy(windowSize: windowSize) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/Snappy/framed/SnappyFramed.swift b/Sources/Snappy/framed/SnappyFramed.swift index 5de4ab6..271def7 100644 --- a/Sources/Snappy/framed/SnappyFramed.swift +++ b/Sources/Snappy/framed/SnappyFramed.swift @@ -6,7 +6,7 @@ public struct SnappyFramed: Sendable { .snappyFramed } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/Snappy/iwa/IWA.swift b/Sources/Snappy/iwa/IWA.swift index 8b2e0bd..751c8c8 100644 --- a/Sources/Snappy/iwa/IWA.swift +++ b/Sources/Snappy/iwa/IWA.swift @@ -14,7 +14,7 @@ public struct IWA: Compressor, Decompressor { .iwa(version: version) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index d2a5e1f..14382a7 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -1,7 +1,8 @@ +@_exported import Brotli @_exported import CompressionDNA @_exported import CompressionLZ -@_exported import CompressionSnappy +@_exported import Snappy @_exported import Zlib @_exported import SwiftCompressionUtilities @@ -15,9 +16,8 @@ extension CompressionAlgorithm { case .mp3: return nil case .arithmetic: return nil - case .brotli(let windowSize): - //return CompressionTechnique.brotli(windowSize: windowSize) - return nil + case .brotli(let quality, let windowSize, let mode): + return Brotli(quality: quality, windowSize: windowSize, mode: mode) case .bwt: return nil case .deflate(let bufferSize, let level): return Deflate(bufferSize: bufferSize, level: level) diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift index 506e7b7..f3c23e1 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift @@ -22,7 +22,7 @@ public struct RunLengthEncoding: Sendable { .runLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/SwiftCompressionUtilities/AnyCompressor.swift b/Sources/SwiftCompressionUtilities/AnyCompressor.swift index cde221c..b6ea01e 100644 --- a/Sources/SwiftCompressionUtilities/AnyCompressor.swift +++ b/Sources/SwiftCompressionUtilities/AnyCompressor.swift @@ -5,5 +5,5 @@ public protocol AnyCompressor: Sendable, ~Copyable { var algorithm: CompressionAlgorithm { get } /// Quality of the compressed data. - var quality: CompressionQuality { get } + var compressionQuality: CompressionQuality { get } } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/AnyDecompressor.swift b/Sources/SwiftCompressionUtilities/AnyDecompressor.swift index a322606..5991f35 100644 --- a/Sources/SwiftCompressionUtilities/AnyDecompressor.swift +++ b/Sources/SwiftCompressionUtilities/AnyDecompressor.swift @@ -5,5 +5,5 @@ public protocol AnyDecompressor: Sendable, ~Copyable { var algorithm: CompressionAlgorithm { get } /// Quality of the decompressed data. - var quality: CompressionQuality { get } + var compressionQuality: CompressionQuality { get } } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift index 107ddfa..3de8ac3 100644 --- a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift +++ b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift @@ -10,7 +10,7 @@ public enum CompressionAlgorithm: Hashable, Sendable { // data case arithmetic - case brotli(windowSize: Int) + case brotli(quality: Int32, windowSize: Int32, mode: UInt32) /// Burrows–Wheeler transform case bwt case deflate(bufferSize: Int, level: Int32) diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift index 7a546a2..7a1c440 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift +++ b/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift @@ -2,12 +2,15 @@ /// The Huffman coding compression technique. /// /// https://en.wikipedia.org/wiki/Huffman_coding -public enum Huffman: Sendable { +public struct Huffman: Sendable { + public init() { + } + public var algorithm: CompressionAlgorithm { .huffmanCoding } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } diff --git a/Sources/Zlib/deflate/DEFLATE.swift b/Sources/Zlib/deflate/DEFLATE.swift index 488cc6d..6a4027d 100644 --- a/Sources/Zlib/deflate/DEFLATE.swift +++ b/Sources/Zlib/deflate/DEFLATE.swift @@ -23,7 +23,7 @@ public struct Deflate: Sendable { .deflate(bufferSize: bufferSize, level: level) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } \ No newline at end of file diff --git a/Sources/Zlib/gzip/Gzip.swift b/Sources/Zlib/gzip/Gzip.swift index c5fb969..8598e52 100644 --- a/Sources/Zlib/gzip/Gzip.swift +++ b/Sources/Zlib/gzip/Gzip.swift @@ -29,7 +29,7 @@ public struct Gzip: Sendable { .gzip(bufferSize: bufferSize, level: level, memLevel: memLevel, strategy: strategy) } - public var quality: CompressionQuality { + public var compressionQuality: CompressionQuality { .lossless } } \ No newline at end of file diff --git a/Tests/BrotliTests/BrotliTests.swift b/Tests/BrotliTests/BrotliTests.swift new file mode 100644 index 0000000..f1b132c --- /dev/null +++ b/Tests/BrotliTests/BrotliTests.swift @@ -0,0 +1,24 @@ + +#if canImport(FoundationEssentials) + +import FoundationEssentials +import Testing +@testable import Brotli + +struct BrotliTests { + @Test + func brotliTest() { + let brotli = Brotli() + let bro = "What in tarnation fornication trepidation what what what what what the the the the" + let broData = bro.data(using: .utf8)! + let compressed = brotli.compress(data: [UInt8](broData), configuration: .default)! + guard var decompressed = brotli.decompress(data: compressed, configuration: .default) else { + #expect(Bool(false)) + return + } + decompressed.append(0) + #expect(String(cString: decompressed) == bro) + } +} + +#endif \ No newline at end of file diff --git a/Tests/SnappyTests/SnappyTests.swift b/Tests/SnappyTests/SnappyTests.swift index 67e0fe1..b1db768 100644 --- a/Tests/SnappyTests/SnappyTests.swift +++ b/Tests/SnappyTests/SnappyTests.swift @@ -2,7 +2,7 @@ #if compiler(>=6.0) import Testing -@testable import CompressionSnappy +@testable import Snappy @testable import SwiftCompressionUtilities struct SnappyTests { diff --git a/Tests/SwiftCompressionTests/HuffmanTests.swift b/Tests/SwiftCompressionTests/HuffmanTests.swift index 052b0d8..c83f56f 100644 --- a/Tests/SwiftCompressionTests/HuffmanTests.swift +++ b/Tests/SwiftCompressionTests/HuffmanTests.swift @@ -7,7 +7,7 @@ import Testing struct HuffmanTests { static let scoobyDooString = "ruh roh raggy!" static let scoobyDoo:[UInt8] = .init(scoobyDooString.utf8) - static let scoobyDooCompressed = CompressionTechnique.Huffman.compress(data: scoobyDoo)! + static let scoobyDooCompressed = Huffman().compress(data: scoobyDoo, configuration: .default)! @Test func compressHuffman() { let result = Self.scoobyDooCompressed @@ -17,7 +17,7 @@ struct HuffmanTests { @Test func decompressHuffman() { let result = Self.scoobyDooCompressed - let decompressed = CompressionTechnique.Huffman.decompress(data: result.data, root: result.rootNode) + let decompressed = Huffman().decompress(data: result.data, configuration: .init(root: result.rootNode)) #expect(result.validBitsInLastByte == 4) #expect(decompressed == Self.scoobyDoo) } @@ -25,7 +25,7 @@ struct HuffmanTests { @Test func decompressHuffmanOnlyFrequencyTable() { let result = Self.scoobyDooCompressed let table = Self.scoobyDooString.huffmanFrequencyTable() - let decompressed = CompressionTechnique.Huffman.decompress(data: result.data, frequencyTable: table) + let decompressed = Huffman().decompress(data: result.data, frequencyTable: table) #expect(decompressed == Self.scoobyDoo) } } From f36d01cad7541533c823116c40c7f5108b8d6073 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 11:15:22 -0500 Subject: [PATCH 12/22] update README; add default `ConcreteCompressionResult` type --- README.md | 5 +++-- Sources/DNA/binary/DNABinaryEncoding+Compress.swift | 1 - Sources/LZ/lz77/LZ77+Compress.swift | 1 - Sources/Snappy/iwa/IWA+Compress.swift | 3 ++- .../techniques/runLength/RunLengthEncoding+Compress.swift | 1 - Sources/SwiftCompressionUtilities/Compressor.swift | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ef087a7..0451fc1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Standalone compression & decompression library. ### Compression and decompression -- [ ] [Brotli](https://github.com/google/brotli) +- [x] [Brotli](https://github.com/google/brotli) - [ ] [DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) - [x] via zlib - [ ] via libdeflate @@ -34,7 +34,8 @@ The products are broken up into their respective algorithms, but the __SwiftComp - SwiftCompression - SwiftCompressionDNA - SwiftCompressionLZ -- SwiftCompressionSnappy +- Brotli +- Snappy - Zlib ## Contributing diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift index b64dedb..2da5ff1 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -3,7 +3,6 @@ import SwiftCompressionUtilities extension DNABinaryEncoding: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias ConcreteCompressionResult = [UInt8] public func compress( data: some Collection, diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift index 045e776..16ade3b 100644 --- a/Sources/LZ/lz77/LZ77+Compress.swift +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -3,7 +3,6 @@ import SwiftCompressionUtilities extension LZ77: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias ConcreteCompressionResult = [UInt8] // Compress a collection of bytes using the LZ77 technique. /// diff --git a/Sources/Snappy/iwa/IWA+Compress.swift b/Sources/Snappy/iwa/IWA+Compress.swift index d13ccfe..1657289 100644 --- a/Sources/Snappy/iwa/IWA+Compress.swift +++ b/Sources/Snappy/iwa/IWA+Compress.swift @@ -1,7 +1,8 @@ +import SwiftCompressionUtilities + extension IWA { // TODO: finish public typealias ConcreteCompressionConfiguration = Configuration - public typealias ConcreteCompressionResult = [UInt8] /// - Parameters: /// - data: Sequence of bytes to compress. diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift index 0dece7e..711a613 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding+Compress.swift @@ -3,7 +3,6 @@ import SwiftCompressionUtilities extension RunLengthEncoding: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration - public typealias ConcreteCompressionResult = [UInt8] public func compress( data: some Collection, diff --git a/Sources/SwiftCompressionUtilities/Compressor.swift b/Sources/SwiftCompressionUtilities/Compressor.swift index d6be942..a02e8ac 100644 --- a/Sources/SwiftCompressionUtilities/Compressor.swift +++ b/Sources/SwiftCompressionUtilities/Compressor.swift @@ -1,7 +1,7 @@ public protocol Compressor: AnyCompressor, ~Copyable { associatedtype ConcreteCompressionConfiguration:CompressionConfiguration - associatedtype ConcreteCompressionResult + associatedtype ConcreteCompressionResult = [UInt8] associatedtype ConcreteCompressionError:Error = CompressionError func compress( From 24abc9bc03d91b29efa4da9bbf001857246b70a2 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 12:06:04 -0500 Subject: [PATCH 13/22] add Snappy compression --- Package.swift | 10 +- README.md | 2 +- Sources/Snappy/Snappy+Compress+Span.swift | 26 ++++ Sources/Snappy/Snappy+Compress.swift | 113 +++++------------- Sources/Snappy/Snappy+Decompress.swift | 44 +++---- Sources/SnappyShim/module.modulemap | 4 + Tests/SnappyTests/SnappyTests.swift | 16 ++- .../StringProtocol+Hexadecimal.swift | 2 +- 8 files changed, 104 insertions(+), 113 deletions(-) create mode 100644 Sources/Snappy/Snappy+Compress+Span.swift create mode 100644 Sources/SnappyShim/module.modulemap rename Sources/SwiftCompressionUtilities/extensions/StringExtensions.swift => Tests/SnappyTests/StringProtocol+Hexadecimal.swift (78%) diff --git a/Package.swift b/Package.swift index 51c05f1..d0667cb 100644 --- a/Package.swift +++ b/Package.swift @@ -89,10 +89,18 @@ let package = Package( ], path: "Sources/LZ" ), + + .systemLibrary( + name: "SnappyShim" + ), .target( name: "Snappy", dependencies: [ - "SwiftCompressionUtilities" + "SwiftCompressionUtilities", + "SnappyShim" + ], + linkerSettings: [ + .unsafeFlags(["-lsnappy"]) ] ), diff --git a/README.md b/README.md index 0451fc1..823daec 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Standalone compression & decompression library. - [ ] [LZ77 & LZ78](https://en.wikipedia.org/wiki/LZ77_and_LZ78) - [x] [Move-to-front](https://en.wikipedia.org/wiki/Move-to-front_transform) - [x] [Run-Length Encoding](https://en.wikipedia.org/wiki/Run-length_encoding) -- [ ] [Snappy](https://github.com/google/snappy) +- [x] [Snappy](https://github.com/google/snappy) - [ ] [Snappy Framed](https://github.com/google/snappy/blob/main/framing_format.txt) - [ ] [Zstd](https://github.com/facebook/zstd) diff --git a/Sources/Snappy/Snappy+Compress+Span.swift b/Sources/Snappy/Snappy+Compress+Span.swift new file mode 100644 index 0000000..3060f26 --- /dev/null +++ b/Sources/Snappy/Snappy+Compress+Span.swift @@ -0,0 +1,26 @@ + +import SnappyShim +import SwiftCompressionUtilities + +extension Snappy { + public func compress( + span: Span, + configuration: ConcreteCompressionConfiguration + ) -> ConcreteCompressionResult { + let inputSize = span.count + var outputSize = snappy_max_compressed_length(inputSize) + let buffer = UnsafeMutableBufferPointer.allocate(capacity: outputSize) + buffer.initialize(repeating: 0) + defer { buffer.deallocate() } + let result = span.withUnsafeBufferPointer { + snappy_compress( + $0.baseAddress, + inputSize, + buffer.baseAddress, + &outputSize + ) + } + guard result == SNAPPY_OK else { return nil } + return [UInt8](buffer.prefix(outputSize)) + } +} \ No newline at end of file diff --git a/Sources/Snappy/Snappy+Compress.swift b/Sources/Snappy/Snappy+Compress.swift index bdc10b5..68fa7e1 100644 --- a/Sources/Snappy/Snappy+Compress.swift +++ b/Sources/Snappy/Snappy+Compress.swift @@ -1,96 +1,39 @@ +import SnappyShim import SwiftCompressionUtilities -extension Snappy { - public func compress(data: some Collection, closure: (UInt8) -> Void) throws(CompressionError) -> UInt8? { - return nil - } - public func compress(data: some Collection, reserveCapacity: Int) throws -> CompressionResult<[UInt8]> { - throw CompressionError.unsupportedOperation - } -} - -/* -extension Snappy { // TODO: finish - /// - Parameters: - /// - data: Sequence of bytes to compress. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func compress(data: some Collection, closure: (UInt8) -> Void) -> UInt8? { - var index = data.startIndex - while index != data.endIndex { - let (length, matchLength, offset) = longestMatch(data, from: index) - if matchLength == 0 { - let next = data.index(index, offsetBy: length) - compressLiteral(data[index..>(_ data: C, from startIndex: C.Index) -> (length: Int, matchLength: Int, offset: Int) { - let maxLength = 60 - var longestMatchLength = 0 - var offset = 0 - - var length = 0 - var index = startIndex - while length < maxLength && index != data.endIndex { - let starts = data.index(index, offsetBy: -min(length, windowSize), limitedBy: data.startIndex) ?? data.startIndex - let longestMatch = longestCommonPrefix(data, index1: index, index2: starts) - if length > longestMatchLength { - longestMatchLength = longestMatch - offset = data.distance(from: starts, to: index) - } - length += 1 - index = data.index(after: index) +extension Snappy: Compressor { + public typealias ConcreteCompressionConfiguration = CompressConfiguration + public typealias ConcreteCompressionResult = [UInt8]? + + public func compress( + data: some Collection, + configuration: ConcreteCompressionConfiguration + ) -> ConcreteCompressionResult { + let inputSize = data.count + var outputSize = snappy_max_compressed_length(inputSize) + let buffer = UnsafeMutableBufferPointer.allocate(capacity: outputSize) + buffer.initialize(repeating: 0) + defer { buffer.deallocate() } + let result = data.withContiguousStorageIfAvailable { + snappy_compress( + $0.baseAddress, + inputSize, + buffer.baseAddress, + &outputSize + ) } - return (length, longestMatchLength, offset: offset) - } - - func longestCommonPrefix>(_ data: C, index1: C.Index, index2: C.Index) -> Int { - var length = 0 - var index1 = index1 - var index2 = index2 - while index1 != data.endIndex && index2 != data.endIndex && data[index1] == data[index2] { - length += 1 - index1 = data.index(after: index1) - index2 = data.index(after: index2) - } - return length + guard result == SNAPPY_OK else { return nil } + return [UInt8](buffer.prefix(outputSize)) } } -// MARK: Literal +// MARK: Configuration extension Snappy { - func compressLiteral>(_ data: C, closure: (UInt8) -> Void) { - let count = data.count - if count < 60 { - closure(UInt8(count << 2)) - } else { - closure(UInt8(60 << 2)) - closure(UInt8(count)) - } - for value in data { - closure(value) - } - } -} + public struct CompressConfiguration: CompressionConfiguration, DecompressionConfiguration { + public static var `default`: Self { .init() } -// MARK: Copy -extension Snappy { - func compressCopy(length: Int, offset: Int, closure: (UInt8) -> Void) { - if length < 12 && offset < 2048 { - let cmd = UInt8((offset >> 8) << 5 | (length - 4) << 2 | 1) - closure(cmd) - closure(UInt8(offset & 0xFF)) - } else { - closure(UInt8((length - 1) << 2) | 2) - closure(UInt8(offset & 0xFF)) - closure(UInt8((offset >> 8) & 0xFF)) + public init() { } } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Sources/Snappy/Snappy+Decompress.swift b/Sources/Snappy/Snappy+Decompress.swift index 1a4e28b..991bb8e 100644 --- a/Sources/Snappy/Snappy+Decompress.swift +++ b/Sources/Snappy/Snappy+Decompress.swift @@ -1,13 +1,16 @@ import SwiftCompressionUtilities -extension Snappy { +extension Snappy: Decompressor { + public typealias ConcreteDecompressionConfiguration = CompressConfiguration + public typealias ConcreteDecompressionResult = [UInt8] + /// - Parameters: /// - data: Collection of bytes to decompress. /// - reserveCapacity: Ignored. public func decompress( data: some Collection, - reserveCapacity: Int = 0 + configuration: ConcreteDecompressionConfiguration = .default ) throws(DecompressionError) -> [UInt8] { var decompressed = [UInt8]() var index = data.startIndex @@ -17,32 +20,13 @@ extension Snappy { return decompressed } - /// - Parameters: - /// - data: Collection of bytes to decompress. - /// - continuation: Yielding async throwing stream continuation. - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func decompress( - data: some Collection, - continuation: AsyncThrowingStream.Continuation - ) throws(DecompressionError) { - var index = data.startIndex - let length:Int = try decompressLength(data: data, index: &index) - try decompress(data: data, index: &index, amount: length) { continuation.yield($0) } - } - - /// Calling this function directly will throw a DecompressionError.unsupportedOperation. - @available(*, deprecated, message: "Use decompress(data:index:amount:closure:) instead") - public func decompress(data: some Collection, closure: (UInt8) -> Void) throws(DecompressionError) { - throw DecompressionError.unsupportedOperation - } - /// - Parameters: /// - data: Collection of bytes to decompress. /// - index: Where to begin decompressing data. /// - amount: Number of bytes to decompress. /// - closure: Logic to execute when a byte is decompressed. /// - Complexity: O(_n_) where _n_ is the length of `data`. - public func decompress>( + func decompress>( data: C, index: inout C.Index, amount: Int, @@ -199,4 +183,20 @@ extension Snappy { } compressed.formIndex(&index, offsetBy: readBytes) } +} + +// MARK: Stream +extension Snappy { + /// - Parameters: + /// - data: Collection of bytes to decompress. + /// - continuation: Yielding async throwing stream continuation. + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func decompress( + data: some Collection, + continuation: AsyncThrowingStream.Continuation + ) throws(DecompressionError) { + var index = data.startIndex + let length:Int = try decompressLength(data: data, index: &index) + try decompress(data: data, index: &index, amount: length) { continuation.yield($0) } + } } \ No newline at end of file diff --git a/Sources/SnappyShim/module.modulemap b/Sources/SnappyShim/module.modulemap new file mode 100644 index 0000000..05b544a --- /dev/null +++ b/Sources/SnappyShim/module.modulemap @@ -0,0 +1,4 @@ +module SnappyShim [system] { + header "/usr/include/snappy-c.h" + export * +} \ No newline at end of file diff --git a/Tests/SnappyTests/SnappyTests.swift b/Tests/SnappyTests/SnappyTests.swift index b1db768..9896f88 100644 --- a/Tests/SnappyTests/SnappyTests.swift +++ b/Tests/SnappyTests/SnappyTests.swift @@ -8,13 +8,23 @@ import Testing struct SnappyTests { static let wikipedia:String = "Wikipedia is a free, web-based, collaborative, multilingual encyclopedia project." - static let wikipediaHexadecimalLiteral:String = "51f04257696b697065646961206973206120667265652c207765622d62617365642c20636f6c6c61626f7261746976652c206d756c74696c696e6775616c20656e6379636c6f093f1c70726f6a6563742e" + static let wikipediaHexadecimalLiteral:String = "51F05057696B697065646961206973206120667265652C207765622D62617365642C20636F6C6C61626F7261746976652C206D756C74696C696E6775616C20656E6379636C6F70656469612070726F6A6563742E" static let wikipediaHexadecimal:UnfoldSequence = wikipediaHexadecimalLiteral.hexadecimal static let wikipediaCompressedData:[UInt8] = .init(wikipediaHexadecimal) @Test func compressSnappy() throws(CompressionError) { - /*let compressed:[UInt8] = try Self.wikipedia.compressed(using: CompressionTechnique.snappy()).data - #expect(compressed == Self.wikipediaCompressedData)*/ + guard let compressed:[UInt8] = Snappy().compress(span: Self.wikipedia.utf8Span.span, configuration: .init()) else { + #expect(Bool(false)) + return + } + #expect(compressed == Self.wikipediaCompressedData) + + guard var decompressed = try? Snappy().decompress(data: compressed) else { + #expect(Bool(false)) + return + } + decompressed.append(0) + #expect(String(cString: decompressed) == Self.wikipedia) } @Test func decompressSnappyLength() throws(DecompressionError) { diff --git a/Sources/SwiftCompressionUtilities/extensions/StringExtensions.swift b/Tests/SnappyTests/StringProtocol+Hexadecimal.swift similarity index 78% rename from Sources/SwiftCompressionUtilities/extensions/StringExtensions.swift rename to Tests/SnappyTests/StringProtocol+Hexadecimal.swift index f75d27a..a87fdd9 100644 --- a/Sources/SwiftCompressionUtilities/extensions/StringExtensions.swift +++ b/Tests/SnappyTests/StringProtocol+Hexadecimal.swift @@ -1,6 +1,6 @@ extension StringProtocol { - package var hexadecimal: UnfoldSequence { // https://stackoverflow.com/a/43360864 + var hexadecimal: UnfoldSequence { // https://stackoverflow.com/a/43360864 sequence(state: startIndex) { startIndex in guard startIndex < endIndex else { return nil } let endIndex = index(startIndex, offsetBy: 2, limitedBy: endIndex) ?? endIndex From 6a70163acdf725c2b4263102882febce5cb9e9d9 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 12:13:51 -0500 Subject: [PATCH 14/22] remove compression technique convenience extensions --- Sources/Brotli/Brotli.swift | 20 +++-------- Sources/DNA/binary/DNABinaryEncoding.swift | 12 ------- .../singleBlock/DNASingleBlockEncoding.swift | 7 ---- Sources/LZ/LZ78.swift | 6 ---- Sources/LZ/lz77/LZ77.swift | 34 +++++-------------- Sources/Snappy/Snappy.swift | 24 ++++--------- Sources/Snappy/framed/SnappyFramed.swift | 14 +++----- Sources/Snappy/iwa/IWA.swift | 13 ++----- .../SwiftCompression/SwiftCompression.swift | 13 ++++--- .../runLength/RunLengthEncoding.swift | 9 ----- .../CompressionAlgorithm.swift | 2 +- 11 files changed, 36 insertions(+), 118 deletions(-) diff --git a/Sources/Brotli/Brotli.swift b/Sources/Brotli/Brotli.swift index 84a95d3..d2261b0 100644 --- a/Sources/Brotli/Brotli.swift +++ b/Sources/Brotli/Brotli.swift @@ -2,6 +2,11 @@ import BrotliShim import SwiftCompressionUtilities +/// The Brotli compression technique. +/// +/// https://github.com/google/brotli +/// +/// https://en.wikipedia.org/wiki/Brotli public struct Brotli: Sendable { public let quality:Int32 @@ -27,19 +32,4 @@ public struct Brotli: Sendable { public var compressionQuality: CompressionQuality { .lossless } -} - -extension CompressionTechnique { - /// The Brotli compression technique. - /// - /// https://github.com/google/brotli - /// - /// https://en.wikipedia.org/wiki/Brotli - public static func brotli( - quality: Int32 = BROTLI_DEFAULT_QUALITY, - windowSize: Int32 = BROTLI_DEFAULT_WINDOW, - mode: UInt32 = BROTLI_MODE_GENERIC.rawValue - ) -> Brotli { - return Brotli(quality: quality, windowSize: windowSize, mode: mode) - } } \ No newline at end of file diff --git a/Sources/DNA/binary/DNABinaryEncoding.swift b/Sources/DNA/binary/DNABinaryEncoding.swift index 55edaca..f176d5c 100644 --- a/Sources/DNA/binary/DNABinaryEncoding.swift +++ b/Sources/DNA/binary/DNABinaryEncoding.swift @@ -30,16 +30,4 @@ public struct DNABinaryEncoding: Sendable { } return reversed } -} - -extension CompressionTechnique { - /// The DNA binary encoding compression technique. - public static func dnaBinaryEncoding(baseBits: [UInt8:[Bool]] = [ - 65: [false, false], // A - 67: [false, true], // C - 71: [true, false], // G - 84: [true, true] // T - ]) -> DNABinaryEncoding { - return DNABinaryEncoding(baseBits: baseBits) - } } \ No newline at end of file diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift index c1d3c32..75f8561 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding.swift @@ -15,11 +15,4 @@ public struct DNASingleBlockEncoding: Sendable { public init() { } -} - -extension CompressionTechnique { - /// The DNA single block encoding compression technique. - /// - /// https://www.mdpi.com/1999-4893/13/4/99 - public static let dnaSingleBlockEncoding = DNASingleBlockEncoding() } \ No newline at end of file diff --git a/Sources/LZ/LZ78.swift b/Sources/LZ/LZ78.swift index 8ec0cfd..e9cc4c0 100644 --- a/Sources/LZ/LZ78.swift +++ b/Sources/LZ/LZ78.swift @@ -1,9 +1,3 @@ -// -// LZ78.swift -// -// -// Created by Evan Anderson on 12/25/24. -// import SwiftCompressionUtilities diff --git a/Sources/LZ/lz77/LZ77.swift b/Sources/LZ/lz77/LZ77.swift index 4119cde..9580b1b 100644 --- a/Sources/LZ/lz77/LZ77.swift +++ b/Sources/LZ/lz77/LZ77.swift @@ -1,6 +1,12 @@ import SwiftCompressionUtilities +/// The LZ77 compression technique. +/// +/// - Generics: +/// - T: Integer type the offset is encoded as. +/// +/// https://en.wikipedia.org/wiki/LZ77_and_LZ78 public struct LZ77: Sendable { /// Size of the window. public let windowSize:Int @@ -8,6 +14,9 @@ public struct LZ77: Sendable { /// Size of the buffer. public let bufferSize:Int + /// - Parameters: + /// - windowSize: Size of the sliding window, measured in bytes. + /// - bufferSize: Size of the buffer, measured in bytes. public init(windowSize: Int, bufferSize: Int) { self.windowSize = windowSize self.bufferSize = bufferSize @@ -20,29 +29,4 @@ public struct LZ77: Sendable { public var compressionQuality: CompressionQuality { .lossless } -} - -extension CompressionTechnique { - /// The LZ77 compression technique. - /// - /// - Parameters: - /// - T: Integer type the offset is encoded as. Default is `UInt16`. - /// - windowSize: Size of the sliding window, measured in bytes. - /// - bufferSize: Size of the buffer, measured in bytes. - /// - /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 - public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { - return LZ77(windowSize: windowSize, bufferSize: bufferSize) - } - - /// The LZ77 compression technique where the offset is encoded as a `UInt16`. - /// - /// - Parameters: - /// - windowSize: Size of the sliding window, measured in bytes. - /// - bufferSize: Size of the buffer, measured in bytes. - /// - /// https://en.wikipedia.org/wiki/LZ77_and_LZ78 - public static func lz77(windowSize: Int, bufferSize: Int) -> LZ77 { - return LZ77(windowSize: windowSize, bufferSize: bufferSize) - } } \ No newline at end of file diff --git a/Sources/Snappy/Snappy.swift b/Sources/Snappy/Snappy.swift index 427ebe8..df17a61 100644 --- a/Sources/Snappy/Snappy.swift +++ b/Sources/Snappy/Snappy.swift @@ -1,31 +1,21 @@ import SwiftCompressionUtilities +/// The Snappy (Zippy) compression technique. +/// +/// https://en.wikipedia.org/wiki/Snappy_(compression) +/// +/// https://github.com/google/snappy public struct Snappy: Sendable { - /// Size of the window. - public let windowSize:Int - - public init(windowSize: Int = Int(UInt16.max)) { - self.windowSize = windowSize + public init() { } public var algorithm: CompressionAlgorithm { - .snappy(windowSize: windowSize) + .snappy } public var compressionQuality: CompressionQuality { .lossless } -} - -extension CompressionTechnique { - /// The Snappy (Zippy) compression technique. - /// - /// https://en.wikipedia.org/wiki/Snappy_(compression) - /// - /// https://github.com/google/snappy - public static func snappy(windowSize: Int = Int(UInt16.max)) -> Snappy { - return Snappy(windowSize: windowSize) - } } \ No newline at end of file diff --git a/Sources/Snappy/framed/SnappyFramed.swift b/Sources/Snappy/framed/SnappyFramed.swift index 271def7..9c24d5d 100644 --- a/Sources/Snappy/framed/SnappyFramed.swift +++ b/Sources/Snappy/framed/SnappyFramed.swift @@ -1,6 +1,11 @@ import SwiftCompressionUtilities +/// The Snappy Framed compression technique. +/// +/// https://en.wikipedia.org/wiki/Snappy_(compression)#Framing_format +/// +/// https://github.com/google/snappy public struct SnappyFramed: Sendable { public var algorithm: CompressionAlgorithm { .snappyFramed @@ -9,13 +14,4 @@ public struct SnappyFramed: Sendable { public var compressionQuality: CompressionQuality { .lossless } -} - -extension CompressionTechnique { - /// The Snappy Framed compression technique. - /// - /// https://en.wikipedia.org/wiki/Snappy_(compression)#Framing_format - /// - /// https://github.com/google/snappy - public static let snappyFramed:SnappyFramed = SnappyFramed() } \ No newline at end of file diff --git a/Sources/Snappy/iwa/IWA.swift b/Sources/Snappy/iwa/IWA.swift index 751c8c8..a41c5f1 100644 --- a/Sources/Snappy/iwa/IWA.swift +++ b/Sources/Snappy/iwa/IWA.swift @@ -1,7 +1,9 @@ import SwiftCompressionUtilities -/// iWork Archive compressor/decompressor. +/// The iWork Archive (iwa) compression technique. +/// +/// https://en.wikipedia.org/wiki/IWork public struct IWA: Compressor, Decompressor { /// Version of the iWork Archive to use. public let version:IWAVersion @@ -19,15 +21,6 @@ public struct IWA: Compressor, Decompressor { } } -extension CompressionTechnique { - /// The iWork Archive (iwa) compression technique. - /// - /// https://en.wikipedia.org/wiki/IWork - public static func iwa(version: IWAVersion) -> IWA { - return IWA(version: version) - } -} - // MARK: Configuration extension IWA { public struct Configuration: CompressionConfiguration, DecompressionConfiguration { diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index 14382a7..599bea0 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -46,10 +46,9 @@ extension CompressionAlgorithm { case .lzw: return nil case .mtf: return nil case .runLengthEncoding(let minRun, let alwaysIncludeRunCount): - return CompressionTechnique.runLength(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) - case .snappy(let windowSize): - //return CompressionTechnique.snappy(windowSize: windowSize) - return nil + return RunLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) + case .snappy: + return Snappy() case .snappyFramed: //return CompressionTechnique.snappyFramed return nil @@ -72,9 +71,9 @@ extension CompressionAlgorithm { case .fibonacci: return nil case .dnaBinaryEncoding(let baseBits): - return CompressionTechnique.dnaBinaryEncoding(baseBits: baseBits) + return DNABinaryEncoding(baseBits: baseBits) case .dnaSingleBlockEncoding: - return CompressionTechnique.dnaSingleBlockEncoding + return DNASingleBlockEncoding() case .boringSSL: return nil @@ -83,7 +82,7 @@ extension CompressionAlgorithm { case .mpeg: return nil case .iwa(let version): - return CompressionTechnique.iwa(version: version) + return IWA(version: version) @unknown default: return nil } diff --git a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift index f3c23e1..178d7ea 100644 --- a/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift +++ b/Sources/SwiftCompression/techniques/runLength/RunLengthEncoding.swift @@ -25,13 +25,4 @@ public struct RunLengthEncoding: Sendable { public var compressionQuality: CompressionQuality { .lossless } -} - -extension CompressionTechnique { - /// The Run-length encoding compression technique. - /// - /// https://en.wikipedia.org/wiki/Run-length_encoding - public static func runLength(minRun: Int, alwaysIncludeRunCount: Bool) -> RunLengthEncoding { - return RunLengthEncoding(minRun: minRun, alwaysIncludeRunCount: alwaysIncludeRunCount) - } } \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift index 3de8ac3..4e1dd32 100644 --- a/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift +++ b/Sources/SwiftCompressionUtilities/CompressionAlgorithm.swift @@ -23,7 +23,7 @@ public enum CompressionAlgorithm: Hashable, Sendable { case mtf case runLengthEncoding(minRun: Int, alwaysIncludeRunCount: Bool) /// AKA Zippy - case snappy(windowSize: Int) + case snappy /// AKA Zippy Framed case snappyFramed case zstd From 37ef73565a7a77aef50b9b9a7def781952aaa624 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 13:51:32 -0500 Subject: [PATCH 15/22] initialize brotli `outBuffer` after allocation --- Sources/Brotli/Brotli+Decompress.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Brotli/Brotli+Decompress.swift b/Sources/Brotli/Brotli+Decompress.swift index 9cbd88b..bdf4239 100644 --- a/Sources/Brotli/Brotli+Decompress.swift +++ b/Sources/Brotli/Brotli+Decompress.swift @@ -17,6 +17,7 @@ extension Brotli: Decompressor { let compressedCount = data.count var decodedSize = configuration.estimatedSize let outBuffer = UnsafeMutableBufferPointer.allocate(capacity: decodedSize) + outBuffer.initialize(repeating: 0) defer { outBuffer.deallocate() } let result = data.withContiguousStorageIfAvailable { inPtr in BrotliDecoderDecompress( From 14f0913874070529bc8017b60021c941d2dd8cfa Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 13:59:36 -0500 Subject: [PATCH 16/22] minor brotli decompress change --- Sources/Brotli/Brotli+Decompress.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Brotli/Brotli+Decompress.swift b/Sources/Brotli/Brotli+Decompress.swift index bdf4239..50e5302 100644 --- a/Sources/Brotli/Brotli+Decompress.swift +++ b/Sources/Brotli/Brotli+Decompress.swift @@ -19,10 +19,10 @@ extension Brotli: Decompressor { let outBuffer = UnsafeMutableBufferPointer.allocate(capacity: decodedSize) outBuffer.initialize(repeating: 0) defer { outBuffer.deallocate() } - let result = data.withContiguousStorageIfAvailable { inPtr in + let result = data.withContiguousStorageIfAvailable { BrotliDecoderDecompress( compressedCount, - inPtr.baseAddress!, + $0.baseAddress, &decodedSize, outBuffer.baseAddress ) From 68b43cdebebcfb51b291b45dc045531c3a379240 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:15:00 -0500 Subject: [PATCH 17/22] move huffman to its own module --- Package.swift | 47 +++++++------------ .../CompressionResult.swift | 0 .../Huffman+Compress+Span.swift | 0 .../Huffman+Compress.swift | 2 + .../Huffman+Decompress.swift | 2 + .../huffman => Huffman}/Huffman.swift | 2 + .../SwiftCompression/SwiftCompression.swift | 1 + 7 files changed, 24 insertions(+), 30 deletions(-) rename Sources/{SwiftCompressionUtilities => Huffman}/CompressionResult.swift (100%) rename Sources/{SwiftCompressionUtilities/techniques/huffman => Huffman}/Huffman+Compress+Span.swift (100%) rename Sources/{SwiftCompressionUtilities/techniques/huffman => Huffman}/Huffman+Compress.swift (99%) rename Sources/{SwiftCompressionUtilities/techniques/huffman => Huffman}/Huffman+Decompress.swift (99%) rename Sources/{SwiftCompressionUtilities/techniques/huffman => Huffman}/Huffman.swift (99%) diff --git a/Package.swift b/Package.swift index d0667cb..695bf7f 100644 --- a/Package.swift +++ b/Package.swift @@ -6,35 +6,14 @@ let package = Package( name: "swift-compression", // MARK: Products products: [ - .library( - name: "SwiftCompression", - targets: ["SwiftCompression"] - ), - - .library( - name: "SwiftCompressionDNA", - targets: ["CompressionDNA"] - ), - - .library( - name: "Brotli", - targets: ["Brotli"] - ), - - .library( - name: "SwiftCompressionLZ", - targets: ["CompressionLZ"] - ), - - .library( - name: "Snappy", - targets: ["Snappy"] - ), + .library(name: "SwiftCompression", targets: ["SwiftCompression"]), - .library( - name: "Zlib", - targets: ["Zlib"] - ), + .library(name: "Brotli", targets: ["Brotli"]), + .library(name: "SwiftCompressionDNA", targets: ["CompressionDNA"]), + .library(name: "SwiftCompressionLZ", targets: ["CompressionLZ"]), + .library(name: "Huffman", targets: ["Huffman"]), + .library(name: "Snappy", targets: ["Snappy"]), + .library(name: "Zlib", targets: ["Zlib"]) ], // MARK: Targets targets: [ @@ -46,11 +25,12 @@ let package = Package( name: "SwiftCompression", dependencies: [ "Brotli", - "SwiftCompressionUtilities", "CompressionDNA", "CompressionLZ", + "Huffman", "Snappy", - "Zlib" + "Zlib", + "SwiftCompressionUtilities", ] ), @@ -90,6 +70,13 @@ let package = Package( path: "Sources/LZ" ), + .target( + name: "Huffman", + dependencies: [ + "SwiftCompressionUtilities" + ] + ), + .systemLibrary( name: "SnappyShim" ), diff --git a/Sources/SwiftCompressionUtilities/CompressionResult.swift b/Sources/Huffman/CompressionResult.swift similarity index 100% rename from Sources/SwiftCompressionUtilities/CompressionResult.swift rename to Sources/Huffman/CompressionResult.swift diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift b/Sources/Huffman/Huffman+Compress+Span.swift similarity index 100% rename from Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress+Span.swift rename to Sources/Huffman/Huffman+Compress+Span.swift diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift b/Sources/Huffman/Huffman+Compress.swift similarity index 99% rename from Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift rename to Sources/Huffman/Huffman+Compress.swift index d01ce46..d7290e3 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Compress.swift +++ b/Sources/Huffman/Huffman+Compress.swift @@ -1,4 +1,6 @@ +import SwiftCompressionUtilities + extension Huffman: Compressor { public typealias ConcreteCompressionConfiguration = CompressConfiguration public typealias ConcreteCompressionResult = CompressionResult<[UInt8]>? diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift b/Sources/Huffman/Huffman+Decompress.swift similarity index 99% rename from Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift rename to Sources/Huffman/Huffman+Decompress.swift index 5336fe3..38ffb8a 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman+Decompress.swift +++ b/Sources/Huffman/Huffman+Decompress.swift @@ -1,4 +1,6 @@ +import SwiftCompressionUtilities + extension Huffman: Decompressor { public typealias ConcreteDecompressionConfiguration = DecompressConfiguration public typealias ConcreteDecompressionResult = [UInt8] diff --git a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift b/Sources/Huffman/Huffman.swift similarity index 99% rename from Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift rename to Sources/Huffman/Huffman.swift index 7a1c440..f0681a0 100644 --- a/Sources/SwiftCompressionUtilities/techniques/huffman/Huffman.swift +++ b/Sources/Huffman/Huffman.swift @@ -1,4 +1,6 @@ +import SwiftCompressionUtilities + /// The Huffman coding compression technique. /// /// https://en.wikipedia.org/wiki/Huffman_coding diff --git a/Sources/SwiftCompression/SwiftCompression.swift b/Sources/SwiftCompression/SwiftCompression.swift index 599bea0..66a1e85 100644 --- a/Sources/SwiftCompression/SwiftCompression.swift +++ b/Sources/SwiftCompression/SwiftCompression.swift @@ -2,6 +2,7 @@ @_exported import Brotli @_exported import CompressionDNA @_exported import CompressionLZ +@_exported import Huffman @_exported import Snappy @_exported import Zlib @_exported import SwiftCompressionUtilities From 169f04759191ff5af70caff1b88ca685fa5f5828 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:26:06 -0500 Subject: [PATCH 18/22] move `ByteBuilder` to its own module --- Package.swift | 9 ++++++--- .../ByteBuilder.swift | 3 ++- .../extensions => ByteBuilder}/IntExtensions.swift | 0 Sources/DNA/binary/DNABinaryEncoding+Compress.swift | 1 + Sources/Huffman/Huffman+Compress.swift | 1 + Sources/Huffman/Huffman+Decompress.swift | 1 + Sources/LZ/lz77/LZ77+Compress.swift | 1 + Sources/Snappy/Snappy+Decompress.swift | 1 + 8 files changed, 13 insertions(+), 4 deletions(-) rename Sources/{SwiftCompressionUtilities => ByteBuilder}/ByteBuilder.swift (99%) rename Sources/{SwiftCompressionUtilities/extensions => ByteBuilder}/IntExtensions.swift (100%) diff --git a/Package.swift b/Package.swift index 695bf7f..bab8fc7 100644 --- a/Package.swift +++ b/Package.swift @@ -17,9 +17,8 @@ let package = Package( ], // MARK: Targets targets: [ - .target( - name: "SwiftCompressionUtilities" - ), + .target(name: "ByteBuilder"), + .target(name: "SwiftCompressionUtilities"), .target( name: "SwiftCompression", @@ -58,6 +57,7 @@ let package = Package( .target( name: "CompressionDNA", dependencies: [ + "ByteBuilder", "SwiftCompressionUtilities" ], path: "Sources/DNA" @@ -65,6 +65,7 @@ let package = Package( .target( name: "CompressionLZ", dependencies: [ + "ByteBuilder", "SwiftCompressionUtilities" ], path: "Sources/LZ" @@ -73,6 +74,7 @@ let package = Package( .target( name: "Huffman", dependencies: [ + "ByteBuilder", "SwiftCompressionUtilities" ] ), @@ -83,6 +85,7 @@ let package = Package( .target( name: "Snappy", dependencies: [ + "ByteBuilder", "SwiftCompressionUtilities", "SnappyShim" ], diff --git a/Sources/SwiftCompressionUtilities/ByteBuilder.swift b/Sources/ByteBuilder/ByteBuilder.swift similarity index 99% rename from Sources/SwiftCompressionUtilities/ByteBuilder.swift rename to Sources/ByteBuilder/ByteBuilder.swift index 52a08c8..e1d6fbd 100644 --- a/Sources/SwiftCompressionUtilities/ByteBuilder.swift +++ b/Sources/ByteBuilder/ByteBuilder.swift @@ -117,6 +117,7 @@ public struct ByteBuilder { bits = (false, false, false, false, false, false, false, false) } } +/* // MARK: Stream & Data Builder extension CompressionTechnique { @@ -167,4 +168,4 @@ extension CompressionTechnique { builder.flush(into: stream) } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/extensions/IntExtensions.swift b/Sources/ByteBuilder/IntExtensions.swift similarity index 100% rename from Sources/SwiftCompressionUtilities/extensions/IntExtensions.swift rename to Sources/ByteBuilder/IntExtensions.swift diff --git a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift index 2da5ff1..79aecdc 100644 --- a/Sources/DNA/binary/DNABinaryEncoding+Compress.swift +++ b/Sources/DNA/binary/DNABinaryEncoding+Compress.swift @@ -1,4 +1,5 @@ +import ByteBuilder import SwiftCompressionUtilities extension DNABinaryEncoding: Compressor { diff --git a/Sources/Huffman/Huffman+Compress.swift b/Sources/Huffman/Huffman+Compress.swift index d7290e3..0e86752 100644 --- a/Sources/Huffman/Huffman+Compress.swift +++ b/Sources/Huffman/Huffman+Compress.swift @@ -1,4 +1,5 @@ +import ByteBuilder import SwiftCompressionUtilities extension Huffman: Compressor { diff --git a/Sources/Huffman/Huffman+Decompress.swift b/Sources/Huffman/Huffman+Decompress.swift index 38ffb8a..e18974b 100644 --- a/Sources/Huffman/Huffman+Decompress.swift +++ b/Sources/Huffman/Huffman+Decompress.swift @@ -1,4 +1,5 @@ +import ByteBuilder import SwiftCompressionUtilities extension Huffman: Decompressor { diff --git a/Sources/LZ/lz77/LZ77+Compress.swift b/Sources/LZ/lz77/LZ77+Compress.swift index 16ade3b..916f088 100644 --- a/Sources/LZ/lz77/LZ77+Compress.swift +++ b/Sources/LZ/lz77/LZ77+Compress.swift @@ -1,4 +1,5 @@ +import ByteBuilder import SwiftCompressionUtilities extension LZ77: Compressor { diff --git a/Sources/Snappy/Snappy+Decompress.swift b/Sources/Snappy/Snappy+Decompress.swift index 991bb8e..3fcc7d4 100644 --- a/Sources/Snappy/Snappy+Decompress.swift +++ b/Sources/Snappy/Snappy+Decompress.swift @@ -1,4 +1,5 @@ +import ByteBuilder import SwiftCompressionUtilities extension Snappy: Decompressor { From 81152722a8ca74bd15fdec7cea05d2af5d7b6996 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:31:55 -0500 Subject: [PATCH 19/22] move building frequency tables to its own module --- Package.swift | 38 ++----- .../DNASingleBlockEncoding+Compress.swift | 3 +- .../FrequencyTables/BuildFrequencyTable.swift | 91 +++++++++++++++++ .../CompressionTechnique.swift | 99 +------------------ 4 files changed, 103 insertions(+), 128 deletions(-) create mode 100644 Sources/FrequencyTables/BuildFrequencyTable.swift diff --git a/Package.swift b/Package.swift index bab8fc7..be7d952 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,7 @@ let package = Package( // MARK: Targets targets: [ .target(name: "ByteBuilder"), + .target(name: "FrequencyTables"), .target(name: "SwiftCompressionUtilities"), .target( @@ -58,6 +59,7 @@ let package = Package( name: "CompressionDNA", dependencies: [ "ByteBuilder", + "FrequencyTables", "SwiftCompressionUtilities" ], path: "Sources/DNA" @@ -79,9 +81,7 @@ let package = Package( ] ), - .systemLibrary( - name: "SnappyShim" - ), + .systemLibrary(name: "SnappyShim"), .target( name: "Snappy", dependencies: [ @@ -103,32 +103,12 @@ let package = Package( ), // MARK: Unit tests - .testTarget( - name: "SwiftCompressionTests", - dependencies: ["SwiftCompression"] - ), - .testTarget( - name: "BrotliTests", - dependencies: ["Brotli"] - ), - .testTarget( - name: "DNATests", - dependencies: ["CompressionDNA"] - ), - .testTarget( - name: "LZTests", - dependencies: ["CompressionLZ"] - ), - .testTarget( - name: "SnappyTests", - dependencies: ["Snappy"] - ), - .testTarget( - name: "ZlibTests", - dependencies: [ - "Zlib" - ] - ) + .testTarget(name: "SwiftCompressionTests", dependencies: ["SwiftCompression"]), + .testTarget(name: "BrotliTests", dependencies: ["Brotli"]), + .testTarget(name: "DNATests", dependencies: ["CompressionDNA"]), + .testTarget(name: "LZTests", dependencies: ["CompressionLZ"]), + .testTarget(name: "SnappyTests", dependencies: ["Snappy"]), + .testTarget(name: "ZlibTests", dependencies: ["Zlib"]) ] ) diff --git a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift index 5553244..712d770 100644 --- a/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift +++ b/Sources/DNA/singleBlock/DNASingleBlockEncoding+Compress.swift @@ -1,4 +1,5 @@ +import FrequencyTables import SwiftCompressionUtilities extension DNASingleBlockEncoding: Compressor { @@ -23,7 +24,7 @@ extension DNASingleBlockEncoding { buffer: UnsafeBufferPointer, configuration: CompressConfiguration ) -> ConcreteCompressionResult { - let frequencyTable:[UInt8:Int] = CompressionTechnique.buildFrequencyTable(buffer: buffer) + let frequencyTable:[UInt8:Int] = buildFrequencyTable(buffer: buffer) var sortedFrequencyTable = frequencyTable.sorted(by: { guard $0.value != $1.value else { return $0.key < $1.key } return $0.value > $1.value diff --git a/Sources/FrequencyTables/BuildFrequencyTable.swift b/Sources/FrequencyTables/BuildFrequencyTable.swift new file mode 100644 index 0000000..df0cbcd --- /dev/null +++ b/Sources/FrequencyTables/BuildFrequencyTable.swift @@ -0,0 +1,91 @@ + +/// Creates a universal frequency table from a sequence of raw bytes. +/// +/// - Parameters: +/// - data: Sequence of raw bytes. +/// - Returns: A universal frequency table. +/// - Complexity: O(_n_) where _n_ is the length of `data`. +public func buildFrequencyTable(data: some Sequence) -> [Int] { + var table = Array(repeating: 0, count: 255) + for byte in data { + table[Int(byte)] += 1 + } + return table +} + +/// Creates a lookup frequency table from a sequence of raw bytes. +/// +/// - Parameters: +/// - data: Sequence of raw bytes. +/// - Returns: A lookup frequency table. +/// - Complexity: O(_n_) where _n_ is the length of `data`. +public func buildFrequencyTable(data: some Sequence) -> [UInt8:Int] { + var table = [UInt8:Int]() + for byte in data { + table[byte, default: 0] += 1 + } + return table +} + +/// Creates a lookup frequency table from a sequence of raw bytes. +/// +/// - Parameters: +/// - data: Sequence of raw bytes. +/// - Returns: A lookup frequency table. +/// - Complexity: O(_n_) where _n_ is the length of `data`. +public func buildFrequencyTable(buffer: UnsafeBufferPointer)-> [UInt8:Int] { + var table = [UInt8:Int]() + for byte in buffer { + table[byte, default: 0] += 1 + } + return table +} + +/// Creates a universal frequency table from a character frequency dictionary. +/// +/// - Parameters: +/// - chars: A frequency table that represents how many times a character is present. +/// - Returns: A universal frequency table. +/// - Complexity: O(_n_) where _n_ is the sum of the `Character` utf8 lengths in `chars`. +public func buildFrequencyTable(chars: [Character:Int]) -> [Int] { + var table = Array(repeating: 0, count: 255) + for (char, freq) in chars { + for byte in char.utf8 { + table[Int(byte)] = freq + } + } + return table +} + +#if compiler(>=6.2) +// MARK: Inline Frequency tables +/// Creates a universal frequency table from a sequence of raw bytes. +/// +/// - Parameters: +/// - data: Sequence of raw bytes. +/// - Returns: A universal frequency table. +/// - Complexity: O(_n_) where _n_ is the length of `data`. +public func buildInlineFrequencyTable(data: some Sequence) -> [255 of Int] { + var table = [255 of Int](repeating: 0) + for byte in data { + table[Int(byte)] += 1 + } + return table +} + +/// Creates a universal frequency table from a character frequency dictionary. +/// +/// - Parameters: +/// - chars: A frequency table that represents how many times a character is present. +/// - Returns: A universal frequency table. +/// - Complexity: O(_n_) where _n_ is the sum of the `Character` utf8 lengths in `chars`. +public func buildInlineFrequencyTable(chars: [Character:Int]) -> [255 of Int] { + var table = [255 of Int](repeating: 0) + for (char, freq) in chars { + for byte in char.utf8 { + table[Int(byte)] = freq + } + } + return table +} +#endif \ No newline at end of file diff --git a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift index 2d84bcc..f78f673 100644 --- a/Sources/SwiftCompressionUtilities/CompressionTechnique.swift +++ b/Sources/SwiftCompressionUtilities/CompressionTechnique.swift @@ -1,101 +1,4 @@ -// MARK: CompressionTechnique /// Collection of well-known and useful compression and decompression technique implementations. public enum CompressionTechnique: Sendable { -} - -// MARK: Frequency tables -extension CompressionTechnique { - /// Creates a universal frequency table from a sequence of raw bytes. - /// - /// - Parameters: - /// - data: Sequence of raw bytes. - /// - Returns: A universal frequency table. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func buildFrequencyTable(data: some Sequence) -> [Int] { - var table = Array(repeating: 0, count: 255) - for byte in data { - table[Int(byte)] += 1 - } - return table - } - - /// Creates a lookup frequency table from a sequence of raw bytes. - /// - /// - Parameters: - /// - data: Sequence of raw bytes. - /// - Returns: A lookup frequency table. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func buildFrequencyTable(data: some Sequence) -> [UInt8:Int] { - var table = [UInt8:Int]() - for byte in data { - table[byte, default: 0] += 1 - } - return table - } - - /// Creates a lookup frequency table from a sequence of raw bytes. - /// - /// - Parameters: - /// - data: Sequence of raw bytes. - /// - Returns: A lookup frequency table. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func buildFrequencyTable(buffer: UnsafeBufferPointer)-> [UInt8:Int] { - var table = [UInt8:Int]() - for byte in buffer { - table[byte, default: 0] += 1 - } - return table - } - - /// Creates a universal frequency table from a character frequency dictionary. - /// - /// - Parameters: - /// - chars: A frequency table that represents how many times a character is present. - /// - Returns: A universal frequency table. - /// - Complexity: O(_n_) where _n_ is the sum of the `Character` utf8 lengths in `chars`. - public static func buildFrequencyTable(chars: [Character:Int]) -> [Int] { - var table = Array(repeating: 0, count: 255) - for (char, freq) in chars { - for byte in char.utf8 { - table[Int(byte)] = freq - } - } - return table - } -} - -#if compiler(>=6.2) -// MARK: Inline Frequency tables -extension CompressionTechnique { - /// Creates a universal frequency table from a sequence of raw bytes. - /// - /// - Parameters: - /// - data: Sequence of raw bytes. - /// - Returns: A universal frequency table. - /// - Complexity: O(_n_) where _n_ is the length of `data`. - public static func buildInlineFrequencyTable(data: some Sequence) -> [255 of Int] { - var table = [255 of Int](repeating: 0) - for byte in data { - table[Int(byte)] += 1 - } - return table - } - - /// Creates a universal frequency table from a character frequency dictionary. - /// - /// - Parameters: - /// - chars: A frequency table that represents how many times a character is present. - /// - Returns: A universal frequency table. - /// - Complexity: O(_n_) where _n_ is the sum of the `Character` utf8 lengths in `chars`. - public static func buildInlineFrequencyTable(chars: [Character:Int]) -> [255 of Int] { - var table = [255 of Int](repeating: 0) - for (char, freq) in chars { - for byte in char.utf8 { - table[Int(byte)] = freq - } - } - return table - } -} -#endif \ No newline at end of file +} \ No newline at end of file From b2f8ae8f747a82759b09bf30b63233cc8054d49e Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:36:17 -0500 Subject: [PATCH 20/22] fix unit test compilation; rename `DataBuilder` to `BytesBuilder` --- Sources/ByteBuilder/ByteBuilder.swift | 25 ------------------- Sources/ByteBuilder/BytesBuilder.swift | 22 ++++++++++++++++ Tests/DNATests/DNABinaryEncodingTests.swift | 4 +-- Tests/LZTests/LZ77Tests.swift | 10 ++------ Tests/SnappyTests/SnappyTests.swift | 4 +-- ...lderTests.swift => ByteBuilderTests.swift} | 7 +++--- .../SwiftCompressionTests/HuffmanTests.swift | 2 +- 7 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 Sources/ByteBuilder/BytesBuilder.swift rename Tests/SwiftCompressionTests/{DataBuilderTests.swift => ByteBuilderTests.swift} (73%) diff --git a/Sources/ByteBuilder/ByteBuilder.swift b/Sources/ByteBuilder/ByteBuilder.swift index e1d6fbd..6a3148f 100644 --- a/Sources/ByteBuilder/ByteBuilder.swift +++ b/Sources/ByteBuilder/ByteBuilder.swift @@ -119,31 +119,6 @@ public struct ByteBuilder { } /* -// MARK: Stream & Data Builder -extension CompressionTechnique { - public struct DataBuilder { - public var data:[UInt8] - public var builder:ByteBuilder - - public init(data: [UInt8] = [], builder: ByteBuilder = ByteBuilder()) { - self.data = data - self.builder = builder - } - - public mutating func write(bit: Bool) { - if let wrote:UInt8 = builder.write(bit: bit) { - data.append(wrote) - } - } - public mutating func write(bits: [Bool]) { - builder.write(bits: bits, closure: { data.append($0) }) - } - public mutating func finalize() { - builder.flush(into: &data) - } - } -} - // MARK: StreamBuilder extension CompressionTechnique { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) diff --git a/Sources/ByteBuilder/BytesBuilder.swift b/Sources/ByteBuilder/BytesBuilder.swift new file mode 100644 index 0000000..c15fcbd --- /dev/null +++ b/Sources/ByteBuilder/BytesBuilder.swift @@ -0,0 +1,22 @@ + +public struct BytesBuilder { + public var data:[UInt8] + public var builder:ByteBuilder + + public init(data: [UInt8] = [], builder: ByteBuilder = ByteBuilder()) { + self.data = data + self.builder = builder + } + + public mutating func write(bit: Bool) { + if let wrote:UInt8 = builder.write(bit: bit) { + data.append(wrote) + } + } + public mutating func write(bits: [Bool]) { + builder.write(bits: bits, closure: { data.append($0) }) + } + public mutating func finalize() { + builder.flush(into: &data) + } +} \ No newline at end of file diff --git a/Tests/DNATests/DNABinaryEncodingTests.swift b/Tests/DNATests/DNABinaryEncodingTests.swift index e78ce81..0c56cae 100644 --- a/Tests/DNATests/DNABinaryEncodingTests.swift +++ b/Tests/DNATests/DNABinaryEncodingTests.swift @@ -8,14 +8,14 @@ import Testing struct DNABinaryEncodingTests { static let sequence = "TACTTGCTAAAAGTACATTGCTAAGATACACCGGCA" static let data = [UInt8](sequence.utf8) - static let compressed = CompressionTechnique.dnaBinaryEncoding().compress(data: data, configuration: .init()) + static let compressed = DNABinaryEncoding().compress(data: data, configuration: .init()) @Test func compressDNABinaryEncoding() { #expect(Self.compressed == [199, 231, 0, 177, 62, 112, 140, 69, 164]) } @Test func decompressDNABinaryEncoding() throws(DecompressionError) { - let result = CompressionTechnique.dnaBinaryEncoding().decompress(data: Self.compressed, configuration: .init()) + let result = DNABinaryEncoding().decompress(data: Self.compressed, configuration: .init()) #expect(result == Self.data) } } diff --git a/Tests/LZTests/LZ77Tests.swift b/Tests/LZTests/LZ77Tests.swift index 8438485..5709a50 100644 --- a/Tests/LZTests/LZ77Tests.swift +++ b/Tests/LZTests/LZ77Tests.swift @@ -1,19 +1,13 @@ -// -// LZ77Tests.swift -// -// -// Created by Evan Anderson on 12/24/24. -// #if compiler(>=6.0) -import Testing @testable import CompressionLZ @testable import SwiftCompressionUtilities +import Testing struct LZ77Tests { static let string:String = "abracadabra abracadabra" - static let lz77:LZ77 = CompressionTechnique.lz77(windowSize: 10, bufferSize: 6) + static let lz77 = LZ77(windowSize: 10, bufferSize: 6) static let compressed = lz77.compress(data: [UInt8](string.utf8), configuration: .init(reserveCapacity: 1024)) @Test func compressLZ77() { diff --git a/Tests/SnappyTests/SnappyTests.swift b/Tests/SnappyTests/SnappyTests.swift index 9896f88..9d86d12 100644 --- a/Tests/SnappyTests/SnappyTests.swift +++ b/Tests/SnappyTests/SnappyTests.swift @@ -28,7 +28,7 @@ struct SnappyTests { } @Test func decompressSnappyLength() throws(DecompressionError) { - let snappy = CompressionTechnique.snappy() + let snappy = Snappy() var data:[UInt8] = [254, 255, 127] var index = data.startIndex var length:UInt32 = try snappy.decompressLength(data: data, index: &index) @@ -50,7 +50,7 @@ struct SnappyTests { } @Test func decompressSnappy() throws(DecompressionError) { - let decompressed = try CompressionTechnique.snappy().decompress(data: Self.wikipediaCompressedData) + let decompressed = try Snappy().decompress(data: Self.wikipediaCompressedData) #expect(decompressed == [UInt8](Self.wikipedia.utf8)) } } diff --git a/Tests/SwiftCompressionTests/DataBuilderTests.swift b/Tests/SwiftCompressionTests/ByteBuilderTests.swift similarity index 73% rename from Tests/SwiftCompressionTests/DataBuilderTests.swift rename to Tests/SwiftCompressionTests/ByteBuilderTests.swift index 746bbf8..0b3d3bf 100644 --- a/Tests/SwiftCompressionTests/DataBuilderTests.swift +++ b/Tests/SwiftCompressionTests/ByteBuilderTests.swift @@ -1,12 +1,13 @@ #if compiler(>=6.0) +import ByteBuilder import Testing @testable import SwiftCompressionUtilities -struct DataBuilderTests { - @Test func dataBuilder() { - var builder:CompressionTechnique.DataBuilder = .init() +struct BytesBuilderTests { + @Test func bytesBuilder() { + var builder = BytesBuilder() builder.data.reserveCapacity(100) builder.write(bits: [true, false, true, false]) builder.write(bits: [true, true, true, true, true]) diff --git a/Tests/SwiftCompressionTests/HuffmanTests.swift b/Tests/SwiftCompressionTests/HuffmanTests.swift index c83f56f..2166340 100644 --- a/Tests/SwiftCompressionTests/HuffmanTests.swift +++ b/Tests/SwiftCompressionTests/HuffmanTests.swift @@ -1,8 +1,8 @@ #if compiler(>=6.0) +import Huffman import Testing -@testable import SwiftCompressionUtilities struct HuffmanTests { static let scoobyDooString = "ruh roh raggy!" From 4814ea28eddb69cdbcd8c4404ca81a2c2f1cbc66 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:42:01 -0500 Subject: [PATCH 21/22] remove `hexadecimal` function and unit test --- .../extensions/SequenceExtensions.swift | 10 ---------- .../HexadecimalTests.swift | 19 ------------------- 2 files changed, 29 deletions(-) delete mode 100644 Tests/SwiftCompressionTests/HexadecimalTests.swift diff --git a/Sources/SwiftCompressionUtilities/extensions/SequenceExtensions.swift b/Sources/SwiftCompressionUtilities/extensions/SequenceExtensions.swift index 90a9a28..9ef1b92 100644 --- a/Sources/SwiftCompressionUtilities/extensions/SequenceExtensions.swift +++ b/Sources/SwiftCompressionUtilities/extensions/SequenceExtensions.swift @@ -1,14 +1,4 @@ -#if canImport(Foundation) -import Foundation - -extension Sequence where Element == UInt8 { - package func hexadecimal(separator: String = "") -> String { - return map({ String.init(format: "%02X", $0) }).joined(separator: separator) - } -} -#endif - extension Collection { /// - Returns: The element at the given index if within bounds. Otherwise `nil`. /// - Complexity: O(1). diff --git a/Tests/SwiftCompressionTests/HexadecimalTests.swift b/Tests/SwiftCompressionTests/HexadecimalTests.swift deleted file mode 100644 index ff15476..0000000 --- a/Tests/SwiftCompressionTests/HexadecimalTests.swift +++ /dev/null @@ -1,19 +0,0 @@ - -#if compiler(>=6.0) - -import Testing -@testable import SwiftCompressionUtilities - -struct HexadecimalTests { - - #if canImport(Foundation) - @Test func hexadecimal() { - let string = "Wikipedia is a free, web-based, collaborative, multilingual encyclopedia project." - let hex = string.utf8.hexadecimal() - #expect(hex == "57696B697065646961206973206120667265652C207765622D62617365642C20636F6C6C61626F7261746976652C206D756C74696C696E6775616C20656E6379636C6F70656469612070726F6A6563742E") - } - #endif - -} - -#endif \ No newline at end of file From 350d7ea394a81cc9782c226802979052ea94e999 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Thu, 9 Apr 2026 14:44:32 -0500 Subject: [PATCH 22/22] add `SwiftCompressionUtilities` product --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index be7d952..9680490 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( // MARK: Products products: [ .library(name: "SwiftCompression", targets: ["SwiftCompression"]), + .library(name: "SwiftCompressionUtilities", targets: ["SwiftCompressionUtilities"]), .library(name: "Brotli", targets: ["Brotli"]), .library(name: "SwiftCompressionDNA", targets: ["CompressionDNA"]),