diff --git a/README.md b/README.md index 7f04844..f895b8b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Flutter Package to parse EPUB files (EBooks), with support for Media Overlays! ✅ List sections (also commonly named "chapters") in default reading order ✅ Get section audio (if exists) ✅ Get text-audio synchronization info for each section -✅ Get text segment given a time for each section +✅ Get text segment given a time for each section +✅ Get page progression direction from the EPUB spine ### Work in progress @@ -318,6 +319,29 @@ And then, access its properties: +- **Retrieving page progression direction:** `epub.pageProgressionDirection` + + ```dart + import 'package:epub_decoder/epub_decoder.dart' show PageProgressionDirection; + + final PageProgressionDirection? direction = epub.pageProgressionDirection; + + switch (direction) { + case PageProgressionDirection.ltr: + // left-to-right + break; + case PageProgressionDirection.rtl: + // right-to-left + break; + case PageProgressionDirection.$default: + // The publication declares no preference. + break; + case null: + // Attribute is not declared. + break; + } + ``` + ## Additional information To understand EPUB3 specification and therefore plan the structure of this package, the following page was taken as reference: [Package and Metadata | EPUB3 Best Practices](https://www.oreilly.com/library/view/epub-3-best/9781449329129/ch01.html). diff --git a/lib/epub.dart b/lib/epub.dart index 727ef0b..ad98b12 100644 --- a/lib/epub.dart +++ b/lib/epub.dart @@ -20,9 +20,11 @@ class Epub extends Equatable { /// The [fileBytes] parameter should contain the raw bytes of the EPUB file. Epub.fromBytes(Uint8List fileBytes) : zip = ZipDecoder().decodeBytes(fileBytes) { + _rootFileContentCache = Lazy(_initializeRootFileContent); _metadata = Lazy(_initializeMetadata); _items = Lazy(_initializeItems); _sections = Lazy(_initializeSections); + _pageProgressionDirection = Lazy(_initializePageProgressionDirection); } /// Constructs an [Epub] instance from a [File]. @@ -31,17 +33,21 @@ class Epub extends Equatable { Epub.fromFile(File file) : assert(file.path._extension == 'epub'), zip = ZipDecoder().decodeBytes(file.readAsBytesSync()) { + _rootFileContentCache = Lazy(_initializeRootFileContent); _metadata = Lazy(_initializeMetadata); _items = Lazy(_initializeItems); _sections = Lazy(_initializeSections); + _pageProgressionDirection = Lazy(_initializePageProgressionDirection); } /// The decoded ZIP archive of the EPUB file. final Archive zip; + late final Lazy _rootFileContentCache; late final Lazy> _metadata; late final Lazy> _items; late final Lazy> _sections; + late final Lazy _pageProgressionDirection; String get title => metadata @@ -91,11 +97,14 @@ class Epub extends Equatable { /// Content of the root file as an XML document. /// /// Throws a [FormatException] if the root file is not found. - XmlDocument get _rootFileContent { + /// Parsed once and cached, since [_metadata], [_items], [_sections] + /// and [_pageProgressionDirection] all read from it. + XmlDocument get _rootFileContent => _rootFileContentCache.value; + + XmlDocument _initializeRootFileContent() { final file = zip.findFile(_rootFilePath); file ?? (throw const FormatException('Root file not found.')); - final content = XmlDocument.parse(utf8.decode(file.content)); - return content; + return XmlDocument.parse(utf8.decode(file.content)); } /// Metadata of the EPUB file, such as title, authors, media overlays, etc. @@ -201,6 +210,20 @@ class Epub extends Equatable { return sections; } + /// Reading direction of the EPUB, as declared in its `` element. + /// + /// Returns null when not specified. + PageProgressionDirection? get pageProgressionDirection => _pageProgressionDirection.value; + + PageProgressionDirection? _initializePageProgressionDirection() { + final spinexml = _rootFileContent.xpath('/package/spine').first; + final direction = spinexml.getAttribute('page-progression-direction'); + + return direction != null + ? PageProgressionDirection.fromValue(direction) + : null; + } + /// The list of properties that are used to determine whether two instances are equal. /// /// props[0] = metadata, props[1] = items, props[2] = sections @@ -212,7 +235,8 @@ class Epub extends Equatable { // _sections.isInitialized ? _sections.value : null, _metadata, _items, - _sections + _sections, + _pageProgressionDirection, ]; } diff --git a/lib/models/models.dart b/lib/models/models.dart index ca5ec17..3fe259a 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -4,6 +4,7 @@ export 'document_metadata.dart'; export 'item.dart'; export 'item_media_type.dart'; export 'item_property.dart'; +export 'page_progression_direction.dart'; export 'section.dart'; export 'smil_parallel.dart'; export 'lazy.dart'; diff --git a/lib/models/page_progression_direction.dart b/lib/models/page_progression_direction.dart new file mode 100644 index 0000000..c10480b --- /dev/null +++ b/lib/models/page_progression_direction.dart @@ -0,0 +1,16 @@ +/// Reading direction declared on the EPUB's `` element. +/// +/// Reference taken from https://www.w3.org/TR/epub-33/#attrdef-spine-page-progression-direction +enum PageProgressionDirection { + ltr('ltr'), + rtl('rtl'), + $default('default'); + + const PageProgressionDirection(this.value); + final String value; + + static PageProgressionDirection fromValue(String value) { + return PageProgressionDirection.values + .firstWhere((item) => item.value == value); + } +} diff --git a/test/epub_test.dart b/test/epub_test.dart index ce1ea2d..f9d5a66 100644 --- a/test/epub_test.dart +++ b/test/epub_test.dart @@ -120,4 +120,9 @@ void main() { expect(epubWithoutCover.cover, isNull); }); + + test('''Epub.pageProgressionDirection returns the direction declared + in ''', () { + expect(epub.pageProgressionDirection, PageProgressionDirection.ltr); + }); }