Native Swift port of Pretext — a text layout engine that measures and lays out multiline text without touching the view hierarchy.
Four targets:
- Pretext — reusable library (core engine)
- PretextUI — optional SwiftUI bridge for
FontDescriptor.makeDisplayFont() - Demo — SwiftUI demo app for iOS + macOS
- Benchmark — macOS benchmark app (GUI + CLI)
swift build— debug build (all targets)swift build -c release— release build (10x faster, use for benchmarks)swift test— run all tests (PretextTests + DemoTests)xcodebuild -scheme Pretext -destination 'generic/platform=iOS Simulator' build CODE_SIGNING_ALLOWED=NO— validate thePretextlibrary on iOS Simulatorxcodebuild build -scheme Demo -destination 'platform=iOS Simulator,id=…' CODE_SIGNING_ALLOWED=NO— build the demo app on iOS Simulatorxcodebuild test -scheme PretextSwift-Package -destination 'platform=iOS Simulator,id=…' -only-testing:DemoTests CODE_SIGNING_ALLOWED=NO— runDemoTestson iOS Simulatorswift run Demo— launch the demo appswift run Benchmark— launch the benchmark GUI.build/release/Benchmark --cli— CLI benchmark (Pretext vs Core Text vs SwiftUI)
The engine has a clean two-phase split:
prepare(text, font)— one-time: segments text, measures via Core Text glyph advances, caches widthslayout(prepared, maxWidth, lineHeight)— hot path: pure arithmetic over cached widths, zero CT calls
Pretext library (Sources/Pretext/):
API/LayoutAPI.swift— public API:prepare(),layout(),layoutNextLine(),walkLineRanges()Engine/TextAnalysis.swift— UTF-8 byte scanner, whitespace normalization, punctuation mergingEngine/TextMeasurement.swift— Core Text glyph advance measurement, segment cacheEngine/LineBreaker.swift— pure arithmetic line-breaking engine (~1100 lines)Model/PreparedText.swift— all data types
PretextUI bridge (Sources/PretextUI/):
Extensions/FontDescriptor+SwiftUI.swift— SwiftUI helper kept out of the core target
Demo app (Sources/Demo/):
App/DemoApp.swift— app entry pointFeatures/SituationalAwareness/EditorialView.swift— "Situational Awareness" demo (light theme, logo obstacles)Features/EditorialEngine/OrbEditorialView.swift— "Editorial Engine" demo (dark theme, floating orbs)
Benchmark support (Sources/BenchmarkSupport/):
Core/BenchmarkCorpus.swift— benchmark corpus and shared typography constantsCore/BenchmarkScenarios.swift— the five benchmark scenariosUI/BenchmarkView.swift— benchmark UI shared by the app and demo
Benchmark app (Sources/Benchmark/):
App/BenchmarkApp.swift— app entry point,--climode
Tests:
Tests/PretextTests/— core engine tests (CoreEngineTests, LineBreakerTests, TextAnalysisTests)Tests/DemoTests/— demo tests (EditorialLayoutTests, LogoHullTests, OrbEditorialLayoutTests, WrapGeometryTests)
Pretext: ~4.5ms (500 texts, prepare + layout)
Core Text: ~29.8ms (CTFramesetterSuggestFrameSizeWithConstraints)
SwiftUI: ~88ms (NSHostingView + fittingSize)
Pretext is 6.5x faster than Core Text, 20x faster than SwiftUI. The hot-path layout() takes 0.1ms for 500 texts.
PretextsupportsiOS 18+andmacOS 15+PretextUIis optional and only provides the SwiftUI display-font helperDemois maintained as an iOS + macOS repo appBenchmarkremains macOS-only
- Always benchmark in release mode (
-c release). Debug mode inflates numbers by 10x due to no inlining, full bounds checking, uneliminated ARC. prepare()uses lazy grapheme measurement:breakableWidthsare nil during prepare, resolved byprepareForWidth()only when a word overflowsmaxWidth. ThemaxBreakableWidthfield enables O(1) early exit.- The text analysis scanner uses raw UTF-8 bytes (
withContiguousStorageIfAvailable/withUnsafeBufferPointer) to avoid String.Index overhead. It handles smart quotes (3-byte E2 80 xx sequences) inline. - Punctuation stickiness checks use
Set<UInt32>(scalar values), notSet<Character>, to avoid expensive grapheme cluster construction. - The measurement cache uses
FontCacheKey(CTFont pointer identity viaUnmanaged.toOpaque()) instead of string-based font keys. - Reference-type cache wrappers (
WidthTable,MetricsTable) avoid Dictionary COW copies in the batch measurement loop — though in release mode the compiler optimizes value-type COW nearly as well. - The glyph-advance fast path (
CTFontGetGlyphsForCharacters+CTFontGetAdvancesForGlyphs) bypasses CTLine creation for Latin text. Falls back to CTTypesetter for complex scripts.
swift test --filter PretextTestscurrently passesDemoandBenchmarkstill compile on macOS after thePretextUIsplit
The prepare() function went through 5 rounds of optimization:
- Fast glyph advances — replaced CTLine with CTFontGetAdvancesForGlyphs (minor win)
- Batch CTTypesetter — one typesetter per text instead of one CTLine per segment (2x win on measurement)
- Lightweight word scanner — replaced NLTokenizer with Unicode scalar scanner (eliminated ML overhead)
- Lazy grapheme measurement — defer grapheme widths to layout time (eliminated 23ms)
- Raw UTF-8 byte scanner — replaced scalar iteration with byte-level scanning (1.5x win on analysis)
The theoretical floor is ~1.5ms. Current 4.5ms is 3x the floor. The gap is Swift runtime tax (String ARC, Dictionary hashing). Further optimization requires abandoning Swift's String type.