Repository navigation
KitePDF 0.8.0
Adds comic book (CBZ) and SVG support, and fixes text that came back with the wrong font size and wrong spacing. Encrypted PDF writing now needs a random source you supply, so read the breaking section before upgrading.
implementation("io.github.yuroyami:kitepdf:0.8.0")Requirements
- Kotlin 2.4.10+ (older compilers cannot read these artifacts)
- JDK 21
- Android SDK 37 / minSdk 21 (compose-viewer 24, native-renderer 29)
- Compose Multiplatform 1.12.0-beta02 for
kitepdf-compose-viewer - Pulls in KiteImage 0.1.0, Ktor 3.5.2 (
kitepdf-net), Skiko 0.148.2 (renderers) - Targets: Android, JVM, iOS, tvOS, watchOS, macOS, Linux, Windows, Android Native, JS, Wasm
⚠️ Breaking
Encrypted writing now needs a random source from you. AES-256 was falling back to Kotlin's Random.Default for keys and IVs, which is not safe for encryption. It now takes a proper platform random source explicitly, and encrypted editing fails at startup without one.
PdfDocument.bytes now copies, which makes its documented immutability true. If you need the old zero-copy access:
@OptIn(KiteRawApi::class)
val raw = doc.rawBytesZipReader moved to kitepdf-core (io.github.yuroyami.kitepdf.core.zip). The old name compiles as a deprecated typealias for one release. KiteXml, KiteXmlNode and CssValues made the same trip out of kitepdf-epub and are public there now.
EpubSettings now validates when you construct it. Non-finite, negative and geometrically impossible settings throw, where they used to lay out nonsense.
Two new formats
Comic book archives (CBZ) and SVG, each its own artifact. KiteDoc.open detects them the way it already detected PDF and EPUB.
val comic = KiteDoc.open(cbzBytes) // one page per image, page2 before page10
val art = KiteDoc.open(svgBytes) // one page at the SVG's own viewportkitepdf-cbz brings no EPUB engine with it. Page sizes come from image headers, so opening an archive does not decode it, and packaging noise (ComicInfo.xml, Thumbs.db, dotfiles) is skipped.
kitepdf-svg holds the renderer that was buried inside the EPUB handler. It gained <use>, <symbol>, <image>, <text>, gradients and clip-path, so a fixed-layout comic whose pages are an SVG wrapping one JPEG now draws.
Text extraction
Every piece of text reported a font size of 1.0pt, whatever its real size (fixes #22, reported by @bfovez). Some PDF producers set the font operator to 1 and carry the real size in the text matrix instead:
BT /F1 1 Tf 15 0 0 15 72 720 Tm (Hello) Tj ET
That is legal, and fontSize was handing back the raw operand.
span.fontSize // 0.7.0: 1.0 0.8.0: 15.0Not just cosmetic. KitePDF uses font size to decide what is one line and where a word break goes: half a font size of vertical wobble splits a line, a quarter of a font size of gap inserts a space. Stuck at 1.0pt, a 0.25pt gap invented word breaks and 0.5pt of wobble split one line into several. extractText judged its line breaks the same wrong way.
Letter-spaced and word-spaced text drew too tightly, then jumped. Tc and Tw only reached the advance positioning the next run, never moving the pen inside the current one. Fixed on every backend, both the stroke and clip passes, and structured-text bounds.
Measured against mutool on a line with 6 units of extra character spacing: 101px short before, 1px after.
EPUB
Russian hyphenation (PR #7 by @ksokolovskiy). A Russian book with automatic hyphenation was broken against the English pattern set, which does nothing for Russian. Eight languages now, chosen per chapter.
Indented first lines were clipped at the right edge (fixes #6, reported by @ksokolovskiy). Lines were measured against the full page width, then the indent was added at placement, so a packed first line stuck out by up to the indent width. Cyrillic was worse: measured at a flat half-em instead of the real widths already in core.
The Paged viewer jumped while chapters loaded (fixes #5, reported by @ksokolovskiy), and lost a saved position if opening was interrupted. It now opens at your bookmark's chapter, holds zoom and selection through landings, and clamps to the nearest page when a saved spot is gone.
Hardening
- Remote downloads stream into a size-capped buffer: 128 MiB default,
maxByteson every URL API - Rasterizers reject nonsense scale values and cap allocation at 40 megapixels (
KITE_DEFAULT_MAX_RASTER_PIXELS) - ZIP entries get 128 MiB decompressed and 100,000 record ceilings. Malformed offsets and truncated records fail closed
Known limits
- Without an embedded font, Skia and Compose draw a run as one platform string, so per-character spacing inside it is approximated. AWT, Android and Canvas2D are exact
extractTextfollows the text matrix but not the page transform (cm), so its thresholds are off under a scaled page.structuredTextis exact- WebP images inside a CBZ are detected but render blank
Full changelog: v0.7.0...v0.8.0