Repository navigation
KitePDF 0.13.0 is an enormous update with major features, so expect regressions. Please file any problems you encounter in the issue tracker.
Highlights in this version:
- Support for video/audio playback inside EPUBs, via my very own KitePlayer KMP library.
- Full Javascript runtime support inside PDFs and EPUBs, via QuickJS wrapped for KMP as KiteJS
- Support for read-aloud (read-along).
- Support for more image formats (AVIF, JPEG-XL, lossy WebP).
- Major enhancements to scrolling, text shaping for a wide range of languages.
- Improved PDF rendering extensively (quality, performance, UX).
Most of these features are made possible thanks to the public W3C resources used in the test suites to confirm EPUB and PDF engine work is up to standard.
Usage
// Every format: PDF, EPUB, CBZ, SVG, XPS and OpenXPS
implementation("io.github.yuroyami:kitepdf:0.13.0")
// Or one format only
implementation("io.github.yuroyami:kitepdf-pdf:0.13.0")
implementation("io.github.yuroyami:kitepdf-epub:0.13.0")
implementation("io.github.yuroyami:kitepdf-cbz:0.13.0")
implementation("io.github.yuroyami:kitepdf-svg:0.13.0")
implementation("io.github.yuroyami:kitepdf-xps:0.13.0")
// Optional (but you may probably need one of these depending on your use case)
implementation("io.github.yuroyami:kitepdf-compose-viewer:0.13.0") // KiteDocView for Compose Multiplatform (renderer)
implementation("io.github.yuroyami:kitepdf-native-renderer:0.13.0") // page-to-image on the platform canvas (headless+renderer)
implementation("io.github.yuroyami:kitepdf-skia-renderer:0.13.0") // page-to-image on Skia multiplatform (headless+renderer)
implementation("io.github.yuroyami:kitepdf-javascript:0.13.0") // runs the JavaScript inside PDFs and EPUBs (pulls KiteJS artifact)
implementation("io.github.yuroyami:kitepdf-net:0.13.0") // allows KitePDF to remotely load documents from a URL
implementation("io.github.yuroyami:kitepdf-media:0.13.0") // new in 0.13.0: plays EPUB audio and video in KiteDocView (pulls KitePlayer)kitepdf-core arrives with every handler. Do not add it yourself. kitepdf-media needs Android minSdk 26, and on iOS the linker flags and privacy manifest entries that KitePlayer's install guide lists.
Changelog
🔴 Breaking changes (8)
| Change | What to do |
|---|---|
#489: Every event method of PdfScriptHandler and EpubScriptHandler is now suspend |
Add suspend to each override, and call the methods from a coroutine |
#598: KiteJsScriptEngine.asmReports and PdfScriptRunner.asmReports are gone |
Remove the calls |
#506: EpubLayout has a new ROLL value |
Handle it in a when that has no else |
#544: KiteXmlNode and KiteXmlToken have a new Comment subclass |
Handle it in a when that has no else |
#428: The synchronous KitePageRasterizer.rasterize throws off the UI thread |
Call rasterizeOffMain from a worker |
10539a3: KiteScriptEngine.defineFunction passes binary data as a ByteArray |
Convert binary data the same way in your own KiteScriptEngine |
#209: KiteCanvas has a new rasterStep with a default body |
Override it in a by wrapper |
#519: KiteDocView opens web and mail links after a prompt |
Pass externalLinks = null to turn it off |
The last four still compile. They change what a call does, and a wrapper that overrides only the old KiteCanvas methods silently misses rasterStep.
Everything since 0.12.0 (196 changes)
| Area | Type | Change |
|---|---|---|
| Colour (3) | New Features | #201: Applied overprint mode 1 to DeviceCMYK path fills and strokes, and to those in the CMYK output intent |
| #625: Applied overprint to CMYK text, and to Separation and DeviceN fills, strokes, text and images | ||
| Fixes | #470: Read the samples of a CMYK JPEG in a PDF as ink, so a light image no longer turns black | |
| EPUB (19) | New Features | #38: Loaded the images, fonts and backgrounds that a book names by an https URL through EpubSettings.resourceFetcher. kitepdf-net ships one on Ktor |
#505: Scrolled a book whose rendition:flow is scrolled-continuous or scrolled-doc, and let the wheel move a page that overflows the view | ||
🔴 #506: Read a rendition:layout roll book of EPUB 3.4 as one strip. EpubLayout has a new ROLL value | ||
#510: Added EpubMetadata.titleRightToLeft and creatorsRightToLeft, the base direction of the title and of each creator | ||
#517: Added EpubDocument.markupErrors(chapter), which lists where a chapter breaks the rules of XML | ||
| #614: Showed an image that the spine lists as a page of its own | ||
| Performance | #389: Laid chapters out in steps with KiteDocument.prepareChapter(chapter, checkpoint), so JS and Wasm keep drawing while a chapter loads | |
| #492: Waited once per process for a stalled remote resource, where every chapter waited two seconds for it | ||
| Fixes | #502: Read the viewport tag of a fixed-layout page and its rendition overrides as EPUB Reading Systems 3.3 asks | |
#514: Loaded resources from data: URLs, over the new KiteDataUrl in kitepdf-core | ||
| #515: Collapsed the white space of package metadata values, as EPUB 3.3 asks | ||
| #516: Stopped loading a file of the book's zip that the manifest does not list | ||
| #518: Dropped a spine item whose fallback chain holds no content document, where its bytes were laid out as text | ||
| #526: Labelled a table of contents entry made of an image with the image's text alternative | ||
#527: Ignored an epub:type on the head, so a link to it no longer counts as a footnote | ||
#529: Kept a tag whole when a quoted attribute value holds a >, and read a < in a script as text | ||
#563: Counted only the chapters a book keeps in TocEntry.spineIndex | ||
#567: Returned from fetchRemoteResources only once remoteArrivals counts what it waited for | ||
| Docs | #497: Published an implementation report against the W3C EPUB 3 test suite, with a docs page that lists its 206 tests | |
| EPUB layout (37) | New Features | #207: Hyphenated EPUB text in 71 languages, every hyph-utf8 set that an Apache-2.0 library can carry, with their exception lists |
#508: Supported the eight CSS text properties that EPUB lists with -epub- prefixes, from tate-chu-yoko to emphasis marks | ||
| Performance | #452: Parsed deeply nested block elements in linear time, where it took quadratic time | |
| Fixes | #450: Limited how deep EPUB markup nests, so a deeply nested book cannot overflow the stack | |
#451: Limited how deep @media blocks and :not() selectors nest, so they cannot overflow the stack | ||
| #453: Kept the content that sits directly in a table, a row group or a row | ||
| #454: Laid out the text that sits directly in a floated block | ||
| #463: Passed a style on a table row group on to its rows and cells | ||
#490: Kept the declared box of an image with object-fit: contain, and fit the picture inside it | ||
#503: Painted every layer of a background-image, each with its own size, position and repeat | ||
| #507: Laid each chapter out in its own writing mode, where every chapter took the mode of the first one | ||
| #509: Applied the chapter's style sheets to an inline SVG | ||
#511: Drew the quotation marks of the q element in the chapter's language, from CLDR 48.2.0 | ||
| #512: Kept a book's page progression apart from its text direction, so a right-to-left book's English pages read left to right | ||
| #549: Matched the selectors of Selectors 4 in style sheets and scripts | ||
#551: Stopped a checked radio button from matching :indeterminate | ||
| #560: Decoded each picture of a chapter once, however many elements name it | ||
#564: Read a chapter without its own dir or lang left to right, as EPUB Reading Systems asks | ||
#565: Sized an SVG placed straight in a block by its width and height | ||
| #566: Took the missing side of an SVG from the aspect ratio of its viewBox | ||
#569: Gave a block or floated image without a size its intrinsic size. SvgImage.hasIntrinsicSize is new | ||
| #570: Decoded all 2125 of HTML's named character references | ||
| #571: Ended a DOCTYPE after its internal subset, and expanded the entities it declares | ||
| #572: Indented and justified the last line of a right-to-left paragraph from the right edge | ||
#573: Left a justified line that a <br> ends at its natural width | ||
#574: Broke a word wider than the line, and read overflow-wrap | ||
| #575: Kept the last valid value of a property when a later one cannot be read, and applied shorthands in order | ||
#576: Kept the spaces of a pre block in copied and searched text | ||
| #577: Kept a no-break space from collapsing or breaking | ||
#578: Drew text-decoration: overline | ||
#579: Read inherit, initial and unset on every property | ||
#580: Flowed an <svg> inside a sentence on its line, as an <img> does | ||
#606: Painted colours written with hsl(), hwb(), lab(), lch(), oklab(), oklch() and color() | ||
| #615: Stopped hyphenating a language that has no bundled patterns with the English ones | ||
#616: Kept hyphenation points in place after a letter such as İ whose lower case is two characters | ||
| #617: Hyphenated words that hold a combining mark | ||
| #618: Hyphenated words next to punctuation or with an apostrophe | ||
| EPUB media (12) | New Features | #31: Added kitepdf-media, a new optional artifact whose KiteMediaOverlay() plays the <video> and <audio> elements of an EPUB page on their boxes |
#36: Added KiteReadAloud(state, playing), which reads an EPUB aloud with its media overlays and follows the text in KiteDocView | ||
| #479: Let the transport bar of a media element seek by tap and drag, and show the elapsed and total time | ||
#481: Added KiteMediaSession, so a media element keeps its place while its book is open, and one element plays at a time | ||
| #482: Played a video full screen from its transport bar | ||
| #483: Showed the subtitle and caption tracks of a video, with a menu to pick a track | ||
| #484: Added a mute button and a speed menu from 0.5× to 2× to the transport bar | ||
#525: Gave the element that read-aloud reads the book's active classes, and spoke clips that have no audio through the new speak parameter | ||
| Fixes | #480: Let a tap pause and play a video that has no controls attribute | |
#491: Played a book's remote media only from https when allowRemote is on, where any scheme played, file: and http: included | ||
| #522: Read each clip of a media overlay that several chapters share once, in the order of the book | ||
#524: Made read-aloud follow the reader to another place in the book. KiteDocViewState.isScrollInProgress is new | ||
| EPUB scripts (41) | New Features | #41: Ran a book's scripts in the viewer with KiteDocView(epubScripts = EpubScriptRunner(book)), on KiteJS over the library's own layout, with no web engine |
| #500: Gave a book's scripts an origin of their own | ||
#501: Let a book's scripts draw on a <canvas> with a full CanvasRenderingContext2D | ||
#520: Gave a book's scripts URL and URLSearchParams of the WHATWG URL Standard | ||
#521: Gave each reader's copy of a book its own script origin, through a new instanceKey | ||
#528: Showed the book document that an <iframe> names, and ran its scripts in a window of their own | ||
#531: Gave a book's scripts FormData, FormDataEvent and SubmitEvent | ||
#532: Gave a book's scripts TextEncoder, TextDecoder, atob and btoa, with every encoding of the Encoding Standard | ||
#533: Gave a book's scripts Blob, File, FileReader and blob URLs | ||
#534: Delivered postMessage, and gave a book's scripts MessageChannel and structuredClone | ||
#536: Gave a book's scripts the Streams Standard, Blob.stream() included | ||
#543: Gave a book's scripts DOMParser and XMLSerializer | ||
#607: Gave a book's scripts AbortController and AbortSignal | ||
#608: Let a stream move through postMessage and structuredClone | ||
#609: Gave a book's scripts DOMPoint, DOMQuad and DOMMatrix | ||
| #610: Let a canvas read and write its pixels, in sRGB or Display P3 | ||
#612: Showed the book document that an <object> embeds, and ran its scripts | ||
#613: Made frames windows of another origin, with postMessage between them | ||
| Performance | 🔴 10539a3: Passed binary data between a script and the library as bytes, so getImageData on a 500 by 500 canvas takes 56 ms where it took 123 ms | |
| Fixes | #495: Gave a book's scripts navigator.epubReadingSystem | |
| #498: Opened a book whose second chapter has scripts, where it failed with "this thread already has an open engine" | ||
| #499: Laid the chapter out again before a script measures an element it just added or changed | ||
#530: Made DOMException the one of Web IDL, with its code and legacy constants | ||
| #535: Ran the promise jobs of each script callback before the next callback, as HTML's microtask checkpoint does | ||
| #537: Let a book's scripts add to and wrap the built-in objects, so a polyfill such as core-js loads | ||
| #538: Gave DOM objects their class strings and every element its own interface | ||
| #539: Ran an event handler at its place among the listeners of its target | ||
| #540: Kept the DOM working when a book patches the built-ins | ||
#541: Kept the case of tagName and nodeName in an XHTML chapter, which scripts now see as an XMLDocument | ||
#542: Made childNodes, children and the getElementsBy* collections live | ||
🔴 #544: Kept the comments of a chapter in its DOM, and made createComment make a comment | ||
#545: Gave attributes their namespace, prefix and case, so getAttribute('epub:type') works | ||
| #546: Showed the document type, processing instructions and CDATA sections of a chapter to its scripts | ||
#547: Made the missing <html>, <head> and <body> of an HTML chapter, as HTML's parser does | ||
#548: Read and wrote XML through innerHTML and its kin in an XHTML chapter | ||
#550: Gave a chapter opened at a fragment its location.hash, and matched :target. EpubDocument.setFragment is new | ||
#552: Kept the state of form controls beside the element, as HTML has it, so :checked follows what a script set | ||
#603: Made document.write and its kin throw in an XHTML chapter, as a browser does | ||
#604: Threw a HierarchyRequestError for an insert that would make no valid document | ||
#605: Sanitized an input's value by its type, as HTML says | ||
#611: Loaded an <img> as a browser does, with load and error events and its natural size | ||
| Images (7) | New Features | #513: Decoded AVIF, JPEG XL and lossy WebP images through ImageKodec 0.3.0 on every platform |
| Performance | #473: Decoded the four components of a CMYK JPEG once, through ImageKodec 0.3.0, where it decoded them four times | |
| #474: Decoded 600 dpi Group 4 fax pages 3.33 times faster, with identical output, through ImageKodec 0.3.0 | ||
| #475: Stopped decoding a PDF's JPEG at load only to check it, which took about a third of a page's render time | ||
| #476: Applied a finer mask block by block at a smaller size, so a layered scan no longer copies its ink to the full mask grid on every draw | ||
| #477: Converted each sample value of a low-bit image once, instead of each pixel through an allocating path | ||
| Fixes | #626: Filtered raster images reduced between power-of-two sizes over every source pixel that reaches an output pixel | |
| JavaScript (5) | New Features | 🔴 #598: Moved document scripts to KiteJS's QuickJS engine. PdfScriptHandler and EpubScriptHandler have a new prepare() |
| API Changes | 🔴 #489: Made every event method of PdfScriptHandler and EpubScriptHandler a suspend function, so a long script on JS and Wasm pauses about every 16 ms and the page keeps drawing | |
| Performance | #555: Opened scripted chapters two to six times faster after the first, by sharing the compiled DOM bytecode across engines | |
| Fixes | #553: Let two scripted documents run at once on JS and Wasm, and added EpubScriptSession.unloadChapters() | |
#554: Stopped counting the library's own DOM setup against the script budget of EpubScriptPolicy | ||
| Net (1) | Docs | #493: Named the Ktor version that kitepdf-net is built on in the loading guide |
| PDF (11) | Performance | #462: Sped up scanned pages drawn smaller than their pixels by averaging the samples instead of the RGBA, and added six scanned books to the render benchmark |
| #465: Reused the bitmaps of masked, stencil and inline images in Vectorized mode, where each draw converted them again | ||
| Fixes | #459: Extracted the plain text inside Form XObjects | |
#468: Used the ActualText of marked content in page text | ||
| #581: Read a reference to a missing object as null, as ISO 32000-1 says, where it failed the whole call | ||
| #582: Drew nothing for a TrueType glyph that reads past the end of the font, where it failed the whole page | ||
#583: Drew a page whose /Contents is null or of a wrong type as an empty page with its annotations | ||
#584: Made PdfDocument.open throw PdfFormatException, and openOrNull return null, for a file with no page tree | ||
| #585: Read a page tree kid that is no page dictionary as a blank page, so later pages keep their index | ||
#586: Wrote a complete cross-reference table in saveIncremental of a file that opened through repair | ||
| 8e591ea: Stopped a CMap parse at the end of its stream, so a cut-off ToUnicode or encoding stream no longer hangs the thread | ||
| Reading themes (3) | New Features | #458: Added ReaderTheme.withImages(ReaderImages.LineArt), which recolours grey line art such as symbols and equations drawn as images |
| Fixes | #456: Kept grey body text readable in the sepia theme | |
| #457: Kept saturated blue text readable in the dark theme | ||
| Rendering (4) | Performance | #382: Cached the coverage of glyphs drawn from embedded outlines, up to 4 MB, as MuPDF does |
| Fixes | #184: Drew an image that the shared decoders refuse as the same placeholder on every canvas, a damaged JPEG included | |
| #308: Blended the paints of a non-isolated transparency group with the page under it, as ISO 32000-1 11.4.8 says | ||
#591: Kept an off-centre radial shading in place on AndroidNativeCanvas below API 31 | ||
| SVG (3) | New Features | 🔴 #209: Drew SVG patterns, masks and all seventeen filter primitives, over the new KiteCanvas.rasterStep |
| Fixes | #523: Included the text that an SVG draws in its page text, so search and selection find it | |
#561: Painted nothing for a rect with a negative size, instead of failing the whole render | ||
| Tests and CI (21) | Fixes | #460: Released each PDFium comparison bitmap after its page, where the run kept them all |
| #461: Compared only the pixels two renders share, instead of rescaling the whole image for a one-pixel size difference | ||
| Tests | #194: Shipped the licensed public test documents with the repo, so a clean checkout runs the real-document tests | |
| #485: Found the blue text of the CSS nesting scene test in any font, so it passes on Linux | ||
| #494: Counted a buffering loop as playing in the full-screen media test | ||
| #496: Gated the scripted content of an EPUB with real books | ||
| #556: Ran a book's script and a form's script through the published artifacts in the consumer test | ||
| #557: Built the sample app on every platform in CI | ||
| #558: Recorded fetches under a lock in the remote resource tests | ||
| #559: Compared the fetches of the stalled URL test without their order | ||
| #562: Tested Linux and Wasm, and compiled Windows and Android native, in CI | ||
| #568: Held a paused player's position only after it settles in the read-aloud test | ||
| #599: Scrolled three notches in the selection wheel tests, so they pass on macOS | ||
| #601: Recorded the page counts of the current layout for local books in the EPUB sweep | ||
| #602: Ran the script tests on Linux native and Wasm in CI | ||
| #620: Added a vertical Japanese EPUB and PDF with embedded fonts to the corpus | ||
| #621: Added a JBIG2 file with an arithmetic-coded halftone region to the corpus | ||
| #623: Scored a wide-gamut page with a matrix profile against mutool | ||
| #624: Added 32 scientific PDFs and 31 complete EPUB publications to the public corpus | ||
| #627: Installed a Japanese fallback font on the Linux CI rendering gate | ||
| 9a253fc: Stored the corpus documents byte for byte, so their checksums hold | ||
| Text (15) | Performance | #590: Looked up the host face of a font once on SkiaCanvas, where each run of host text asked again |
| Fixes | #466: Read two-byte ToUnicode codes as the font maps them, so plain page text no longer turns letters into U+FFFD | |
| #467: Mapped a ToUnicode range with a two-character target to the right letters | ||
#469: Drew and extracted the right characters for an embedded CFF font without /Encoding | ||
| #471: Drew the vertical forms of punctuation in vertical Japanese text set in a substitute font | ||
| #472: Drew a non-embedded CJK font in a host face of its own language and style, such as a Mincho face for a Mincho font | ||
| #486: Drew right-to-left text in a host font the right way round in the Compose viewer | ||
#587: Drew a letter that a host face lacks in another host face on SkiaCanvas, whatever language the font names | ||
| #588: Drew host-font runs whose letters join or reorder as one string on the AWT, Android, Skia and Canvas2D canvases | ||
#589: Drew host-font text through CoreText on CoreGraphicsCanvas, with font fallback and joined Arabic | ||
| #593: Drew text in a generic font family in the bundled face of its family in a browser, where it drew in Roboto | ||
| #596: Decrypted each Type 1 subroutine before a glyph calls it, so the letters of LaTeX PDFs draw (reported by @GaurA1Apps) | ||
#597: Ran the charstrings of a Type 1 font whose /lenIV is -1 as they are | ||
| #600: Drew digits inside right-to-left text as the host's own paragraph draws them | ||
| #622: Placed a mark in right-to-left text where HarfBuzz puts it, where it sat over the next letter | ||
| Viewer (14) | New Features | #437: Added KitePageFit.PAGE to KiteDocLayout.Continuous, and prefetched the pages next to the screen once the strip rests |
🔴 #519: Opened a web or mail link that onLinkTap does not take, after a small prompt | ||
| API Changes | 🔴 #428: Made the synchronous KitePageRasterizer.rasterize require the UI thread, and throw before it draws on another one | |
| Performance | #131: Drew system-font text on the raster thread, so a page that needs host fonts renders off Main in one pass | |
#371: Shared a bounded cache of converted image bitmaps across redraws and recycled pages in Vectorized mode. imageCacheBudgetBytes sets it | ||
| #487: Rendered a page with host-font text off the main thread on Android | ||
| Fixes | #340: Kept document page numbers apart from viewer strip slots, so a highlight lands on the page it was made on | |
| #446: Showed the choices of a choice field in a picker, with typed values, where it opened a text keyboard | ||
#455: Delivered every tap to the host's onTap on a paged reader, also while a page settles (reported by @ksokolovskiy) | ||
| #464: Drew system-font text on Vectorized pages in a desktop scene that draws off the AWT thread | ||
| #504: Placed a page that asks for one side of a spread in that half when it has no partner | ||
#592: Turned the page of a horizontal Paged or Spread layout with the mouse wheel | ||
#594: Scrolled a Continuous layout with the mouse wheel while text is selected | ||
| #595: Drew a page again in a browser once Compose downloads the font for its missing-glyph boxes |
Dependencies
| Version | |
|---|---|
| Kotlin | 2.4.21 |
| Compose Multiplatform | 1.13.0-alpha02 (kitepdf-compose-viewer, kitepdf-media, passed on to apps) |
| Android Gradle Plugin | 9.4.1 (built with) |
| Gradle | 9.8.1 (built with) |
| JDK | 21 |
| Android SDK | compileSdk 37.2, minSdk 21 (kitepdf-compose-viewer 24, kitepdf-media 26, kitepdf-native-renderer 29) |
| ImageKodec | 0.3.0 (kitepdf-core, kept inside it; it was KiteImageCodec 0.2.0) |
| KiteJS | 0.7.0 (kitepdf-javascript, kitejs-api and kitejs-quickjs; it ran on the Rhino engine of 0.2.0) |
| KitePlayer | 0.3.0 (kitepdf-media, passed on to apps) |
| Ktor | 3.6.0 (kitepdf-net) |
| Skiko | 0.153.0 (kitepdf-skia-renderer, kitepdf-compose-viewer) |
Targets: Android, JVM, iOS, tvOS, watchOS, macOS, Linux, Windows, Android Native, JS and Wasm. kitepdf-javascript has no tvOS, watchOS, Android Native or wasmWasi build, and kitepdf-media publishes Android, iOS and the desktop JVM only. The viewer and the renderers build for fewer targets. The platforms page lists them.
Full changelog: v0.12.0...v0.13.0