All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.2.3 - 2026-07-05
0.2.2 - 2026-07-05
Fixed
- The generated
nav.xhtmlno longer contains an empty<nav epub:type="landmarks">wrapper. epub-builder emits the wrapper even when there are no landmark entries, which fails epubcheck RSC-005 ("element \"nav\" incomplete; missing required element \"ol\""). The writer's post-processing pass now strips a landmarks nav that has no entries; a landmarks nav with real entries is kept verbatim. Built EPUBs that previously carried this epubcheck error are now clean, and the test suite no longer allowlists it. - The generated
toc.ncxno longer trips epubcheck RSC-005 "different playOrder values for navPoint/navTarget/pageTarget that refer to same target". epub-builder gives every navPoint a fresh sequentialplayOrder, so a file appearing in the TOC more than once (e.g. as both a nested child and a top-level entry — common in real books) got distinct values where the NCX spec requires one shared value. The writer now renumbersplayOrderin document order, 1-based, with repeat targets reusing the first occurrence's value. The test suite no longer allowlists this error either.
0.2.1 - 2026-07-05
0.2.0 - 2026-07-05
Added
LangelicEpub.Documentgains apage_progression_directionfield ("rtl","ltr", ornil). When set,build/1writes the OPF<spine page-progression-direction>attribute so right-to-left target languages (Arabic, Hebrew, …) paginate correctly in real readers. For"rtl", the generatednav.xhtmlroot<html>also getsdir="rtl"and the document language so table-of-contents labels render in the correct direction.LangelicEpub.Errormay now havekind: :invalid_page_directionwhenpage_progression_directionis anything other than"rtl","ltr", ornil. The value is rejected at build time rather than silently dropped.
Changed
- When
page_progression_directionisnil, the built OPF now omits the<spine>page-progression-directionattribute entirely. Previously the underlyingepub-builderunconditionally emittedpage-progression-direction="ltr". An omitted direction is semantically identical (readers default toltr) but no longer hard-codes a direction the caller never asked for. Existingnilbuilds change bytes but not rendering.
Notes
- The reader continues to not surface a source EPUB's spine direction:
parse/1always returnspage_progression_direction: nil. Direction is a build-time decision derived from the target language, never round-tripped from the source (artlJapanese source rebuilt into English must shed it).
0.1.1 - 2026-06-27
Changed
- Dependency updates:
rustler0.37 → 0.38 (Elixir + crate),zip7 → 8,quick-xml0.39 → 0.40, plusuuid,credo, andex_docbumps. The quick-xmlAttribute::unescape_valueAPI was deprecated; migrated tonormalized_value(XmlVersion::Implicit1_0), which is behavior-identical (same XML 1.0 implicit version, depth, and predefined-entity resolver). No user-facing API change. - Reader no longer depends on the
iepubcrate. All structural data (manifest, spine, metadata, cover, TOC) now comes from a purezip+quick_xmlstack. This removes a large transitive dependency surface and lets us tolerate non-OCF-conformantmimetypefiles (trailing\n,\r\n, space, or a leading UTF-8 BOM) — common in Calibre exports and other real-world EPUBs that other readers accept silently. Genuine non-EPUB content is still rejected with a new:invalid_mimetypeerror kind.
Added
LangelicEpub.Errormay now havekind: :invalid_mimetypewhen themimetypezip entry is missing or its content (after trimming a UTF-8 BOM and whitespace) is notapplication/epub+zip.- OTP 29 support:
rustler0.38 builds against OTP 29's NIF interface, and CI now tests on OTP 29 / Elixir 1.20 in addition to OTP 26/27. The precompiled NIF 2.16 artifact forward-loads on OTP 29's newer NIF ABI, so no new artifact is shipped (a 2.17 artifact would needlessly drop OTP 26 compatibility).
Fixed
- Dublin Core metadata is now matched by its namespace URI rather than a
hard-coded
dc:prefix (the OPF is parsed with quick-xml'sNsReader), sodc:-equivalent elements bound to a non-standard prefix or a default namespace are recovered instead of silently dropped. Parsing the OPF also now rejects elements that declare more than 256 namespace bindings, bounding a malformed or hostile package.
0.1.0 - 2026-04-20
Added
LangelicEpub.parse/1— parse EPUB 2 and EPUB 3 bytes into a%LangelicEpub.Document{}with spine, assets, table of contents, and metadata (including fields like<dc:language>,<dc:rights>, and multiple<dc:creator>entries that the underlying iepub crate does not expose natively).LangelicEpub.build/1— emit EPUB 3 bytes from a%LangelicEpub.Document{}, with a backward-compatibletoc.ncxalongside the EPUB 3nav.xhtmlso EPUB 2-only readers still navigate correctly.- Validation of required fields (
title,identifier,language) and spine/asset ID uniqueness at build time. UTF-8 is enforced on chapterdatato prevent silent corruption in downstream readers. - Rust panics inside the NIF are caught (
std::panic::catch_unwind) and returned as{:error, %LangelicEpub.Error{kind: :panic, ...}}so a malformed input cannot crash the BEAM scheduler. - Precompiled NIFs published via GitHub Releases for
aarch64-apple-darwin,x86_64-apple-darwin,aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu, andx86_64-unknown-linux-musl.