mangayomi-mirror/epubx/lib/src/epub_reader.dart
2025-05-04 23:45:46 +02:00

183 lines
6.7 KiB
Dart

import 'dart:async';
import 'package:archive/archive.dart';
import 'entities/epub_book.dart';
import 'entities/epub_byte_content_file.dart';
import 'entities/epub_chapter.dart';
import 'entities/epub_content.dart';
import 'entities/epub_content_file.dart';
import 'entities/epub_text_content_file.dart';
import 'readers/content_reader.dart';
import 'readers/schema_reader.dart';
import 'ref_entities/epub_book_ref.dart';
import 'ref_entities/epub_byte_content_file_ref.dart';
import 'ref_entities/epub_chapter_ref.dart';
import 'ref_entities/epub_content_file_ref.dart';
import 'ref_entities/epub_content_ref.dart';
import 'ref_entities/epub_text_content_file_ref.dart';
import 'schema/opf/epub_metadata_creator.dart';
/// A class that provides the primary interface to read Epub files.
///
/// To open an Epub and load all data at once use the [readBook()] method.
///
/// To open an Epub and load only basic metadata use the [openBook()] method.
/// This is a good option to quickly load text-based metadata, while leaving the
/// heavier lifting of loading images and main content for subsequent operations.
///
/// ## Example
/// ```dart
/// // Read the basic metadata.
/// EpubBookRef epub = await EpubReader.openBook(epubFileBytes);
/// // Extract values of interest.
/// String title = epub.Title;
/// String author = epub.Author;
/// var metadata = epub.Schema.Package.Metadata;
/// String genres = metadata.Subjects.join(', ');
/// ```
class EpubReader {
/// Loads basics metadata.
///
/// Opens the book asynchronously without reading its main content.
/// Holds the handle to the EPUB file.
///
/// Argument [bytes] should be the bytes of
/// the epub file you have loaded with something like the [dart:io] package's
/// [readAsBytes()].
///
/// This is a fast and convenient way to get the most important information
/// about the book, notably the [Title], [Author] and [AuthorList].
/// Additional information is loaded in the [Schema] property such as the
/// Epub version, Publishers, Languages and more.
static Future<EpubBookRef> openBook(FutureOr<List<int>> bytes) async {
List<int> loadedBytes;
if (bytes is Future) {
loadedBytes = await bytes;
} else {
loadedBytes = bytes;
}
var epubArchive = ZipDecoder().decodeBytes(loadedBytes);
var bookRef = EpubBookRef(epubArchive);
bookRef.Schema = await SchemaReader.readSchema(epubArchive);
bookRef.Title = bookRef.Schema!.Package!.Metadata!.Titles!
.firstWhere((String name) => true, orElse: () => '');
bookRef.AuthorList = bookRef.Schema!.Package!.Metadata!.Creators!
.map((EpubMetadataCreator creator) => creator.Creator)
.toList();
bookRef.Author = bookRef.AuthorList!.join(', ');
bookRef.Content = ContentReader.parseContentMap(bookRef);
return bookRef;
}
/// Opens the book asynchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
static Future<EpubBook> readBook(FutureOr<List<int>> bytes) async {
var result = EpubBook();
List<int> loadedBytes;
if (bytes is Future) {
loadedBytes = await bytes;
} else {
loadedBytes = bytes;
}
var epubBookRef = await openBook(loadedBytes);
result.Schema = epubBookRef.Schema;
result.Title = epubBookRef.Title;
result.AuthorList = epubBookRef.AuthorList;
result.Author = epubBookRef.Author;
result.Content = await readContent(epubBookRef.Content!);
result.CoverImage = await epubBookRef.readCover();
var chapterRefs = await epubBookRef.getChapters();
result.Chapters = await readChapters(chapterRefs);
return result;
}
static Future<EpubContent> readContent(EpubContentRef contentRef) async {
var result = EpubContent();
result.Html = await readTextContentFiles(contentRef.Html!);
result.Css = await readTextContentFiles(contentRef.Css!);
result.Images = await readByteContentFiles(contentRef.Images!);
result.Fonts = await readByteContentFiles(contentRef.Fonts!);
result.AllFiles = <String, EpubContentFile>{};
result.Html!.forEach((String? key, EpubTextContentFile value) {
result.AllFiles![key!] = value;
});
result.Css!.forEach((String? key, EpubTextContentFile value) {
result.AllFiles![key!] = value;
});
result.Images!.forEach((String? key, EpubByteContentFile value) {
result.AllFiles![key!] = value;
});
result.Fonts!.forEach((String? key, EpubByteContentFile value) {
result.AllFiles![key!] = value;
});
await Future.forEach(contentRef.AllFiles!.keys, (dynamic key) async {
if (!result.AllFiles!.containsKey(key)) {
result.AllFiles![key] =
await readByteContentFile(contentRef.AllFiles![key]!);
}
});
return result;
}
static Future<Map<String, EpubTextContentFile>> readTextContentFiles(
Map<String, EpubTextContentFileRef> textContentFileRefs) async {
var result = <String, EpubTextContentFile>{};
await Future.forEach(textContentFileRefs.keys, (dynamic key) async {
EpubContentFileRef value = textContentFileRefs[key]!;
var textContentFile = EpubTextContentFile();
textContentFile.FileName = value.FileName;
textContentFile.ContentType = value.ContentType;
textContentFile.ContentMimeType = value.ContentMimeType;
textContentFile.Content = await value.readContentAsText();
result[key] = textContentFile;
});
return result;
}
static Future<Map<String, EpubByteContentFile>> readByteContentFiles(
Map<String, EpubByteContentFileRef> byteContentFileRefs) async {
var result = <String, EpubByteContentFile>{};
await Future.forEach(byteContentFileRefs.keys, (dynamic key) async {
result[key] = await readByteContentFile(byteContentFileRefs[key]!);
});
return result;
}
static Future<EpubByteContentFile> readByteContentFile(
EpubContentFileRef contentFileRef) async {
var result = EpubByteContentFile();
result.FileName = contentFileRef.FileName;
result.ContentType = contentFileRef.ContentType;
result.ContentMimeType = contentFileRef.ContentMimeType;
result.Content = await contentFileRef.readContentAsBytes();
return result;
}
static Future<List<EpubChapter>> readChapters(
List<EpubChapterRef> chapterRefs) async {
var result = <EpubChapter>[];
await Future.forEach(chapterRefs, (EpubChapterRef chapterRef) async {
var chapter = EpubChapter();
chapter.Title = chapterRef.Title;
chapter.ContentFileName = chapterRef.ContentFileName;
chapter.Anchor = chapterRef.Anchor;
chapter.HtmlContent = await chapterRef.readHtmlContent();
chapter.SubChapters = await readChapters(chapterRef.SubChapters!);
result.add(chapter);
});
return result;
}
}