epub_rtl 0.1.0 copy "epub_rtl: ^0.1.0" to clipboard
epub_rtl: ^0.1.0 copied to clipboard

Pure-Flutter EPUB 2/3 parser and reader with right-to-left support for Arabic books: page-progression-direction, dir, bidi, paginated RTL paging.

epub_rtl #

A pure-Flutter EPUB 2/3 parser and reader that handles right-to-left books. It is built for Arabic and other RTL texts, and it uses no WebView.

pub package License: MIT

Features #

Parser (pure Dart, depends only on archive and xml)

  • Finds the package document through META-INF/container.xml.
  • Reads the OPF:
    • metadata: titles, creators with role and file-as (EPUB 2 attributes or EPUB 3 refinements), languages, identifiers, publisher, date, description, dcterms:modified and the cover image;
    • the manifest;
    • the spine, including linear and page-progression-direction. EpubBook.isRtl uses the spine direction, and falls back to the book language when the spine declares none.
  • Navigation: reads the EPUB 3 nav document (toc, landmarks, page-list), and falls back to the EPUB 2 NCX (navMap, pageList) with the <guide> as landmarks.
  • Content access by href:
    • resolves relative paths (.., ./, percent-encoding, fragments);
    • reads entries lazily: only the zip directory is read up front, and each chapter or resource is inflated when it is requested and not cached;
    • EpubBook.openFile reads entries from disk on demand.
  • Errors:
    • malformed books throw a typed EpubException with an EpubErrorKind;
    • lenient: true recovers from common real-world errors and records them in warnings. These include a missing or misplaced mimetype, a missing NCX or nav document, missing or unknown media types, dangling spine references and wrong letter case in paths.
  • Encryption: a book with DRM listed in META-INF/encryption.xml is refused with EpubErrorKind.encrypted. A book whose only encrypted resources are obfuscated fonts opens in lenient mode, and those fonts are unreadable.

Content model (pure Dart): EpubChapter turns XHTML into a flat list of blocks. Each id maps to a block index, which is what locations point to.

  • Blocks: headings, paragraphs, pre, images (including SVG-wrapped covers), lists, blockquotes, basic tables and rules.
  • Inline content: emphasis, bold, underline, strike, sup/sub, code, links and basic ruby.
  • dir and lang are inherited. dir="auto" uses the first strong character.
  • Inline dir is wrapped in bidi isolates (LRI/RLI … PDI), bdi in FSI … PDI, and bdo in LRO/RLO … PDF.
  • A CSS subset from linked stylesheets, <style> and style="":
    • properties: text-align, font-style, font-weight, margin*, text-indent, direction and display: none;
    • selectors: tag, class, id and descendant.

Reader (Flutter)

  • EpubReaderView has two modes:
    • scroll: one continuous vertical scroll through the whole book;
    • paginated: one chapter per page, swiped in the book's reading direction. RTL books advance with a right swipe.
  • Reading position: EpubLocation (spine index + element index + offset fraction). You can serialise it with toJson or encode, restore it with initialLocation, and get it through onLocationChanged and the controller.
  • EpubReaderController: jumpTo, jumpToHref (TOC entries and internal links), nextChapter, previousChapter.
  • EpubTocDrawer: a TOC drawer in the book's direction.
  • EpubReaderSettings:
    • font size and line height;
    • light, sepia and dark themes;
    • arabicFontFamily, used for RTL-language runs and first in the fallback list;
    • justification.
  • Jumps inside a chapter use GlobalKeys and Scrollable.ensureVisible. The package does not depend on the archived scrollable_positioned_list.
  • Internal links jump inside the book. External links go to onExternalLink.

Install #

dependencies:
  epub_rtl: ^0.1.0

Usage #

import 'package:epub_rtl/epub_rtl.dart';
import 'package:flutter/material.dart';

class ReaderScreen extends StatefulWidget {
  const ReaderScreen({super.key, required this.bytes, this.saved});

  final List<int> bytes;
  final String? saved; // a previously stored EpubLocation.encode()

  @override
  State<ReaderScreen> createState() => _ReaderScreenState();
}

class _ReaderScreenState extends State<ReaderScreen> {
  late final EpubBook book = EpubBook.fromBytes(widget.bytes, lenient: true);
  final controller = EpubReaderController();

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: Text(book.metadata.title)),
        drawer: EpubTocDrawer(book: book, controller: controller),
        body: EpubReaderView(
          book: book,
          controller: controller,
          mode: EpubReaderMode.paginated,
          settings: const EpubReaderSettings(
            fontSize: 20,
            theme: EpubReaderTheme.sepia,
            arabicFontFamily: 'Amiri', // bundle the font in your app
            justify: true,
          ),
          initialLocation:
              widget.saved == null ? null : EpubLocation.tryDecode(widget.saved!),
          onLocationChanged: (location) {
            // Persist location.encode() or location.toJson().
          },
          onExternalLink: (url) {
            // Open with url_launcher, for example.
          },
        ),
      );
}

Parser only:

final book = EpubBook.fromBytes(bytes);
print(book.metadata.title);
print(book.spine.pageProgressionDirection); // PageProgressionDirection.rtl
for (final entry in book.navigation.toc) {
  print('${entry.title} -> ${entry.href}');
}
final chapter = EpubChapter.fromBook(book, 0);
print(chapter.blocks.length);

Migrating from epubx / epub_view #

epubx has had no release since June 2023. epub_view depends on it and on the archived scrollable_positioned_list.

epubx / epub_view epub_rtl
EpubReader.readBook(bytes) (async, loads everything) EpubBook.fromBytes(bytes) (sync, lazy)
book.Title, book.Author book.metadata.title, book.metadata.creators
book.Chapters book.navigation.toc (titles + hrefs) and book.spine.items
chapter.HtmlContent book.chapterHtml(i) or EpubChapter.fromBook(book, i)
book.Content.Images[name] book.readBytes(fullPath)
EpubView(controller: EpubController(document: ...)) EpubReaderView(book: book, controller: EpubReaderController())
EpubController.generateEpubCfi() / gotoEpubCfi() controller.location (EpubLocation) / controller.jumpTo(location)
EpubViewTableOfContents EpubTocDrawer

Locations are not EPUB CFIs. Saved epub_view CFIs cannot be converted.

Limitations #

  • DRM-encrypted books are refused. Obfuscated embedded fonts are not de-obfuscated, and embedded fonts are not loaded. Use fontFamily and arabicFontFamily with fonts bundled in your app.
  • Paginated mode shows one chapter per page, and the page scrolls vertically. The text is not split into screen-sized pages.
  • The CSS support is a small subset: no floats, positioning, colours, font sizes from CSS, pseudo-classes, >/+/~ selectors or @media rules.
  • Tables are basic: no colspan or rowspan.
  • Ruby is shown as annotation-over-base, without full ruby layout.
  • SVG images, audio, video, MathML and scripts are not rendered. Fixed-layout EPUBs are rendered as reflowable text.
  • Each chapter is built as one widget subtree so element keys exist for jumps. Very long single chapters cost more to lay out.
  • EpubBook.openFile needs dart:io and is not available on the web. Use fromBytes there.

Data sources #

No third-party book content is bundled. The test fixtures and the example's sample book are generated in code, with text written for this package.

License #

MIT, see LICENSE.

Made by Abdeldjalil Chougui.

0
likes
150
points
324
downloads

Documentation

API reference

Publisher

verified publisherabdeldjalilchougui.biz

Weekly Downloads

Pure-Flutter EPUB 2/3 parser and reader with right-to-left support for Arabic books: page-progression-direction, dir, bidi, paginated RTL paging.

Homepage
Repository (GitHub)
View/report issues

Topics

#epub #ebook #reader #rtl #arabic

License

MIT (license)

Dependencies

archive, flutter, xml

More

Packages that depend on epub_rtl