Skip to content

Explore adopting SwiftPorts compression to replace the custom PDF deflate + ZIPFoundation #46

Description

@odrobnik

Motivation

SwiftText currently carries two separate compression implementations:

  1. Custom pure-Swift DEFLATE/zlib encoderSources/SwiftTextPDFWriter/Deflate.swift (added in Compress PDF content streams (FlateDecode) in SwiftTextPDFWriter #44 / Compress PDF content streams with FlateDecode (#44) #45) for PDF /FlateDecode content-stream compression.
  2. ZIPFoundation — the external dependency used by SwiftTextDOCX and SwiftTextIWA (Pages/Numbers/Keynote) to read/write .docx and .iwa Zip containers. Currently pinned to a development HEAD commit for a Windows #import fix (weichsel/ZIPFoundation#380), with no tagged release carrying it.

SwiftPorts already has cross-platform compression/archive code that builds and is tested on macOS/iOS/Linux/Windows. This issue is to explore whether reusing it lets us drop one or both of the above, reduce code we own, and/or escape the ZIPFoundation dev-HEAD pin.

What SwiftPorts actually provides

Worth being precise up front — it is thin Swift over system/C libraries, not pure Swift:

Concern SwiftPorts module Backend
raw deflate / zlib / gzip GzipKit.Zlib system libz (CZlib = module CZlib [system] { link "z" })
zip / tar containers ZipKit / TarKit libarchive via marcprux/swift-archive
xz / lz4 on Apple XzKit / Lz4Kit Compression.framework; Linux/Windows use system liblzma/liblz4
  • For the PDF path, GzipKit.Zlib.compress(data, wrap: .zlib) emits exactly the RFC 1950 stream /FlateDecode expects — a clean API fit, and real zlib compresses somewhat better than our fixed-Huffman encoder.
  • For the Zip container path, ZipKit (libarchive) is the analog to ZIPFoundation.

The tradeoff to resolve

  • SwiftTextPDFWriter is deliberately Foundation-only, zero external dependencies, and is part of the portable subset (SWIFTTEXT_PORTABLE_ONLY=1). Adopting GzipKit/CZlib there means linking system zlib — reversing the decision made in Compress PDF content streams (FlateDecode) in SwiftTextPDFWriter #44 (which explicitly weighed "vendor a small deflate vs. pull in zlib"). It also needs zlib provided on Windows (vcpkg), and GzipKit.Lib is coupled to ShellKit + the wider SwiftPorts package (min macOS 13 / iOS 16, vs. SwiftText's macOS 12 / iOS 13).
  • Swapping ZIPFoundation → swift-archive trades a mostly-Swift dependency (ZIPFoundation, which itself links system zlib) for a C/libarchive dependency — heavier, with its own cross-compile/system-lib burden on Linux/Windows.
  • Note: the custom encoder already hits ~10× on text-heavy PDFs (in the webkit/EPUB ballpark Compress PDF content streams (FlateDecode) in SwiftTextPDFWriter #44 targeted), so the PDF ratio is not currently a pain point — this is about code ownership / reuse / cross-platform maturity, not compression quality.

Options to evaluate

  1. PDF deflate
    • (a) Keep Deflate.swift as-is (zero-dep, portable).
    • (b) Hybrid: Compression.framework when canImport(Compression) (Apple, no external dep, better ratio/speed) wrapped with our existing header + Adler-32, falling back to Deflate.swift elsewhere. No SwiftPorts needed.
    • (c) Adopt GzipKit/system zlib — reverses the zero-dep guarantee for the portable substrate.
  2. Zip containers
    • (a) Keep ZIPFoundation (move off the dev-HEAD pin once upstream tags a release with the Windows fix).
    • (b) Adopt ZipKit/swift-archive (libarchive) across SwiftTextDOCX + SwiftTextIWA.

What this exploration should answer

  • Do SwiftPorts' GzipKit/ZipKit build standalone (without dragging git/gh/sqlite/etc.), or would we need them factored into a smaller package / vendored?
  • Is the platform-minimum bump (macOS 13 / iOS 16) acceptable for SwiftText?
  • Is a system-zlib link acceptable in SwiftTextPDFWriter, or should that module stay strictly zero-dependency (favoring option 1a/1b)?
  • Windows story for libz / libarchive under the current CI (vcpkg) — does it actually build green on all platforms end to end?
  • Does dropping ZIPFoundation meaningfully simplify our dependency graph, or just swap one external dep for a heavier C one?

Cross-refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions