Skip to content

Add Android and iOS multiplatform support#3

Draft
chrisjenx wants to merge 8 commits intomainfrom
feature/multiplatform
Draft

Add Android and iOS multiplatform support#3
chrisjenx wants to merge 8 commits intomainfrom
feature/multiplatform

Conversation

@chrisjenx
Copy link
Copy Markdown
Owner

Summary

  • Kotlin Multiplatform migration — compose2pdf now targets JVM, Android (minSdk 24), and iOS (arm64/x64/simulatorArm64)
  • Android rendering via native android.graphics.pdf.PdfDocument (zero external deps, suspend API, always vector)
  • iOS rendering via Core Graphics (CGPDFContext) with Skia SVGCanvas intermediary
  • CI updated — Android SDK setup, iOS simulator tests on macOS, publish jobs moved to macOS
  • Security hardening — XXE/DTD prevention in DocumentBuilderFactory (defense-in-depth)
  • Full docs rewrite — README, getting started, compatibility, API reference, architecture, changelog all updated for multiplatform
  • CLAUDE.md rewritten for multiplatform source sets, pipelines, and gotchas

Platform feature matrix

Feature JVM Android iOS
Vector output VECTOR/RASTER Always vector Always vector
Auto-pagination Yes Yes Yes
Multi-page (manual) Yes -- --
OutputStream streaming Yes Yes --
PdfLink annotations Yes -- --
Bundled Inter font Yes -- --

Test plan

  • ./gradlew :compose2pdf:compileKotlinJvm — JVM compiles
  • ./gradlew :compose2pdf:jvmTest — JVM unit tests pass
  • ./gradlew :fidelity-test:test — Fidelity tests pass (verified locally)
  • ./gradlew :compose2pdf:iosSimulatorArm64Test — iOS simulator tests pass (macOS)
  • ./gradlew :compose2pdf:publishToMavenLocal — Verify artifact structure in ~/.m2
  • cd docs && bundle exec jekyll serve — Docs site renders correctly
  • Review CI workflow runs for Android SDK + iOS test steps

🤖 Generated with Claude Code

chrisjenx and others added 8 commits March 27, 2026 17:29
Migrate compose2pdf from kotlin("jvm") to kotlin("multiplatform") with
JVM, Android, and iOS targets. Portable types (PdfPageConfig, PdfMargins,
PdfLink, etc.) move to commonMain; JVM-specific rendering stays in jvmMain.

Android implementation uses native android.graphics.pdf.PdfDocument with
off-screen Compose rendering via VirtualDisplay + Presentation, producing
vector PDF output. iOS target compiles with stub renderer.

Add shared test-fixtures KMP module with 28 composable fidelity fixtures
(material3) used by both JVM fidelity tests and Android instrumented tests.
Android tests run on Gradle Managed Devices (ATD API 30) with PDF extraction
via TestStorage. Fidelity report now includes cross-platform Android column
with diff images and metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create intermediate skikoMain source set for code shared between JVM and
iOS (both use Skiko). Move ComposeToSvg.kt there with expect/actual for
the SVG byte output (JVM uses ByteArrayOutputStream, iOS uses Skia's
OutputWStream.toData()). Validates that CanvasLayersComposeScene and
SVGCanvas compile on iOS — clearing the critical blocker for iOS PDF.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add complete SVG-to-PDF conversion pipeline for iOS using native APIs:
- SvgDocument.kt: NSXMLParser-based SVG DOM parser
- CoreGraphicsPdfConverter.kt: Renders SVG elements to CGPDFContext
  (paths, shapes, text via Core Text, images, transforms, clipping,
  opacity, link annotations)
- CoreGraphicsPathParser.kt: SVG path d-attribute parser (all commands)
- IosPdfRenderer.kt: Orchestrator connecting ComposeToSvg (skikoMain)
  to Core Graphics converter, with single-page and auto-pagination modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The skikoMain intermediate source set broke the iOS test→main dependency
chain (tests couldn't see iosMain production code). Replace with
duplicated ComposeToSvg in both jvmMain and iosMain — the code sharing
was causing more problems than it solved.

iOS ComposeToSvg uses OutputWStream.toData() instead of ByteArrayOutputStream.
CoreGraphicsPdfConverter has known compile errors (K/N API naming) to be
fixed in the next iteration.

Also fixes Math.PI → kotlin.math.PI in shared test fixtures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix OutputWStream: use DynamicMemoryWStream on iOS (no-arg constructor)
- Fix CG enum constants: CGLineCap.kCGLineCapButt, CGLineJoin.*, CGPathDrawingMode.*
- Fix CGRect: use CGRectMake() instead of struct allocation
- Fix String→CFStringRef casts: use CFBridgingRetain(text as NSString)
- Remove iosTest duplicate, use iosSimulatorArm64Test directly

All 2 iOS tests pass (basicRender + 28 shared fixtures) on simulator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
iOS simulator tests now save generated PDFs to /tmp/compose2pdf-ios-test-output/,
which the fidelity report picks up and includes as an "iOS" column alongside
JVM Vector, JVM Raster, and Android.

The report now shows 4 rendering modes per fixture with diff images and metrics,
enabling visual comparison across all 3 platforms.

Known: iOS text rendering shows per-glyph spacing from SVG x-position attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-character CTLine rendering introduced bearing offsets that caused
visible letter spacing artifacts. CTFontDrawGlyphs places glyphs directly
at SVG coordinates without CTLine's text layout adjustments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add XXE/DTD prevention to DocumentBuilderFactory (defense-in-depth)
- Update CI workflows: add Android SDK setup, iOS simulator tests,
  switch publish jobs to macOS for full KMP target support
- Rewrite all docs to reflect Android and iOS support: README, getting
  started, compatibility, API reference, architecture, changelog
- Rewrite CLAUDE.md for multiplatform (all source sets, pipelines, gotchas)
- Add 2.0.0 changelog entry with platform feature matrix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant