Skip to content

Repository files navigation

InkPond

InkPond (墨池) is a native iOS/iPadOS editor for Typst, with live preview and PDF export powered by a Rust FFI bridge.

App Store TestFlight Chinese README

Platform Language Typst Version License

Languages

Quick Actions

Action Command / Link
Download App Store
Join TestFlight beta testflight.apple.com/join/w5jmkR2T
Build Rust FFI cd rust-ffi && ./build-ios.sh
Simulator debug build xcodebuild -project InkPond.xcodeproj -scheme InkPond -configuration Debug -destination 'generic/platform=iOS Simulator' build
Export options file release/ExportOptions.plist

Features

Editor

  • Parser-backed Typst syntax highlighting with themed tokens, rainbow bracket coloring, and bracket mismatch detection
  • Auto-pairing for {}[]()""$$ with smart type-over, auto-delete, and auto-indent
  • Code completion for Typst functions (~150), keywords, #import package specs, font families, labels, references, and image paths
  • Snippets library with custom templates and $0 cursor placeholders
  • Find and replace (system UIFindInteraction)
  • Line number gutter with error line highlighting
  • Keyboard accessory bar with quick-insert buttons

Preview & Compile

  • Typst 0.15.0 compilation through a Rust FFI bridge
  • Live SVG/WKWebView preview with debounced recompilation (350ms)
  • Bidirectional editor-to-preview sync via source maps
  • @preview package downloads cached on device, plus local package resolution
  • Document statistics (pages, words/tokens, characters; CJK-aware)
  • Error banner with source location links
  • Full-screen slideshow mode
  • Parser-backed outline view for heading navigation
  • Compiled preview cache for faster reopen and rebuild cycles

Project Management

  • Multi-file projects with customizable entry file
  • Project file browser with .typ, image, and font sections
  • Image import from Photos, clipboard (including HTML paste), and remote URLs
  • Per-project, app-wide, and system font resolution for explicit Typst font declarations
  • Optional iCloud project sync with separate app font, local package, and snippet sync controls
  • Local Typst package manager for folders and .zip / .tar / .tar.gz / .tgz archives
  • ZIP project import and export
  • PDF and source (.typ) export

UI/UX

  • Adaptive layout: split view on iPad, tabbed on iPhone
  • Separate app appearance controls plus three editor themes: Mocha (dark), Latte (light), System (adaptive) — based on Catppuccin
  • Onboarding flow for new users
  • Editor position resumption across launches
  • Full VoiceOver and accessibility support
  • Localized in English, Simplified Chinese, Traditional Chinese (HK/TW)
  • Settings for iCloud, app fonts, local packages, keyboard shortcuts, and compile/package/system-font caches
  • iOS 26 keyboard glass and visual enhancements where available, with iOS 17-compatible fallbacks

Requirements

  • macOS with Xcode 26.3+
  • iOS/iPadOS deployment target: 17.0 (app target)
  • Rust toolchain (rustup, cargo) for building typst_ios.xcframework

Quick Start

  1. Clone repo:
    git clone <your-fork-or-origin-url>
    cd InkPond
  2. Make sure the native toolchain is ready:
    xcode-select -p
    cargo --version
    rustup show
    If any of these fail, install Xcode command line tools and the Rust toolchain before continuing.
  3. Rebuild the Rust FFI framework after Rust changes:
    cd rust-ffi
    ./build-ios.sh
    cd ..
    This updates the committed Frameworks/typst_ios.xcframework.
  4. Build and run in Xcode:
    open InkPond.xcodeproj

Build Commands

# Simulator debug build
xcodebuild -project InkPond.xcodeproj -scheme InkPond -configuration Debug -destination 'generic/platform=iOS Simulator' build

# Device release archive
xcodebuild -project InkPond.xcodeproj -scheme InkPond -configuration Release -destination 'generic/platform=iOS' archive

# Unit tests
xcodebuild test -project InkPond.xcodeproj -scheme InkPond -only-testing:InkPondTests -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5'

# UI tests
xcodebuild test -project InkPond.xcodeproj -scheme InkPond -only-testing:InkPondUITests -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5'

# If your local simulator list differs, inspect available destinations first:
# xcodebuild -showdestinations -project InkPond.xcodeproj -scheme InkPond

Rust FFI Notes

  • Frameworks/typst_ios.xcframework is generated by rust-ffi/build-ios.sh and committed so Xcode Cloud can build without a Rust bootstrap step.
  • rust-ffi/build-ios.sh builds the three Rust slices in parallel by default and keeps rust-ffi/target/ so Cargo can reuse compiled dependencies across rebuilds.
  • Set RUST_FFI_PARALLEL_SLICES=0 to build slices serially, CARGO_BUILD_JOBS=<n> to control jobs per slice, or CLEAN_RUST_TARGET=1 to remove rust-ffi/target/ after packaging.
  • Rebuild and commit Frameworks/typst_ios.xcframework/ when Rust FFI output changes.
  • The Swift bridge passes font paths, the project root, an @preview package cache, and the local package directory into Rust.
  • Re-run rust-ffi/build-ios.sh when:
    • updating Typst / Rust dependencies
    • changing rust-ffi/src/lib.rs
  • If Xcode shows Typst compiler library not linked, the xcframework is missing, stale, or was built before switching branches. Re-run the build script and rebuild the app.

Current pinned Typst version: 0.15.0 (see rust-ffi/Cargo.toml).

Release Pipeline (CLI)

Typical flow:

# 1) Archive
xcodebuild -project InkPond.xcodeproj -scheme InkPond -configuration Release -destination 'generic/platform=iOS' -archivePath /private/tmp/InkPond.xcarchive archive

# 2) Export IPA (using your ExportOptions.plist)
xcodebuild -exportArchive -archivePath /private/tmp/InkPond.xcarchive -exportPath /private/tmp/InkPond-export -exportOptionsPlist release/ExportOptions.plist

# 3) Upload (InkPond app id)
asc --profile default builds upload --app 6760032537 --ipa /private/tmp/InkPond-export/InkPond.ipa --output table

After upload, wait for processing, then distribute build to TestFlight groups.

Public App Store listing: apps.apple.com/us/app/inkpond/id6760032537

Project Layout

InkPond/
├── InkPond/
│   ├── InkPondApp.swift                 # @main entry point, SwiftData ModelContainer
│   ├── ContentView.swift                # NavigationStack project home, environment setup
│   ├── AppAppearanceManager.swift       # App-wide light/dark/system appearance
│   ├── Models/
│   │   └── InkPondDocument.swift        # @Model: document data + project config
│   ├── Editor/
│   │   ├── TypstTextView.swift         # UITextView subclass (TextKit 1)
│   │   ├── SyntaxHighlighter.swift     # Typst token styling + rainbow brackets
│   │   ├── CompletionEngine.swift      # Context-aware code completion
│   │   ├── AutoPairEngine.swift        # Bracket/quote auto-pairing
│   │   ├── SyncCoordinator.swift       # Bidirectional editor↔preview sync
│   │   ├── EditorTheme.swift           # Mocha/Latte/System theme definitions
│   │   ├── ThemeManager.swift          # Theme persistence (UserDefaults)
│   │   ├── Snippet*.swift              # Snippet model, library, and store
│   │   ├── HighlightScheduler.swift    # Debounced highlighting
│   │   ├── LineNumberGutterView.swift  # Gutter with error markers
│   │   └── KeyboardAccessoryView.swift # Accessory bar (photo/snippet buttons)
│   ├── Compiler/
│   │   ├── TypstBridge.swift           # Rust FFI wrapper (compile + source map)
│   │   ├── TypstCompiler.swift         # Debounced SVG/PDF preview pipeline + cache
│   │   ├── SourceMap.swift             # Line↔page bidirectional mapping
│   │   ├── ProjectFileManager.swift    # Per-project file CRUD + validation
│   │   ├── FontManager.swift           # Project/app font metadata + parsing helpers
│   │   ├── CompileFontResolver.swift   # Font resolution and materialization planning
│   │   ├── CoreTextFontMaterializer.swift
│   │   │                              # System font materialization cache for Typst
│   │   ├── LocalPackageStore.swift     # Local Typst package catalog and imports
│   │   ├── PackageArchiveImporter.swift
│   │   │                              # Package archive extraction
│   │   ├── ExportManager.swift         # PDF/source/ZIP export (custom ZIP writer)
│   │   ├── ExportController.swift      # Export UI state machine
│   │   ├── ZipImporter.swift           # ZIP project import
│   │   ├── DirectoryMonitor.swift      # DispatchSource file system watcher
│   │   └── *CacheStore.swift           # Compiled preview + package caches
│   ├── Views/
│   │   ├── DocumentList/               # Document library, search, sort, rename
│   │   ├── DocumentEditor/             # Editor/preview split, file ops, images
│   │   │   └── OutlineView.swift       # Heading outline navigation
│   │   ├── EditorView.swift            # UIViewRepresentable for TypstTextView
│   │   ├── PreviewPane.swift           # SVG/WKWebView live preview + stats + sync marker
│   │   ├── SlideshowView.swift         # Full-screen SVG slideshow
│   │   ├── OnboardingView.swift        # First-launch onboarding
│   │   ├── SnippetBrowserSheet.swift   # Snippet library browser
│   │   ├── ProjectFileBrowserSheet.swift
│   │   ├── ProjectSettingsSheet.swift
│   │   └── Settings/                   # iCloud, appearance, editor font, packages, fonts, caches, shortcuts
│   ├── Localization/                   # L10n.swift lookup helpers
│   ├── Resources/Localization/         # Localizable.xcstrings (en, zh-Hans, zh-Hant-HK, zh-Hant-TW)
│   ├── Storage/
│   │   ├── StorageManager.swift        # Local/iCloud mode and migration
│   │   ├── Cloud*.swift                # iCloud coordination, availability, sync monitor
│   │   └── AppFontLibrary.swift        # App-wide font import tracking
│   ├── Shared/UI/                      # UIKit/SwiftUI bridges, haptics, a11y
│   ├── Support/
│   │   ├── AppIdentity.swift           # Bundle/app group/iCloud identifiers
│   │   └── InteractionSupport.swift    # Haptics and accessibility announcements
│   └── Bridging/                       # typst_ffi.h bridging header
├── rust-ffi/
│   ├── src/lib.rs                      # Rust Typst wrapper
│   ├── Cargo.toml                      # Rust dependencies (Typst engine)
│   └── build-ios.sh                    # XCFramework build (device + sim)
├── Frameworks/
│   └── typst_ios.xcframework/          # Committed Typst FFI artifact
├── release/
│   └── ExportOptions.plist
└── InkPond.xcodeproj

Troubleshooting

  • Typst compiler library not linked:
    • Run cd rust-ffi && ./build-ios.sh and rebuild the app.
    • If the script fails immediately, check cargo --version, rustup show, and xcode-select -p.
    • If the script succeeds but Xcode still fails to link, clean the build folder and rerun the app build.
  • Simulator link errors about typst_ios architecture:
    • Rebuild xcframework; the script generates arm64 + x86_64 simulator slices.
  • TestFlight upload succeeded but not distributable yet:
    • Build is still processing in App Store Connect.
  • @preview package import fails:
    • Check network access, then clear the package cache from Settings and recompile.
  • iCloud files appear missing or not downloaded:
    • Open the Files app, go to iCloud Drive, and keep the InkPond folder downloaded before reopening the project.

Acknowledgements

  • Typst - core typesetting engine used for rendering and PDF generation (Apache 2.0)
  • Catppuccin - color system used by the editor themes (MIT)
  • swift-bridge - inspiration and reference for Swift/Rust interop patterns (MIT or Apache-2.0)

Special Thanks

  • Thanks to everyone at Donut for support and inspiration.

About

A native iOS/iPadOS editor for Typst — the modern typesetting system.

Resources

Stars

62 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages