diff --git a/.gitmodules b/.gitmodules index 44193fc5f..be64f08be 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "components/cpp-misc"] path = components/cpp-misc url = git@github.com:sindarin-inc/cpp-misc.git +[submodule "components/tiny-epub"] + path = components/tiny-epub + url = git@github.com:sindarin-inc/tiny-epub.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 320ba6521..7070fa5c2 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -73,6 +73,20 @@ "cStandard": "c17", "cppStandard": "c++17", "compileCommands": "${workspaceFolder}/components/esp_lcd_seeya103/test_apps/build-esp32p4/compile_commands.json" + }, + { + "name": "sim", + "includePath": [ + "${default}" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "cStandard": "c17", + "cppStandard": "c++17", + "compileCommands": "${workspaceFolder}/lib/Simulator/build/compile_commands.json" } ], "version": 4 diff --git a/components/cpp-misc b/components/cpp-misc index 104e9e07b..c8d15a094 160000 --- a/components/cpp-misc +++ b/components/cpp-misc @@ -1 +1 @@ -Subproject commit 104e9e07be93ca30e1d4e2d518c1e93f4796e24e +Subproject commit c8d15a094c2c7f4c7fb41e78228502713fdd173c diff --git a/components/tiny-epub b/components/tiny-epub new file mode 160000 index 000000000..3cea0c805 --- /dev/null +++ b/components/tiny-epub @@ -0,0 +1 @@ +Subproject commit 3cea0c8059bae485e555003b72d25e4d2f86585f diff --git a/components/tiny-font b/components/tiny-font index 72fee05fc..53838e7c7 160000 --- a/components/tiny-font +++ b/components/tiny-font @@ -1 +1 @@ -Subproject commit 72fee05fc30a120b8e32b82dd1893208ea31dd70 +Subproject commit 53838e7c76943d95e209372bf65ad2d058e603d7 diff --git a/lib/Simulator/Common/CMakeLists.txt b/lib/Simulator/Common/CMakeLists.txt index 48f769db5..b0ee5d363 100644 --- a/lib/Simulator/Common/CMakeLists.txt +++ b/lib/Simulator/Common/CMakeLists.txt @@ -43,17 +43,36 @@ add_subdirectory(${REPO_ROOT_DIR}/components/miniz ${CMAKE_CURRENT_BINARY_DIR}/m add_subdirectory(${REPO_ROOT_DIR}/components/Adafruit-GFX-Library ${CMAKE_CURRENT_BINARY_DIR}/Adafruit-GFX-Library) +# Compile configs to have libraries use our esp-idf stubs +add_library(stubs_config INTERFACE) +target_compile_definitions(stubs_config INTERFACE + CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=1 + CONFIG_LOG_MAXIMUM_LEVEL=4 + ESP_PLATFORM=1 +) +target_include_directories(stubs_config INTERFACE ${REPO_ROOT_DIR}/lib/Simulator/stubs/esp-idf) + add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/defer" ${CMAKE_CURRENT_BINARY_DIR}/defer) add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/spiram-cpp" ${CMAKE_CURRENT_BINARY_DIR}/spiram-cpp) -# Have spiram-cpp use our esp-idf stubs -target_include_directories(spiram-cpp PUBLIC ${REPO_ROOT_DIR}/lib/Simulator/stubs/esp-idf) -target_compile_definitions(spiram-cpp PUBLIC ESP_PLATFORM=1) +target_link_libraries(spiram-cpp PUBLIC stubs_config) add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/stringutil" ${CMAKE_CURRENT_BINARY_DIR}/stringutil) +add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/bmpimage" ${CMAKE_CURRENT_BINARY_DIR}/bmpimage) +target_link_libraries(bmpimage PUBLIC stubs_config) + +add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/unzipper" ${CMAKE_CURRENT_BINARY_DIR}/unzipper) +target_link_libraries(unzipper PUBLIC stubs_config) + +add_subdirectory("${REPO_ROOT_DIR}/components/cpp-misc/idflog" ${CMAKE_CURRENT_BINARY_DIR}/idflog) + add_subdirectory(${REPO_ROOT_DIR}/components/tiny-font ${CMAKE_CURRENT_BINARY_DIR}/tiny-font) +add_subdirectory(${REPO_ROOT_DIR}/components/tiny-epub/epubfile ${CMAKE_CURRENT_BINARY_DIR}/epubfile) + +add_subdirectory(${REPO_ROOT_DIR}/components/tiny-epub/renderer ${CMAKE_CURRENT_BINARY_DIR}/renderer) + # Conditionally link freetype if we're using 8-bit or 16-bit display if(DISPLAY_TYPE STREQUAL "DISPLAY_SIM_24BIT" OR DISPLAY_TYPE STREQUAL "DISPLAY_SIM_16BIT" OR DISPLAY_TYPE STREQUAL "DISPLAY_SIM_8BIT") # Set FreeType options before adding the subdirectory @@ -67,11 +86,19 @@ if(DISPLAY_TYPE STREQUAL "DISPLAY_SIM_24BIT" OR DISPLAY_TYPE STREQUAL "DISPLAY_S CONFIG_TINYFONT_IBMF=0 CONFIG_TINYFONT_DISPLAY_DPI=${TTF_SCREEN_RES_PER_INCH} ) + target_compile_definitions(renderer PRIVATE + CONFIG_EPUB_RENDERER_REPLACE_SUPERSUBSCRIPT_CHARACTERS=0 + CONFIG_EPUB_RENDERER_NATIVE_SUPERSUBSCRIPT_CHARACTERS=1 + ) else() target_compile_definitions(tiny-font PUBLIC CONFIG_TINYFONT_TTF=0 CONFIG_TINYFONT_IBMF=1 ) + target_compile_definitions(renderer PRIVATE + CONFIG_EPUB_RENDERER_REPLACE_SUPERSUBSCRIPT_CHARACTERS=1 + CONFIG_EPUB_RENDERER_NATIVE_SUPERSUBSCRIPT_CHARACTERS=0 + ) endif() add_subdirectory(${REPO_ROOT_DIR}/lib/esp_ringbuf ${CMAKE_CURRENT_BINARY_DIR}/esp_ringbuf) @@ -90,7 +117,16 @@ endif() add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HTTP_CLIENT_SOURCE}) -target_link_libraries(${PROJECT_NAME} PUBLIC pugixml ArduinoJson miniz astubs esp-idf-stubs nvs_flash Adafruit-GFX-Library tiny-font mbedtls mbedx509 mbedcrypto esp_ringbuf defer spiram-cpp stringutil) +target_link_libraries(${PROJECT_NAME} PUBLIC ArduinoJson miniz astubs esp-idf-stubs nvs_flash Adafruit-GFX-Library tiny-font mbedtls mbedx509 mbedcrypto esp_ringbuf defer spiram-cpp stringutil epubfile renderer) + +target_compile_definitions(renderer PUBLIC + CONFIG_EPUB_RENDERER_MAX_BYTES_PER_PARAGRAPH=5003 + CONFIG_EPUB_RENDERER_MAX_BYTES_PER_DISPLAY_PAGE=2400 + CONFIG_EPUB_RENDERER_CSTR_POOL_SIZE=2000 + CONFIG_EPUB_RENDERER_TOKENS_SIZE=500 + CONFIG_EPUB_RENDERER_MAX_BYTES_PER_LINE=200 + CONFIG_EPUB_RENDERER_LINES_SIZE=36 +) target_include_directories(${PROJECT_NAME} PUBLIC "${REPO_ROOT_DIR}/src" diff --git a/lib/Simulator/stubs/esp-idf/CMakeLists.txt b/lib/Simulator/stubs/esp-idf/CMakeLists.txt index 91763aef6..c35a7df35 100644 --- a/lib/Simulator/stubs/esp-idf/CMakeLists.txt +++ b/lib/Simulator/stubs/esp-idf/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.5) project(esp-idf-stubs) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Path to the root of the repo set(REPO_ROOT_DIR "${PROJECT_SOURCE_DIR}/../../../..") diff --git a/lib/Simulator/stubs/esp-idf/esp_log.cpp b/lib/Simulator/stubs/esp-idf/esp_log.cpp index 4e0e95cc2..a23aef522 100644 --- a/lib/Simulator/stubs/esp-idf/esp_log.cpp +++ b/lib/Simulator/stubs/esp-idf/esp_log.cpp @@ -1,5 +1,23 @@ #include "esp_log.h" +#include +#include + void esp_log_level_set(const char *tag, esp_log_level_t level) { return; } esp_log_level_t esp_log_level_get(const char *tag) { return ESP_LOG_DEBUG; } + +uint32_t esp_log_timestamp(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); +} + +void esp_log_write(esp_log_level_t level, const char *tag, const char *format, ...) { + va_list args; + va_start(args, format); + char buffer[512]; + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + fprintf(stdout, "%s", buffer); +} diff --git a/scripts/epub-render/main.cpp b/scripts/epub-render/main.cpp index 3ffb0aba8..c26fb0f81 100644 --- a/scripts/epub-render/main.cpp +++ b/scripts/epub-render/main.cpp @@ -10,9 +10,12 @@ #include #pragma clang diagnostic pop +#include +#include +#include + #include "DisplayUtil.hpp" -#include "EPub/EPubFile.hpp" -#include "Renderers/Renderer.hpp" +#include "Storage/SFileUnzipperStream.hpp" #include "UI/Fonts.hpp" int main(int argc, char **argv) { @@ -121,11 +124,14 @@ int main(int argc, char **argv) { readable->readableType = readableType; PreferencesStore prefs; - SolDrm solDrm(prefs); - auto epubFile = std::make_shared(file, readable, solDrm); - - Renderer renderer(*display, boundingBoxRect, font); - renderer.setStream(epubFile); + auto solDrm = std::make_shared(prefs); + auto unzipperStream = std::make_shared(file); + auto epubFile = std::make_shared(unzipperStream, solDrm); + epubFile->setIsAnArticle(readable->readableType == "article"); + auto epubStream = std::make_shared(epubFile); + + Renderer renderer(*display, boundingBoxRect, FontHandle{font}); + renderer.setStream(epubStream); renderer.renderPage(place); renderer.setLineSpacing(lineSpacing); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c06fdbe01..f2d181e3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,6 @@ set(components miniz ulp astubs - pugixml improv ArduinoJson Adafruit-GFX-Library diff --git a/src/Communications/HtmlCleaner.hpp b/src/Communications/HtmlCleaner.hpp index 846ed24ae..d61c09bae 100644 --- a/src/Communications/HtmlCleaner.hpp +++ b/src/Communications/HtmlCleaner.hpp @@ -1,11 +1,11 @@ #pragma once +#include #include #include #include #include -#include "Renderers/RendererStream.hpp" #include "Storage/SFile.hpp" enum StripHtmlState { diff --git a/src/Displays/DisplaySystem.hpp b/src/Displays/DisplaySystem.hpp index cc5e3be2a..053e40e4a 100644 --- a/src/Displays/DisplaySystem.hpp +++ b/src/Displays/DisplaySystem.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -65,13 +66,19 @@ struct DisplayRect { [[nodiscard]] auto inset(uint16_t xAmount, uint16_t yAmount) const -> DisplayRect { return inset(xAmount, xAmount, yAmount, yAmount); } + + // Implicit conversion to tiny-epub RendererDisplayRect + operator RendererDisplayRect() const { + return {static_cast(x), static_cast(y), static_cast(width), + static_cast(height)}; + } }; -class DisplaySystem { +class DisplaySystem : public RendererDisplay { public: DisplaySystem(); DisplaySystem(DisplaySystem const &) = delete; - virtual ~DisplaySystem() = default; + ~DisplaySystem() override = default; // Constant text-box inset defaults during reading. These should be overridden to sane values in // subclasses. @@ -124,14 +131,14 @@ class DisplaySystem { // Use these methods for normal drawing as they'll take into account the offsets and focus area // automatically. Any methods we need from Adafruit_GFX that need to respect the focus area // should be copied in here. - [[nodiscard]] auto width() const -> int16_t { + [[nodiscard]] inline auto width() const -> int16_t override { return nativeWidth() - displayInsets().left - displayInsets().right; }; - [[nodiscard]] auto height() const -> int16_t { + [[nodiscard]] inline auto height() const -> int16_t override { return nativeHeight() - displayInsets().top - displayInsets().bottom; }; - void fillScreen(uint16_t color) { gfxClass_->fillScreen(color); } + void fillScreen(uint16_t color) override { gfxClass_->fillScreen(color); } void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) { gfxClass_->fillTriangle(xOffset(x0), yOffset(y0), xOffset(x1), yOffset(y1), xOffset(x2), @@ -141,7 +148,7 @@ class DisplaySystem { void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { gfxClass_->fillRect(xOffset(x), yOffset(y), w, h, color); } - void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) { + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) override { gfxClass_->drawLine(xOffset(x0), yOffset(y0), xOffset(x1), yOffset(y1), color); } void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { @@ -205,12 +212,11 @@ class DisplaySystem { -> int; auto getTextWidth(const std::string &buffer, Font *font = static_cast(&fontFace0)) -> int; - inline auto getTextWidthQuick(const char *buffer, Font *font = static_cast(&fontFace0)) - -> int { - if (font == nullptr) { + inline auto getTextWidthQuick(const char *cstr, FontHandle font) -> uint16_t override { + if (font.ptr == nullptr) { return -1; } else { - return font->getTextWidthQuick(buffer); + return reinterpret_cast(font.ptr)->getTextWidthQuick(cstr); } } @@ -227,7 +233,76 @@ class DisplaySystem { // Draw a bitmap void drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, - uint16_t color); + uint16_t color) override; + + inline auto lineHeight(FontHandle font) -> uint16_t override { + return font.ptr ? reinterpret_cast(font.ptr)->lineHeight() : 0; + } + + inline auto getTextWidth(const char *cstr, FontHandle font) -> uint16_t override { + if (!font.ptr || cstr == nullptr) { + return 0; + } + return reinterpret_cast(font.ptr)->getTextWidth(cstr); + } + + inline void getTextSize(const char *cstr, uint16_t *outWidth, uint16_t *outHeight, + FontHandle font) override { + if (!outWidth || !outHeight) { + return; + } + if (!font.ptr || !cstr) { + *outWidth = 0; + *outHeight = 0; + return; + } + Dim dim = reinterpret_cast(font.ptr)->getTextSize(std::string(cstr)); + *outWidth = dim.width; + *outHeight = dim.height; + } + + [[nodiscard]] auto getSuperscriptCharacter(const char inputChar, FontHandle font) const -> const + char * override { +#if CONFIG_TINYFONT_IBMF + return reinterpret_cast(font.ptr)->getSuperscriptCharacter(inputChar); +#elif CONFIG_TINYFONT_TTF + return nullptr; +#else +#error "No font system selected" +#endif + }; + + [[nodiscard]] auto getSubscriptCharacter(const char inputChar, FontHandle font) const -> const + char * override { +#if CONFIG_TINYFONT_IBMF + return reinterpret_cast(font.ptr)->getSubscriptCharacter(inputChar); +#elif CONFIG_TINYFONT_TTF + return nullptr; +#else +#error "No font system selected" +#endif + }; + + inline void setSupSubFontSize(FontHandle font) override { +#if CONFIG_TINYFONT_TTF + reinterpret_cast(font.ptr)->setSupSubFontSize(); +#endif + } + + inline void setNormalFontSize(FontHandle font) override { +#if CONFIG_TINYFONT_TTF + reinterpret_cast(font.ptr)->setNormalFontSize(); +#endif + } + + inline auto drawSingleLineOfText(const char *cstr, int16_t x, int16_t y, bool invert, + FontHandle font) -> int16_t override { + if (!font.ptr || !cstr) { + return x; + } + return static_cast(this->drawSingleLineOfText(std::string(cstr), x, y, invert, + reinterpret_cast(font.ptr))); + } // Draw a bitmap scaled by a multiplier void drawScaledXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, diff --git a/src/EPub/EPubDefs.hpp b/src/EPub/EPubDefs.hpp deleted file mode 100644 index a682d70ef..000000000 --- a/src/EPub/EPubDefs.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -#include "sindarin-debug.h" - -using SpineIdx = int; -using ChapterIdx = int; - -#define EPUB_PROFILING 0 - -#if EPUB_PROFILING -#define EPUB_PROFILE_START(v) PROFILE_START(v) -#define EPUB_PROFILE_END(v) PROFILE_END_LOG_MS(v) -#else -#define EPUB_PROFILE_START(v) -#define EPUB_PROFILE_END(v) -#endif diff --git a/src/EPub/EPubFile.cpp b/src/EPub/EPubFile.cpp deleted file mode 100644 index 87ecaba1a..000000000 --- a/src/EPub/EPubFile.cpp +++ /dev/null @@ -1,574 +0,0 @@ -#include "EPubFile.hpp" - -#include - -#include "EPubDefs.hpp" -#include "EPubMetaFile.hpp" - -auto EPubFile::findFileOffsetAtCharOffset(const std::string &path, uint32_t charOffset) - -> uint32_t { - - pugi::xml_document &doc = getXHTMLFile(path); - - struct Walker : pugi::xml_tree_walker { - auto for_each(pugi::xml_node &node) -> bool override { - if (node.type() == pugi::xml_node_type::node_pcdata) { - uint32_t length = strlen(node.value()); - if ((chOffset + length) > charOffset) { - offset = epub->getOffset(node.value()) + (charOffset - chOffset); - return false; - } - chOffset += length; - } - return true; - } - - uint32_t offset{}; - uint32_t chOffset{}; - uint32_t charOffset{}; - EPubFile *epub{}; - } walker; - - walker.offset = walker.chOffset = 0; - walker.charOffset = charOffset; - walker.epub = this; - - doc.child("html").child("body").traverse(walker); - // if (readable_->isArticle()) log_w("article"); - - return walker.offset; -} - -auto EPubFile::findCharOffsetAtFileOffset(const std::string_view &path, uint32_t fileOffset) - -> uint32_t { - - pugi::xml_document &doc = getXHTMLFile(path); - - struct Walker : pugi::xml_tree_walker { - auto for_each(pugi::xml_node &node) -> bool override { - if (node.type() == pugi::xml_node_type::node_pcdata) { - uint32_t length = strlen(node.value()); - uint32_t nodeOffset = node.offset_debug(); - if ((nodeOffset + length) >= fileOffset) { - uint32_t offset = (fileOffset > nodeOffset) ? fileOffset - nodeOffset : 0; - charOffset += offset; - return false; - } - charOffset += length; - } - return true; - } - - uint32_t charOffset{}; - uint32_t fileOffset{}; - } walker; - - walker.charOffset = 0; - walker.fileOffset = fileOffset; - - doc.child("html").child("body").traverse(walker); - // if (readable_->isArticle()) log_w("article"); - - return walker.charOffset; -} - -// Returns a pointer to the buffer containing the extracted file from the EPub and it's length in a -// tuple -auto EPubFile::getFile(const std::string &completeFilePath) - -> std::pair, uint32_t> { - - EPUB_PROFILE_START(UnzipperOpenFile); - auto res = epubUnzipper_.openFile(completeFilePath); - EPUB_PROFILE_END(UnzipperOpenFile); - - if (!res) { - log_e("Unzipper Failed to open file %s", completeFilePath.c_str()); - } else { - EPUB_PROFILE_START(UnzipperGetFileSize); - auto size = epubUnzipper_.getFileSize(); - EPUB_PROFILE_END(UnzipperGetFileSize); - - if (size > 0) { - uint32_t bufferSize = size + 1; - auto buffer = SpiramArrayPtr(bufferSize); - buffer[size] = 0; // Pugixml requires null termination - - if (buffer != nullptr) { - EPUB_PROFILE_START(UnzipperReadFromFile); - uint32_t length = epubUnzipper_.readFile(buffer.get(), size); - epubUnzipper_.closeFile(); - EPUB_PROFILE_END(UnzipperReadFromFile); - if (length == size) { - // if (readable_->isArticle()) log_w("article"); - - return {std::move(buffer), bufferSize}; - } else { - log_w("Failed to read file %s, read %" PRIu32 " bytes, expected %" PRIu32, - completeFilePath.c_str(), length, size); - } - } else { - epubUnzipper_.closeFile(); - log_e("Unable to allocate space for file %s, space required: %" PRIu32, - completeFilePath.c_str(), static_cast(size)); - } - } - } - - // if (readable_->isArticle()) log_w("article"); - - return {nullptr, 0}; -} - -auto EPubFile::getXHTMLFile(const std::string_view &path) -> pugi::xml_document & { - std::string filePath = opf_->getFullPath(path); - // log_w("Loading file %s", filePath.c_str()); - uint32_t length; - - if (currentFilePath_ != filePath) { - - // log_w("currentFilePath_: %s, filePath : %s", currentFilePath_.c_str(), filePath.c_str()); - - std::tie(currentFileBuffer_, length) = getFile(filePath); - - if (currentFileBuffer_ != nullptr) { - if (solDrmKey_.has_value()) { - std::tie(currentFileBuffer_, length) = solDrm_.decryptInPlaceIfNecessary( - solDrmKey_.value(), currentFileBuffer_, length); - } - // Using load_buffer_inplace() is mandatory here as it permits the - // computation of a node offset location, as string elements are taken - // directly in the currentFileBuffer instead of being copied. - // (see the EPubFile::getOffset() method). - - EPUB_PROFILE_START(ParseXHTML); - pugi::xml_parse_result result = currentFileDoc_.load_buffer_inplace( - currentFileBuffer_.get(), length, - (pugi::parse_default | pugi::parse_ws_pcdata) & ~pugi::parse_escapes); - EPUB_PROFILE_END(ParseXHTML); - - if (result) { - currentFilePath_ = filePath; - } else { - log_e("Failed to parse XHTML file %s: %s", filePath.c_str(), result.description()); - } - } else { - log_e("Unable to unzip file %s", filePath.c_str()); - } - } - - // if (readable_->isArticle()) log_w("article"); - - return currentFileDoc_; -} - -auto EPubFile::getUncompressedSize(const std::string &href) -> uint32_t { - std::string filePath = opf_->getFullPath(href); - // log_w("Loading file %s", filePath.c_str()); - if (epubUnzipper_.openFile(filePath)) { - uint32_t size = epubUnzipper_.getFileSize(); - epubUnzipper_.closeFile(); - return size; - } - return 0; -} - -auto EPubFile::getUncompressedSize(SpineIdx spineIdx) -> uint32_t { - return getUncompressedSize(static_cast(opf_->getSpine(spineIdx).item->href)); -} - -auto EPubFile::getChapterIdx(const std::shared_ptr &ePubPlace) const -> ChapterIdx { - return opf_->getChapterIdx(ePubPlace->getHRef()); -} - -auto EPubFile::getChapterIdx(const std::string_view &href) const -> ChapterIdx { - return opf_->getChapterIdx(href); -} - -auto EPubFile::getChapterIdx(SpineIdx spineIdx) const -> ChapterIdx { - return opf_->getChapterIdx(spineIdx); -} - -auto EPubFile::spineIdxIsChapterStart(SpineIdx spineIdx) const -> bool { - return opf_->spineIdxIsChapterStart(spineIdx); -} - -auto EPubFile::getChapterFirstSpineIdx(SpineIdx spineIdx) const -> SpineIdx { - return opf_->getChapterFirstSpineIdx(spineIdx); -} - -auto EPubFile::getOffsetPreviousSpineItemsInChapter(SpineIdx spineIdx) -> uint32_t { - auto previousSpineOffset = 0; - auto chapterStartSpineIdx = getChapterFirstSpineIdx(spineIdx); - for (auto i = chapterStartSpineIdx; i < spineIdx; i++) { - previousSpineOffset += getUncompressedSize(i); - } - return previousSpineOffset; -} - -auto EPubFile::hasToc() -> bool { - std::string tocId = "nav"; - return opf_->exists(tocId); -} - -auto EPubFile::getToc() -> ReadableTableOfContentsChapterVector { - if (toc_) { - return *toc_; - } - - std::string tocId = "nav"; - auto tocPath = opf_->getHrefByManifestId(tocId); - pugi::xml_document &tocDoc = getXHTMLFile(tocPath); - - ReadableTableOfContentsChapterVector toc; - uint32_t cumulativeOffset = 0; - - std::function processNavPoints; - processNavPoints = [&](pugi::xml_node navPoint, ReadableTableOfContentsChapterVector &toc) { - for (auto navPointChild : navPoint.children("li")) { - ReadableTableOfContentsChapter chapter; - chapter.title = navPointChild.child("a").child_value(); - chapter.href = navPointChild.child("a").attribute("href").value(); - pugi::xml_attribute epubOffsetAttr = - navPointChild.child("a").attribute("data-epub-offset"); - if (epubOffsetAttr) { - chapter.epubOffset = - static_cast(strtoul(epubOffsetAttr.value(), nullptr, 10)); - } else { - chapter.epubOffset = cumulativeOffset; - } - pugi::xml_attribute endEpubOffsetAttr = - navPointChild.child("a").attribute("data-end-epub-offset"); - if (endEpubOffsetAttr) { - chapter.endEpubOffset = - static_cast(strtoul(endEpubOffsetAttr.value(), nullptr, 10)); - } else { - auto fileSize = getUncompressedSize(static_cast(chapter.href)); - cumulativeOffset += fileSize; - chapter.endEpubOffset = cumulativeOffset; - } - - // log_w("Chapter %s: %s (%" PRIu32 " - %" PRIu32 ")", chapter.title.c_str(), - // chapter.href.c_str(), chapter.epubOffset, chapter.endEpubOffset); - toc.push_back(chapter); - - // Recurse into child
    elements, if any - for (auto nestedList : navPointChild.children("ol")) { - processNavPoints(nestedList, toc); - } - } - }; - - // Process the top-level
      elements in the TOC - for (auto navPoint : tocDoc.child("html").child("body").child("nav").children("ol")) { - processNavPoints(navPoint, toc); - } - - toc_ = std::make_shared(toc); - return toc; -} - -auto EPubFile::hasSummary(const std::string_view &spineHref) -> bool { - return hasEpubAddition(spineHref, "summary"); -} - -auto EPubFile::getSummaryContent( - const std::shared_ptr &placeInReadable, std::shared_ptr &placeInSummary, - const std::shared_ptr &partialChapterSummary, - bool includeUnread) -> SpiramString { - - auto placeHref = placeInReadable->getHRef(); - if (!hasSummary(placeHref) && partialChapterSummary == nullptr) { - log_e("This chapter does not have a summary."); - return "This chapter does not have a summary."; - } - - // Load the summary xhtml content - auto chapterHref = opf_->getChapterFirstHref(placeHref); - auto chapterIdx = opf_->getChapterIdx(chapterHref); - if (chapterIdx == -1) { - log_e("Unable to find chapter index for %s", chapterHref.c_str()); - return "This chapter does not have a summary."; - } - log_w("Chapter href: %s, chapter idx: %d, toc href: %s", chapterHref.c_str(), chapterIdx, - std::string(getToc()[chapterIdx].href).c_str()); - if (std::string(getToc()[chapterIdx].href) != chapterHref) { - chapterHref = std::string(getToc()[chapterIdx].href); - } - - std::string partialSummaryChapterHRef; - if (partialChapterSummary != nullptr && !partialChapterSummary->text.empty() && - partialChapterSummary->text != "null") { - auto summarySpineHref = partialChapterSummary->ePubPlace->getHRef(); - partialSummaryChapterHRef = opf_->getChapterFirstHref(summarySpineHref); - } - - log_d("Loading summary for chapter href: %s, chapter idx: %d", chapterHref.c_str(), chapterIdx); - SpiramString summaryText; - bool foundAnySummaries = false; - for (const auto &chapter : getToc()) { - auto sameChapter = chapter.href == chapterHref; - auto tocChapterIdx = opf_->getChapterIdx(std::string(chapter.href)); - if (tocChapterIdx == -1) { - log_e("Unable to find chapter index for %s", chapter.href.c_str()); - continue; - } - auto futureChapter = tocChapterIdx > chapterIdx; - auto pastOrSameChapter = tocChapterIdx <= chapterIdx; - - if (pastOrSameChapter) { - placeInSummary->setOffset(summaryText.length()); - } - - if (futureChapter && !includeUnread) { - break; - } - - if (sameChapter && !includeUnread && partialSummaryChapterHRef == chapter.href) { - summaryText += "Summary: " + chapter.title; - summaryText += StringFormat(" (%d%% read)\n\n", partialChapterSummary->readPct); - summaryText += partialChapterSummary->text; - summaryText += "\n\n"; - foundAnySummaries = true; - } else { - std::string currentChapterId = - opf_->getManifestIdByHref(static_cast(chapter.href)); - auto currentSummaryPath = opf_->getHrefByManifestId("summary-" + currentChapterId); - if (currentSummaryPath.empty()) { - log_w("No summary for %s", chapter.href.c_str()); - continue; - } - foundAnySummaries = true; - pugi::xml_document ¤tDoc = getXHTMLFile(currentSummaryPath); - - for (auto node : currentDoc.child("html").child("body").children()) { - // If the node is p or other nodes with child nodes - if (std::string(node.name()) == "p") { - for (auto child : node.children()) { - if (child.type() == pugi::node_pcdata) { // If child is plain character data - summaryText += child.value(); - } - if (std::string(child.name()) == "br") { - summaryText += "\n"; - } - } - summaryText += "\n\n"; - } else if (std::string(node.name()) == "h1") { - summaryText += node.text().get(); - summaryText += "\n\n"; - } else if (std::string(node.name()) == "br") { - summaryText += "\n"; - } else { - summaryText += node.text().get(); - } - } - } - } - - if (!foundAnySummaries) { - return "This chapter does not have a summary."; - } - - return summaryText; -} - -auto EPubFile::hasEpubAddition(const std::string_view &spineHref, const std::string &additionType) - -> bool { - // Find the chapterId corresponding to this spineHref - auto chapterSpineHref = opf_->getChapterFirstHref(spineHref); - auto chapterId = opf_->getManifestIdByHref(chapterSpineHref); - - std::string additionId = additionType + "-" + chapterId; - - auto hasDirectChapterAddition = opf_->exists(additionId); - auto hasToc = opf_->exists("nav"); - if (hasDirectChapterAddition && hasToc) { - return true; - } - - return false; -} - -auto EPubFile::hasDictionary(const std::string_view &spineHref) -> bool { - return hasEpubAddition(spineHref, "dictionary"); -} - -auto EPubFile::getDictionaryContent(const std::shared_ptr &placeInReadable, - std::shared_ptr &placeInDictionary) -> SpiramString { - - auto spineHref = placeInReadable->getHRef(); - if (!hasDictionary(spineHref)) { - log_e("This chapter does not have any dictionary definitions."); - return "This chapter does not have any dictionary definitions."; - } - - // Load the dictionary xhtml content - int fileOffset = placeInReadable->getOffset(); - auto chapterIdx = opf_->getChapterIdx(spineHref); - auto spineIdx = opf_->getSpineIdx(spineHref); - auto spineId = opf_->getManifestIdByHref(spineHref); - - if (chapterIdx == -1 || spineIdx == -1) { - log_e("Unable to find chapter index for %s", spineHref.c_str()); - return "This chapter does not have any dictionary definitions."; - } - - auto previousSpineOffset = 0; - if (getToc()[chapterIdx].href != spineHref) { - spineHref = getToc()[chapterIdx].href; - spineId = opf_->getManifestIdByHref(spineHref); - // If we're looking at a spine item that doesn't match the chapter, we need to calculate the - // offset to match how the server is calculating word locations, which is cumulatively - // adding the size of each spine item - previousSpineOffset = getOffsetPreviousSpineItemsInChapter(spineIdx); - log_d("Calculated previousSpineOffset: %d", previousSpineOffset); - } - - // Convert chapterId to dictionary file path - std::string dictionaryId = "dictionary-" + spineId; - auto dictionaryPath = opf_->getHrefByManifestId(dictionaryId); - // int currentOffset = findCharOffsetAtFileOffset(spineHref, fileOffset); - int currentOffset = fileOffset + previousSpineOffset; - pugi::xml_document &doc = getXHTMLFile(dictionaryPath); - pugi::xml_node closestNode; - pugi::xml_node prevNode; - int closestLocation = -1; - - log_d("Loading dictionary for chapter: %s. (href: %s, chapter idx: %d) Current offset: %d " - "(file:%d+prev:%d)", - spineId.c_str(), spineHref.c_str(), chapterIdx, currentOffset, fileOffset, - previousSpineOffset); - // Search for the closest data-locations attribute closest to currentOffset without going over - for (auto p : doc.child("html").child("body").children("p")) { - int location = p.attribute("data-locations").as_int(); - log_d("Found dictionary location: %d", location); - - // If currentOffset is 0, set the closestNode as the first node - if (currentOffset == 0 || closestNode == nullptr) { - closestNode = p; - closestLocation = location; - } - - if (location <= currentOffset) { - // Still finding nodes behind the user's placemark - closestLocation = location; - prevNode = closestNode; - closestNode = p; - } else { - // If we've gone over and the location is greater than the offset, we're good with - // what's left. Clearing prevNode means we haven't exhausted all definitions and will - // still have some to show. - prevNode = pugi::xml_node(); - break; - } - } - - // If closestNode refers to the last entry and there was no break, backtrack one entry so we - // always show one dictionary definition - if (prevNode) { - closestNode = prevNode; - closestLocation = closestNode.attribute("data-locations").as_int(); - } - - SpiramString dictionaryText; - SpiramString beforeClosestNodeText; - if (closestLocation != -1) { - // Construct the text with the XML before the closest node - for (auto p : doc.child("html").child("body").children("p")) { - if (p == closestNode) { - if (closestLocation < currentOffset) { - beforeClosestNodeText += StringFormat("%s\n\n", p.child_value()); - } else { - dictionaryText += StringFormat("%s\n\n", p.child_value()); - } - break; - } - beforeClosestNodeText += StringFormat("%s\n\n", p.child_value()); - } - - // Construct the text with the XML after the closest node - for (auto p = closestNode.next_sibling(); p; p = p.next_sibling()) { - dictionaryText += StringFormat("%s\n\n", p.child_value()); - } - - // Set the place in the dictionary based on the length of the text before the closest node - uint32_t charOffset = beforeClosestNodeText.length(); - placeInDictionary = std::dynamic_pointer_cast(Place::factory(charOffset)); - - // Concatenate to form the full dictionaryText - dictionaryText = beforeClosestNodeText + dictionaryText; - - log_i("Found closest node at char offset: %" PRIu32 " for location: %d/%d", charOffset, - closestLocation, currentOffset); - } else { - log_e("Location not found in dictionaryString chapter: %s : %d", spineId.c_str(), - currentOffset); - } - - return dictionaryText; -} - -auto EPubFile::open() -> bool { - - PROFILE_START(openEPub); - - EPUB_PROFILE_START(UnzipperOpen); - if (!epubUnzipper_.open()) { - log_e("Failed to open ePub file"); - return false; - } - EPUB_PROFILE_END(UnzipperOpen); - - EPUB_PROFILE_START(EPubManifest); - EPubMetaFile manifest("META-INF/container.xml"); - auto [manifestBuffer, manifestLength] = getFile("META-INF/container.xml"); - if (manifestBuffer == nullptr) { - log_e("Failed to open ePub's manifest %s", "META-INF/container.xml"); - return false; - } else { - manifest.parse(manifestBuffer, manifestLength); - } - EPUB_PROFILE_END(EPubManifest); - - // for (auto &path : manifest.rootfilePaths) { - // log_i("Rootfile path: %s", path.c_str()); - // } - - if (manifest.rootfilePaths.empty()) { - log_e("No rootfile paths found"); - return false; - } else if (manifest.rootfilePaths.size() > 1) { - log_w("Multiple rootfile paths found"); - } - - EPUB_PROFILE_START(EPubOpf) - auto [opfBuffer, length] = getFile(manifest.rootfilePaths[0]); - if (opfBuffer == nullptr) { - log_e("Failed to open ePub's opf %s", manifest.rootfilePaths[0].c_str()); - return false; - } else { - opf_ = std::make_shared(manifest.rootfilePaths[0].c_str()); - opf_->parse(opfBuffer, length, readable_); - auto toc = getToc(); - opf_->loadSpine(toc); - } - EPUB_PROFILE_END(EPubOpf) - - EPUB_PROFILE_START(EPubParseKey) - // Load the DRM encryption key if it exists - auto [solDrmKeyBuffer, solDrmKeyLength] = getFile("sol_drm.key"); - if (solDrmKeyBuffer != nullptr) { - solDrmKey_ = solDrm_.parseKeyfile(solDrmKeyBuffer.get(), solDrmKeyLength); - } - EPUB_PROFILE_END(EPubParseKey) - - PROFILE_END_LOG_MS(openEPub); - - // log_d("Total size of all ePub text is %d", opf_->getTotalSize()); - fileOpen_ = true; - return true; -} - -void EPubFile::close() { - epubUnzipper_.close(); - fileOpen_ = false; -} diff --git a/src/EPub/EPubFile.hpp b/src/EPub/EPubFile.hpp deleted file mode 100644 index 153273037..000000000 --- a/src/EPub/EPubFile.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "EPubDefs.hpp" -#include "EPubOpf.hpp" -#include "Misc/Unzipper.hpp" -#include "Models/DocType.hpp" -#include "Models/Readable.hpp" -#include "Models/UserReadablePartialChapterSummary.hpp" -#include "Renderers/RendererStream.hpp" -#include "Storage/SolDrm.hpp" - -class EPubFile : public RendererStream { -public: - EPubFile(const SFile &file, std::shared_ptr readable, SolDrm &solDrm) - : RendererStream(DocType::EPUB), epubUnzipper_(file), readable_(std::move(readable)), - currentFileBuffer_(nullptr), solDrm_(solDrm) { - open(); - } - - ~EPubFile() override { close(); }; - - [[nodiscard]] auto read() -> int override { return 0; } - [[nodiscard]] auto seek(uint32_t pos) -> bool override { return false; } - [[nodiscard]] auto position() -> size_t override { return 0; } - [[nodiscard]] auto size() -> size_t override { return 0; /* opf_->getTotalSize(); */ } - - [[nodiscard]] auto isAnArticle() const -> bool { return readable_->isArticle(); } - - // [[nodiscard]] inline auto getSpineIdxForOffset(uint32_t offset) const -> SpineIdx { - // return opf_->getSpineIdxForOffset(offset); - // } - - [[nodiscard]] inline auto getSpine(SpineIdx idx) const -> const EPubOpf::SpineItem & { - return opf_->getSpine(idx); - } - - [[nodiscard]] auto findFileOffsetAtCharOffset(const std::string &path, uint32_t charOffset) - -> uint32_t; - - [[nodiscard]] auto findCharOffsetAtFileOffset(const std::string_view &path, uint32_t fileOffset) - -> uint32_t; - - [[nodiscard]] inline auto getSpineCount() const -> uint16_t { return opf_->getSpineCount(); } - - [[nodiscard]] auto getFile(const std::string &path) - -> std::pair, uint32_t>; - - [[nodiscard]] inline auto getSpineIdx(const std::string_view &href) const -> SpineIdx { - return opf_->getSpineIdx(href); - } - - [[nodiscard]] inline auto getHrefByManifestId(const std::string &manifestId) const - -> std::string_view { - return opf_->getHrefByManifestId(manifestId); - } - - [[nodiscard]] inline auto getManifestIdByHref(const std::string &href) const -> std::string { - return opf_->getManifestIdByHref(href); - } - - [[nodiscard]] auto getXHTMLFile(const std::string_view &path) -> pugi::xml_document &; - - [[nodiscard]] inline auto getOffset(const pugi::char_t *item) const -> uint32_t { - return item - reinterpret_cast(currentFileBuffer_.get()); - } - - [[nodiscard]] inline auto getFullPath(const std::string &filename) const -> std::string { - return opf_->getFullPath(filename); - } - - [[nodiscard]] inline auto isOpen() const -> bool { return fileOpen_; } - - [[nodiscard]] auto getRelativeFilePath(const std::string &filename, SpineIdx spineIdx) const - -> std::string { - std::string mainFilePath = - opf_->getFullPath(static_cast(opf_->getSpine(spineIdx).item->href)); - std::string path; - opf_->extractPath(mainFilePath.c_str(), path); - return path + filename; - } - - auto getUncompressedSize(const std::string &href) -> uint32_t; - auto getUncompressedSize(SpineIdx spineIdx) -> uint32_t; - - auto getChapterIdx(const std::shared_ptr &ePubPlace) const -> ChapterIdx; - auto getChapterIdx(const std::string_view &href) const -> ChapterIdx; - auto getChapterIdx(SpineIdx spineIdx) const -> ChapterIdx; - auto spineIdxIsChapterStart(SpineIdx spineIdx) const -> bool; - auto getChapterFirstSpineIdx(SpineIdx spineIdx) const -> SpineIdx; - auto getOffsetPreviousSpineItemsInChapter(SpineIdx spineIdx) -> uint32_t; - - auto hasToc() -> bool; - auto getToc() -> ReadableTableOfContentsChapterVector; - - auto hasEpubAddition(const std::string_view &spineHref, const std::string &additionType) - -> bool; - auto hasSummary(const std::string_view &spineHref) -> bool; - auto getSummaryContent( - const std::shared_ptr &placeInReadable, - std::shared_ptr &placeInSummary, - const std::shared_ptr &partialChapterSummary, - bool includeUnread) -> SpiramString; - auto hasDictionary(const std::string_view &spineHref) -> bool; - auto getDictionaryContent(const std::shared_ptr &placeInReadable, - std::shared_ptr &placeInDictionary) -> SpiramString; - - void close(); - -private: - Unzipper epubUnzipper_; - const std::shared_ptr readable_; - std::shared_ptr toc_; - - pugi::xml_document currentFileDoc_; - - bool fileOpen_{false}; - - std::shared_ptr opf_{nullptr}; - - // std::unique_ptr currentFileBuffer_; - std::shared_ptr currentFileBuffer_; - std::string currentFilePath_; - SolDrm &solDrm_; - std::optional> solDrmKey_; - - auto open() -> bool; -}; diff --git a/src/EPub/EPubMetaFile.cpp b/src/EPub/EPubMetaFile.cpp deleted file mode 100644 index 82ed08a75..000000000 --- a/src/EPub/EPubMetaFile.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "EPubMetaFile.hpp" - -#include - -void EPubMetaFile::parse(const std::shared_ptr &buffer, uint32_t length) { - - pugi::xml_document doc; - - EPUB_PROFILE_START(XMLParseManifest); - pugi::xml_parse_result result = doc.load_buffer_inplace(buffer.get(), length); - EPUB_PROFILE_END(XMLParseManifest); - - if (result) { - for (pugi::xml_node &rootfile : - doc.child("container").child("rootfiles").children("rootfile")) { - rootfilePaths.emplace_back(rootfile.attribute("full-path").value()); - // log_w("-----------> adding %s", rootfile.attribute("full-path").value()); - } - } else { - log_e("Failed to parse EPub manifest %s: %s", path_, result.description()); - } -} diff --git a/src/EPub/EPubMetaFile.hpp b/src/EPub/EPubMetaFile.hpp deleted file mode 100644 index 8ed7e87f4..000000000 --- a/src/EPub/EPubMetaFile.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "EPubDefs.hpp" - -class EPubMetaFile { -public: - EPubMetaFile(const char *path) : path_(path) {} - - void parse(const std::shared_ptr &buffer, uint32_t length); - - std::vector rootfilePaths; - -private: - const char *path_; -}; diff --git a/src/EPub/EPubOpf.cpp b/src/EPub/EPubOpf.cpp deleted file mode 100644 index f202454dc..000000000 --- a/src/EPub/EPubOpf.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "EPubOpf.hpp" - -#include -#include -#include - -#include "EPubDefs.hpp" - -void EPubOpf::parse(const std::shared_ptr &buffer, uint32_t length, - const std::shared_ptr &readable) { - - basePath_ = ""; - extractPath(path_, basePath_); - // log_i("Base Path: %s", basePath_.c_str()); - - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_buffer_inplace(buffer.get(), length); - - if (result) { - - pugi::xml_node package = doc.child("package"); - pugi::xml_node manifest = package.child("manifest"); - pugi::xml_node metadata = package.child("metadata"); - pugi::xml_node spine = package.child("spine"); - - title_ = metadata.child("dc:title").value(); - creator_ = metadata.child("dc:creator").value(); - - for (auto item : manifest.children("item")) { - manifest_[item.attribute("id").value()] = - ManifestItem{.id = item.attribute("id").value(), - .href = item.attribute("href").value(), - .mediaType = item.attribute("media-type").value()}; - } - - for (auto itemref : spine.children("itemref")) { - std::string idref = itemref.attribute("idref").value(); - - auto it = manifest_.find(static_cast(idref)); - if (it != manifest_.end()) { - spine_.push_back(SpineItem{.item = &it->second}); - } else { - log_e("Spine idref not found in manifest: %s", idref.c_str()); - } - } - } else { - log_e("Failed to parse EPub opf %s: %s", path_, result.description()); - } -} - -void EPubOpf::loadSpine(const ReadableTableOfContentsChapterVector &toc) { - int lastChapterIdx = -1; - SpineVector spineWithChapters; - - for (auto spineItem : spine_) { - auto chapterIdx = findChapterIdxInToc(toc, spineItem); - bool chapterStart = false; - if (chapterIdx != -1) { - lastChapterIdx = chapterIdx; - chapterStart = true; - } - // log_w("Spine item %s chapter %d chapterStart %d", spineItem.item->href.c_str(), - // chapterIdx, - // chapterStart); - spineWithChapters.push_back(SpineItem{ - .item = spineItem.item, .chapterIdx = lastChapterIdx, .chapterStart = chapterStart}); - } - - spine_ = spineWithChapters; -} - -auto EPubOpf::findChapterIdxInToc(const ReadableTableOfContentsChapterVector &toc, - SpineItem &spineItem) -> ChapterIdx { - ChapterIdx idx = 0; - // Walk the spine and compare the href to the toc. If the href isn't found in the toc, walk the - // spine backwards until the href is found - for (auto &chapter : toc) { - // log_w("%d: Comparing %s to %s", idx, chapter.href.c_str(), spineItem.item->href.c_str()); - if (chapter.href == spineItem.item->href) { - return idx; - } - idx++; - } - return -1; -} - -void EPubOpf::extractPath(const char *fname, std::string &path) { - path.clear(); - int i = strlen(fname) - 1; - while ((i > 0) && (fname[i] != '/')) { - i--; - } - if (i > 0) { - path.assign(fname, ++i); - } -} - -auto EPubOpf::getSpineIdx(const std::string_view &href) const -> SpineIdx { - for (SpineIdx spineIdx = 0; - spineIdx < - std::min(spine_.size(), static_cast(std::numeric_limits::max())); - spineIdx++) { - if (spine_[spineIdx].item->href == href) { - return spineIdx; - } - } - return -1; -} - -auto EPubOpf::getChapterIdx(const std::string_view &href) const -> ChapterIdx { - SpineIdx spineIdx = getSpineIdx(href); - if (spineIdx == -1) { - return -1; - } - return spine_[spineIdx].chapterIdx; -} - -auto EPubOpf::getChapterIdx(SpineIdx spineIdx) const -> ChapterIdx { - return spine_[spineIdx].chapterIdx; -} - -auto EPubOpf::spineIdxIsChapterStart(SpineIdx spineIdx) const -> bool { - return spine_[spineIdx].chapterStart; -} - -auto EPubOpf::getChapterFirstSpineIdx(SpineIdx spineIdx) const -> SpineIdx { - SpineIdx chapterSpineIdx = spineIdx; - while (!spine_[chapterSpineIdx].chapterStart && (chapterSpineIdx > 0)) { - chapterSpineIdx--; - } - - return chapterSpineIdx; -} - -auto EPubOpf::getChapterFirstHref(const std::string_view &spineHRef) const -> std::string { - auto spineIdx = getSpineIdx(spineHRef); - auto chapterSpineIdx = getChapterFirstSpineIdx(spineIdx); - return static_cast(getSpine(chapterSpineIdx).item->href); -} - -auto EPubOpf::exists(const std::string &id) const -> bool { - log_d("Checking if %s exists in manifest: %d", id.c_str(), - manifest_.find(static_cast(id)) != manifest_.end()); - return manifest_.find(static_cast(id)) != manifest_.end(); -} - -auto EPubOpf::getHrefByManifestId(const std::string &manifestId) const -> std::string_view { - auto it = manifest_.find(static_cast(manifestId)); - if (it != manifest_.end()) { - return it->second.href; - } - return ""; -} - -auto EPubOpf::getManifestIdByHref(const std::string_view &href) const -> std::string { - for (auto &item : manifest_) { - if (item.second.href == href) { - return static_cast(item.first); - } - } - return ""; -} diff --git a/src/EPub/EPubOpf.hpp b/src/EPub/EPubOpf.hpp deleted file mode 100644 index 8bd740e31..000000000 --- a/src/EPub/EPubOpf.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "EPubDefs.hpp" -#include "Models/Readable.hpp" - -class EPubOpf { -public: - EPubOpf(const char *path) : path_(path) {} - - struct ManifestItem { - SpiramString id; - SpiramString href; - SpiramString mediaType; - }; - struct SpineItem { - ManifestItem *item; - int chapterIdx; - bool chapterStart; - }; - - void parse(const std::shared_ptr &buffer, uint32_t length, - const std::shared_ptr &readable); - void loadSpine(const ReadableTableOfContentsChapterVector &toc); - auto findChapterIdxInToc(const ReadableTableOfContentsChapterVector &toc, SpineItem &spineItem) - -> ChapterIdx; - - [[nodiscard]] auto getSpine(SpineIdx idx) const -> const SpineItem & { return spine_[idx]; } - - [[nodiscard]] auto getFullPath(const std::string_view &fileName) const -> std::string { - return StringFormat("%s%.*s", basePath_.c_str(), fileName.size(), fileName.data()); - } - - [[nodiscard]] inline auto getSpineCount() const -> uint16_t { return spine_.size(); } - - void extractPath(const char *fname, std::string &path); - - auto getSpineIdx(const std::string_view &href) const -> SpineIdx; - - auto getChapterIdx(const std::string_view &href) const -> ChapterIdx; - auto getChapterIdx(SpineIdx spineIdx) const -> ChapterIdx; - auto spineIdxIsChapterStart(SpineIdx spineIdx) const -> bool; - auto getChapterFirstSpineIdx(SpineIdx spineIdx) const -> SpineIdx; - auto getChapterFirstHref(const std::string_view &spineHRef) const -> std::string; - - auto exists(const std::string &id) const -> bool; - - // TODO: It'd probably be good practice to use a type alias for manifest IDs - auto getHrefByManifestId(const std::string &manifestId) const -> std::string_view; - auto getManifestIdByHref(const std::string_view &href) const -> std::string; - -private: - const char *path_; - - std::string basePath_; - - using ManifestMap = SpiramMap; - using ManifestIterator = SpiramMap::iterator; - using SpineVector = SpiramVector; - - std::string title_; - std::string creator_; - ManifestMap manifest_; - SpineVector spine_; -}; diff --git a/src/Misc/BMPImage.cpp b/src/Misc/BMPImage.cpp deleted file mode 100644 index a662299b3..000000000 --- a/src/Misc/BMPImage.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "BMPImage.hpp" - -#include "Misc/Ditherer.hpp" -#include "Renderers/RendererStream.hpp" -#include "bmp.h" - -std::shared_ptr bmpConversionLUT = nullptr; - -void BMPImage::createLUT() { - if (bmpConversionLUT) { - return; - } - - bmpConversionLUT = SpiramArrayPtr(256); - - // store bit-reversed and value flipped version of each byte - // converts from bmp 1 bit to xbm format - for (int i = 0; i < 256; i++) { - uint8_t val = i; - uint8_t out = 0; - for (int b = 0; b < 8; b++) { - out <<= 1; - out |= val & 0b1; - val >>= 1; - } - - out ^= 0xFF; - bmpConversionLUT[i] = out; - } -} - -auto BMPImage::readFromBytes(uint8_t *bmpbytes, int size) -> bool { - BufferStream stream(bmpbytes, size); - return readFromStream(stream); -} - -auto BMPImage::readFromStream(RendererStream &stream) -> bool { - bmp_image *img = BmpRead(stream); - if (!img) { - log_e("error reading bmp image"); - return false; - } - - width_ = img->dib.bmiHeader.biWidth; - height_ = img->dib.bmiHeader.biHeight; - - int datasize = img->dib.bmiHeader.biSizeImage; - int bytesPerLine = datasize / height_; - - int bitcount = img->dib.bmiHeader.biBitCount; - if (bitcount != 8 && bitcount != 1) { - log_e("%d bit bmp not supported", bitcount); - return false; - } - - bitsPerPixel_ = bitcount; - int outputBytesPerLine = (width_ + 7) / 8; - int outputTotalBytes = outputBytesPerLine * height_; - - // allocate output buffer - outputBuffer_ = SpiramArrayPtr(outputTotalBytes); - - // if the bmp already has 1 bit per pixel, it can be displayed directly - // convert to xbm format - if (bitsPerPixel_ == 1) { - // BMP stores lines of pixels starting at the bottom - // copy starting at the bottom to match current bitmap format - uint8_t *imptr; - uint8_t *buf = outputBuffer_.get(); - - for (int y = height_ - 1; y >= 0; y--) { - imptr = &img->ciPixelArray[y * bytesPerLine]; - // bit convert to match xbm format - for (int x = 0; x < outputBytesPerLine; x++) { - buf[x] = bmpConversionLUT[imptr[x]]; - } - - buf += outputBytesPerLine; - } - } else if (bitsPerPixel_ == 8) { - - // this is an 8 bit grayscale image - // dither the image into the allocated output buffer - Ditherer::getInstance().ditherToBitmap(img->ciPixelArray, outputBuffer_.get(), width_, - height_, bytesPerLine, true); - } - - BmpCleanup(nullptr, img); - return true; -} - -auto BMPImage::getBitsPerPixel() const -> int { return bitsPerPixel_; } - -auto BMPImage::getBuffer() -> uint8_t * { return outputBuffer_.get(); } - -auto BMPImage::getWidth() const -> int { return width_; } - -auto BMPImage::getHeight() const -> int { return height_; } diff --git a/src/Misc/BMPImage.hpp b/src/Misc/BMPImage.hpp deleted file mode 100644 index f25dfe1bb..000000000 --- a/src/Misc/BMPImage.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#include "Renderers/RendererStream.hpp" - -class BMPImage { -public: - BMPImage() : outputBuffer_(nullptr) { createLUT(); } - ~BMPImage() = default; - - auto readFromBytes(uint8_t *bmpbytes, int size) -> bool; - auto readFromStream(RendererStream &stream) -> bool; - [[nodiscard]] auto getWidth() const -> int; - [[nodiscard]] auto getHeight() const -> int; - [[nodiscard]] auto getBitsPerPixel() const -> int; - auto getBuffer() -> uint8_t *; - -private: - std::shared_ptr outputBuffer_; - int bitsPerPixel_{0}; - int width_{0}; - int height_{0}; - - void createLUT(); -}; \ No newline at end of file diff --git a/src/Misc/CryptoMbedTls.cpp b/src/Misc/CryptoMbedTls.cpp index 6b6fc4aca..3cb425083 100644 --- a/src/Misc/CryptoMbedTls.cpp +++ b/src/Misc/CryptoMbedTls.cpp @@ -8,7 +8,7 @@ auto CalculateFileHash(const FileSystem &fileSystem, const char *path) -> std::string { SFile file = fileSystem.open(path); if (!file) { - log_e("Failed to open file: %s is the filename", path); + log_e("Failed to open file: %s", path); return ""; } diff --git a/src/Misc/Ditherer.cpp b/src/Misc/Ditherer.cpp deleted file mode 100644 index 5278722ac..000000000 --- a/src/Misc/Ditherer.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "Ditherer.hpp" - -#include -#include - -#include "Platform.hpp" -#include "sindarin-debug.h" - -Ditherer ditherer; - -auto Ditherer::getInstance() -> Ditherer & { return ditherer; } - -void Ditherer::convertToFloat(uint8_t *image, int width, int height, int bytesPerLine, - bool reversed) { - uint8_t *inpixel = image; - float div = 1.0 / 255.0; - float *outpixel = ditherBuffer_.get(); - - if (reversed) { - for (int h = height - 1; h >= 0; h--) { - inpixel = &image[h * bytesPerLine]; - for (int w = 0; w < width; w++) { - *(outpixel++) = static_cast(*(inpixel++)) * div; - } - } - } else { - for (int h = 0; h < height; h++) { - inpixel = &image[h * bytesPerLine]; - for (int w = 0; w < width; w++) { - *(outpixel++) = static_cast(*(inpixel++)) * div; - } - } - } -} - -void Ditherer::ditherToBitmap(uint8_t *image, uint8_t *bitmap, int width, int height, - int bytesPerLine, bool reversed) { - convertToFloat(image, width, height, bytesPerLine, reversed); - ditherFloatToBitmap(bitmap, width, height); -} - -auto Ditherer::pixelAt(int x, int y, int width, int height) -> float * { - if (x >= 0 && x < width && y >= 0 && y < height) { - return &ditherBuffer_.get()[y * width + x]; - } else { - return nullptr; - } -} - -void Ditherer::ditherFloatToBitmap(uint8_t *bitmap, int width, int height) { - float *pixel = ditherBuffer_.get(); - float val; - float div1 = 7.0 / 16.0; - float div2 = 3.0 / 16.0; - float div3 = 5.0 / 16.0; - float div4 = 1.0 / 16.0; - - uint8_t *bmpptr = bitmap; - uint8_t bmpByte = 0; - int bitsInByte = 0; - - for (int h = 0; h < height; h++) { - for (int w = 0; w < width; w++) { - // clip value - if (*pixel < 0.0) { - *pixel = 0.0; - } else if (*pixel > 1.0) { - *pixel = 1.0; - } - - // writing into xbitmap format - // lsb is the leftmost pixel - // TODO: change this if converting to PBM style (msb as left pixel) - bmpByte >>= 1; - bitsInByte++; - if (*pixel >= 0.5) { - val = 1.0; - } else { - val = 0.0; - bmpByte |= 0x80; - } - - float diff = *pixel - val; - - float *pp; - pp = pixelAt(w + 1, h, width, height); - if (pp) { - *pp += diff * div1; - } - pp = pixelAt(w - 1, h + 1, width, height); - if (pp) { - *pp += diff * div2; - } - pp = pixelAt(w, h + 1, width, height); - if (pp) { - *pp += diff * div3; - } - pp = pixelAt(w + 1, h + 1, width, height); - if (pp) { - *pp += diff * div4; - } - - *(pixel++) = val; - if (bitsInByte == 8) { - *(bmpptr++) = bmpByte; - bitsInByte = 0; - } - } - } -} \ No newline at end of file diff --git a/src/Misc/Ditherer.hpp b/src/Misc/Ditherer.hpp deleted file mode 100644 index 03dd9b4b8..000000000 --- a/src/Misc/Ditherer.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -class Ditherer { -public: - static auto getInstance() -> Ditherer &; - - Ditherer() { ditherBuffer_ = SpiramArrayPtr(256 * 256); } - void ditherToBitmap(uint8_t *image, uint8_t *bitmap, int width, int height, int bytesPerLine, - bool reversed = false); - -private: - std::shared_ptr ditherBuffer_; - - auto pixelAt(int x, int y, int width, int height) -> float *; - void ditherFloatToBitmap(uint8_t *bitmap, int width, int height); - void convertToFloat(uint8_t *image, int width, int height, int bytesPerLine, bool reversed); -}; \ No newline at end of file diff --git a/src/Misc/GUnzipStream.hpp b/src/Misc/GUnzipStream.hpp index 948c3668f..70269adca 100644 --- a/src/Misc/GUnzipStream.hpp +++ b/src/Misc/GUnzipStream.hpp @@ -1,10 +1,10 @@ #pragma once +#include +#include #include #include "Misc/GUnzip.hpp" -#include "Models/DocType.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/SFile.hpp" #include "sindarin-debug.h" diff --git a/src/Misc/LookBackStream.hpp b/src/Misc/LookBackStream.hpp index 7b506ce70..ec404fc70 100644 --- a/src/Misc/LookBackStream.hpp +++ b/src/Misc/LookBackStream.hpp @@ -1,7 +1,8 @@ #pragma once +#include +#include + #include "Misc/CircBuffer.hpp" -#include "Models/DocType.hpp" -#include "Renderers/RendererStream.hpp" #define LOOKBACK_BUFFER_SIZE 32768 diff --git a/src/Misc/PBMImage.hpp b/src/Misc/PBMImage.hpp index 40d8c6dbb..b1ffa081f 100644 --- a/src/Misc/PBMImage.hpp +++ b/src/Misc/PBMImage.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Renderers/RendererStream.hpp" +#include auto ParsePbmHeader(RendererStream &stream, int &width, int &height) -> bool; \ No newline at end of file diff --git a/src/Misc/Unzipper.cpp b/src/Misc/Unzipper.cpp deleted file mode 100644 index 4f2c67365..000000000 --- a/src/Misc/Unzipper.cpp +++ /dev/null @@ -1,532 +0,0 @@ -#include "Unzipper.hpp" - -#include -#include -#include -#include -#include - -auto Unzipper::open() -> bool { - if (!file_) { - log_e("Unzipper has no file"); - return false; - } - - isOpen_ = true; - - int err = 99; - -#define ERR(e) \ - { \ - err = e; \ - break; \ - } - - bool completed = false; - while (true) { - // Seek to beginning of central directory - // - // We seek the file back until we reach the "End Of Central Directory" - // signature "PK\5\6". - // - // end of central dir signature 4 bytes (0x06054b50) - // number of this disk 2 bytes 4 - // number of the disk with the - // start of the central directory 2 bytes 6 - // total number of entries in the - // central directory on this disk 2 bytes 8 - // total number of entries in - // the central directory 2 bytes 10 - // size of the central directory 4 bytes 12 - // offset of start of central - // directory with respect to - // the starting disk number 4 bytes 16 - // .ZIP file comment length 2 bytes 20 - // --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE --- - // .ZIP file comment (variable size) - - static constexpr const uint32_t fileCentralSize = 22; - - buffer_[fileCentralSize] = 0; - - if (!file_.seek(0, SSeekEnd)) { - log_e("Unable to seek to end of file."); - err = 0; - break; - } - - off_t length = file_.position(); - if (length < fileCentralSize) ERR(1); - off_t ecdOffset = length - fileCentralSize; - - if (!file_.seek(ecdOffset, SSeekSet)) ERR(2); - if (file_.read(buffer_, fileCentralSize) != fileCentralSize) ERR(3); - if (getuint32(buffer_) != DIR_END_SIGNATURE) { - // There must be a comment in the last entry. Search for the beginning of the entry - off_t endOffset = ecdOffset - 65536; - if (endOffset < 0) { - endOffset = 0; - } - ecdOffset -= fileCentralSize; - bool found = false; - while (!found && (ecdOffset > endOffset)) { - if (!file_.seek(ecdOffset, SSeekSet)) ERR(4); - if (file_.read(buffer_, fileCentralSize + 5) != (fileCentralSize + 5)) ERR(5); - auto p = static_cast(memmem(buffer_, fileCentralSize + 5, "PK\5\6", 4)); - if (p != nullptr) { - ecdOffset += (p - buffer_); - if (!file_.seek(ecdOffset, SSeekSet)) ERR(6); - if (file_.read(buffer_, fileCentralSize) != fileCentralSize) ERR(7); - found = true; - break; - } - ecdOffset -= fileCentralSize; - } - if (!found) { - ecdOffset = 0; - } - } - - if (ecdOffset > 0) { - // Central Directory record structure: - - // [file header 1] - // . - // . - // . - // [file header n] - // [digital signature] // PKZip 6.2 or later only - - uint32_t startOffset = getuint32(&buffer_[16]); - uint16_t count = getuint16(&buffer_[10]); - uint32_t length = ecdOffset - startOffset; - - if (count == 0) ERR(8); - - if (!file_.seek(startOffset, SSeekSet)) ERR(9); - auto entries = SpiramArrayPtr(length); - if ((entries == nullptr) || (entries == nullptr)) ERR(10); - if (file_.read(entries.get(), length) != length) ERR(11); - - // fileEntries_.resize(count); - // uint32_t fileEntryOffset = startOffset; - uint32_t fileEntryOffset = 0; - while (count > 0) { - auto *dirFileHeader = reinterpret_cast(&entries[fileEntryOffset]); - - if (dirFileHeader->signature != DIR_FILE_HEADER_SIGNATURE) { - if (fileEntryOffset != length) ERR(11); - } - - const char *fName = - reinterpret_cast(&entries[fileEntryOffset + sizeof(DirFileHeader)]); - auto filePath = SpiramString(fName, dirFileHeader->filePathLength); - - std::shared_ptr fe = std::make_shared( - FileEntry{dirFileHeader->headerOffset, dirFileHeader->compressedSize, - dirFileHeader->uncompressedSize, dirFileHeader->compresionMethod}); - - fileEntries_[filePath] = std::move(fe); - - fileEntryOffset += sizeof(DirFileHeader) + dirFileHeader->filePathLength + - dirFileHeader->extraFieldLength + - dirFileHeader->commentFieldLength; - count--; - } - completed = count == 0; - } else { - log_e("Unable to read central directory."); - ERR(14); - } - break; - } - - if (!completed) { - log_e("open error: %d", err); - close(); - } else { - // log_d("open completed!"); - showEntries(); - } - - return completed; -} - -void Unzipper::close() { - if (isOpen_) { - if (currentFileEntry_ != nullptr) { - currentFileEntry_.reset(); - }; - currentFileEntry_ = nullptr; - - fileEntries_.clear(); - if (file_) { - file_.close(); - } - isOpen_ = false; - } -} - -#if 0 -struct dir_component { - char * dir; // one component of the path (no separators) - int len; // length of this component -}; - -// a way to print struct dir_component entries for debug -static char lbuf[32]; -char * -dc_str(struct dir_component * dir) -{ - int len = dir->len; - - if (len > sizeof(lbuf)-1) - len = sizeof(lbuf)-1; - - if (len) - strncpy(lbuf, dir->dir, len); - lbuf[len] = '\0'; - - return lbuf; -} - - -char * -canonicalize_path(char * path, int debug) -{ - struct dir_component * dirs; - struct dir_component * dp; - char * p; - char * q; - int n_dirs; - int first_dir; - int i; - int j; - - if (path[0] == '\0') - return path; - - // - // estimate the number of directory components in path - // - - n_dirs = 1; - for (p = path; *p; p++) - if (*p == '/') - n_dirs++; - - if (debug) { - printf("========\n"); - printf("in: %s (%d)\n", path, n_dirs); - } - - dirs = malloc(sizeof(struct dir_component) * n_dirs); - if (dirs == NULL) { - printf("canonicalize_path: out of memory\n"); - return NULL; - } - - // - // break path into component parts - // - // path separators mark the start of each directory component - // (except maybe the first) - // - - n_dirs = 0; - dirs[n_dirs].dir = path; - dirs[n_dirs].len = 0; - - for (p = path; *p; p++) { - if (*p == '/') { - // found a new component, save its location - // and start counting its length - n_dirs++; - dirs[n_dirs].dir = p + 1; - dirs[n_dirs].len = 0; - } else { - dirs[n_dirs].len++; - } - } - n_dirs++; - - if (debug) { - for (i = 0; i < n_dirs; i++) - printf(" %2d %s\n", dirs[i].len, dc_str(&dirs[i])); - } - - // - // canonicalize '.' and '..' - // - - for (i = 0; i < n_dirs; i++) { - dp = &dirs[i]; - - // mark '.' as an empty component - if (dp->len == 1 && dp->dir[0] == '.') { - dirs[i].len = 0; - continue; - } - - // '..' eliminates the previous non-empty component - if (dp->len == 2 && dp->dir[0] == '.' && dp->dir[1] == '.') { - dirs[i].len = 0; - for (j = i - 1; j >= 0; j--) - if (dirs[j].len > 0) - break; - if (j < 0) { - free(dirs); // no previous components are available, - return NULL; // the path is invalid - } - dirs[j].len = 0; - } - } - - if (debug) { - printf("----\n"); - for (i = 0; i < n_dirs; i++) - printf(" %2d %s\n", dirs[i].len, dc_str(&dirs[i])); - } - - // - // reconstruct path using non-zero length components - // - - p = path; - if (*p == '/') // if path was absolute, keep it that way - p++; - first_dir = 1; - for (i = 0; i < n_dirs; i++) - if (dirs[i].len > 0) { - if (!first_dir) - *p++ = '/'; - q = dirs[i].dir; - j = dirs[i].len; - while (j--) - *p++ = *q++; - first_dir = 0; - } - *p = '\0'; - - free(dirs); - return path; -} -#endif - -// This method cleans filePath path that may contain relation folders (like '..'). -auto Unzipper::cleanFilePath(const char *filePath) -> std::string { - std::string ret(strlen(filePath), '\0'); - - char *str = ret.data(); - - const char *s = filePath; - const char *u; - char *t = str; - - while ((u = strstr(s, "/../")) != nullptr) { - const char *ss = s; // keep it for copy in target - s = u + 3; // prepare for next iteration - do { // get rid of preceeding folder name - u--; - } while ((u > ss) && (*u != '/')); - if (u >= ss) { - while (ss != u) { - *t++ = *ss++; - } - } else if ((*u != '/') && (t > str)) { - do { - t--; - } while ((t > str) && (*t != '/')); - } - } - if ((t == str) && (*s == '/')) { - s++; - } - while ((*t++ = *s++)) { - ; - } - - ret.resize(strlen(ret.data())); - return ret; -} - -auto Unzipper::getFileSize() -> uint32_t { - if (!isOpen_ || (currentFileEntry_ == nullptr)) { - log_e("Hum... no current file!"); - return 0; - } - if constexpr (UNZIPPER_DEBUG) { - log_d("File size: %" PRIu32, currentFileEntry_->size); - } - return currentFileEntry_->size; -} - -auto Unzipper::fileExists(const std::string &filePath) -> bool { - if (!isOpen_) { - return false; - } - - std::string cleanedFilePath = cleanFilePath(filePath.c_str()); - - return fileEntries_.find(static_cast(cleanedFilePath)) != fileEntries_.end(); -} - -void Unzipper::showEntries() { - if constexpr (UNZIPPER_DEBUG) { - std::cout << "---- Files available: ----" << std::endl; - for (auto &f : fileEntries_) { - std::cout << "pos: " << std::setw(7) << f.second->startPos - << " zip size: " << std::setw(7) << f.second->compressedSize - << " out size: " << std::setw(7) << f.second->size - << " method: " << std::setw(1) << f.second->method << " name: <" << f.first - << ">" << std::endl; - } - std::cout << "[End of List]" << std::endl; - } -} - -auto Unzipper::openFile(const std::string &filePath) -> bool { - - if (!isOpen_) { - return false; - } - - std::string cleanedFilePath = cleanFilePath(filePath.c_str()); - - if (auto it = fileEntries_.find(static_cast(cleanedFilePath)); - it == fileEntries_.end()) { - log_e("Unzipper openFile: File not found: <%s>", cleanedFilePath.c_str()); - showEntries(); - } else { - currentFileEntry_ = it->second; - int err = 0; - bool completed = false; - - while (true) { - if (!file_.seek(currentFileEntry_->startPos, SSeekSet)) ERR(13); - if (file_.read(reinterpret_cast(&fileHeader_), sizeof(FileHeader)) != - sizeof(FileHeader)) - ERR(14); - - if (fileHeader_.signature != FILE_HEADER_SIGNATURE) ERR(15); - - completed = true; - break; - } - - if (completed) { - return true; - } else { - log_e("Unzipper openFile: Error!: %d", err); - } - } - - return false; -} - -void Unzipper::closeFile() { - if (currentFileEntry_ != nullptr) { - currentFileEntry_.reset(); - } - currentFileEntry_ = nullptr; -} - -auto Unzipper::readFile(uint8_t *fileData, uint32_t fileDataSize) -> uint32_t { - // log_d("getFile: %s", filePath); - - if (!isOpen_ || (currentFileEntry_ == nullptr) || (currentFileEntry_->size > fileDataSize)) { - return 0; - } - - int err = 0; - uint32_t fileSize = 0; - - bool completed = false; - while (true) { - - if (!file_.seek(currentFileEntry_->startPos + sizeof(FileHeader) + - fileHeader_.filePathLength + fileHeader_.extraFieldLength, - SSeekSet)) - ERR(17); - - if (fileData == nullptr) ERR(18); - - if (currentFileEntry_->method == 0) { // No Compression - if (file_.read(fileData, currentFileEntry_->size) != currentFileEntry_->size) ERR(19); - } else if (currentFileEntry_->method == 8) { // Deflate compression - - repeat_ = currentFileEntry_->compressedSize / BUFFER_SIZE; - remains_ = currentFileEntry_->compressedSize % BUFFER_SIZE; - current_ = 0; - - zstr_.zalloc = SpiZAlloc; - zstr_.zfree = SpiZFree; - // zstr_.zalloc = nullptr; - // zstr_.zfree = nullptr; - zstr_.opaque = nullptr; - zstr_.next_in = nullptr; - zstr_.avail_in = 0; - zstr_.avail_out = currentFileEntry_->size; - zstr_.next_out = static_cast(fileData); - - int zret = mz_inflateInit2(&zstr_, -15); - - // Use inflateInit2 with negative windowBits to get raw decompression - if (zret != MZ_OK) { - ERR(23); - } - - // int szDecomp; - - // Decompress until deflate stream ends or end of file - do { - uint16_t size = current_ < repeat_ ? BUFFER_SIZE : remains_; - - if constexpr (UNZIPPER_DEBUG) { - log_w("File size to read: %" PRIu16, size); - } - - if (file_.read(static_cast(buffer_), size) != size) { - mz_inflateEnd(&zstr_); - err = 24; - goto error; - } - - current_++; - - zstr_.avail_in = size; - zstr_.next_in = static_cast(buffer_); - - zret = mz_inflate(&zstr_, MZ_NO_FLUSH); - - switch (zret) { - case MZ_NEED_DICT: - case MZ_DATA_ERROR: - case MZ_MEM_ERROR: - mz_inflateEnd(&zstr_); - err = 25; - goto error; - default:; - } - } while (current_ <= repeat_); - - mz_inflateEnd(&zstr_); - - } else { - break; - } - - completed = true; - break; - } - -error: - if (!completed) { - fileSize = 0; - log_e("Unzipper readFile: Error!: %d", err); - } else { - fileSize = currentFileEntry_->size; - } - - return fileSize; -} diff --git a/src/Misc/Unzipper.hpp b/src/Misc/Unzipper.hpp deleted file mode 100644 index 420956032..000000000 --- a/src/Misc/Unzipper.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "Storage/FileSystem.hpp" - -static const constexpr bool UNZIPPER_DEBUG = false; - -class Unzipper { -private: - static constexpr char const *TAG = "Unzipper"; - -#pragma pack(push, 1) - // File header: - - // central file header signature 4 bytes (0x02014b50) - // version made by 2 bytes - // version needed to extract 2 bytes - // general purpose bit flag 2 bytes - // compression method 2 bytes - // last mod file time 2 bytes - // last mod file date 2 bytes - // crc-32 4 bytes - // compressed size 4 bytes - // uncompressed size 4 bytes - // file name length 2 bytes - // extra field length 2 bytes - // file comment length 2 bytes - // disk number start 2 bytes - // internal file attributes 2 bytes - // external file attributes 4 bytes - // relative offset of local header 4 bytes - - // file name (variable size) - // extra field (variable size) - // file comment (variable size) - struct DirFileHeader { - uint32_t signature; - uint16_t version; - uint16_t extractVersion; - uint16_t flags; - uint16_t compresionMethod; - uint16_t lastModTime; - uint16_t lastModDate; - uint32_t crc32; - uint32_t compressedSize; - uint32_t uncompressedSize; - uint16_t filePathLength; - uint16_t extraFieldLength; - uint16_t commentFieldLength; - uint16_t diskNumberStart; - uint16_t internalFileAttr; - uint32_t externalFileAttr; - uint32_t headerOffset; - }; - - // Local header record. - - // local file header signature 4 bytes (0x04034b50) - // version needed to extract 2 bytes - // general purpose bit flag 2 bytes - // compression method 2 bytes - // last mod file time 2 bytes - // last mod file date 2 bytes - // crc-32 4 bytes - // compressed size 4 bytes - // uncompressed size 4 bytes - // file name length 2 bytes - // extra field length 2 bytes - - // file name (variable size) - // extra field (variable size) - struct FileHeader { - uint32_t signature; - uint16_t extractVersion; - uint16_t flags; - uint16_t compressionMethod; - uint16_t lastModTime; - uint16_t lastModDate; - uint32_t crc32; - uint32_t compressedSize; - uint32_t uncompressedSize; - uint16_t filePathLength; - uint16_t extraFieldLength; - } fileHeader_{}; -#pragma pack(pop) - - static constexpr const uint32_t DIR_FILE_HEADER_SIGNATURE = 0x02014b50; - static constexpr const uint32_t FILE_HEADER_SIGNATURE = 0x04034b50U; - static constexpr const uint32_t DIR_END_SIGNATURE = 0x06054b50; - - static const int BUFFER_SIZE = 1024 * 16; - uint8_t buffer_[BUFFER_SIZE]{}; - - struct FileEntry { - uint32_t startPos; // in zip file - uint32_t compressedSize; // in zip file - uint32_t size; // once decompressed - uint16_t method; // compress method (0 = not compressed, 8 = DEFLATE) - }; - - typedef SpiramMap> FileEntries; - - FileEntries fileEntries_; - std::shared_ptr currentFileEntry_; - - // As long as we are operating as little endian, the following is ok - [[nodiscard]] inline auto getuint32(const uint8_t *b) const -> uint32_t { - return *reinterpret_cast(const_cast(b)); - } - [[nodiscard]] inline auto getuint16(const uint8_t *b) const -> uint16_t { - return *reinterpret_cast(const_cast(b)); - } - - auto cleanFilePath(const char *filePath) -> std::string; - - uint16_t repeat_{}; - uint16_t remains_{}; - uint16_t current_{}; - - SFile file_; // Current File Descriptor - bool isOpen_{false}; - - mz_stream zstr_{}; - -public: - Unzipper(const SFile &file) : file_(file) {} - - ~Unzipper() { - if (isOpen_) { - close(); - } - } - auto open() -> bool; - void close(); - - auto getFileSize() -> uint32_t; - auto readFile(uint8_t *fileData, uint32_t fileDataSize) -> uint32_t; - auto fileExists(const std::string &filePath) -> bool; - void showEntries(); - auto openFile(const std::string &filePath) -> bool; - void closeFile(); -}; diff --git a/src/Misc/bmp.cpp b/src/Misc/bmp.cpp deleted file mode 100644 index 46cf2c718..000000000 --- a/src/Misc/bmp.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/** - * @file bmp.c - * @author Nilo Edson (niloedson.ms@gmail.com) - * @brief Bitmap C library - * @version 0.7 - * @date 2022-04-06 - * - * @copyright Copyright (c) 2022 - */ - -#include "bmp.h" - -#include - -auto BmpMalloc(size_t size) -> void * { return SpiramMalloc(size); } - -auto BmpRead(RendererStream &stream) -> bmp_image * { - bmp_image *img = nullptr; - - img = static_cast(BmpMalloc(sizeof(bmp_image))); - if (img == nullptr) { - return BmpCleanup(nullptr, img); - } - - if (stream.readBytes(reinterpret_cast(&img->fileheader), sizeof(bmp_fileheader)) != - sizeof(bmp_fileheader)) { - return BmpCleanup(nullptr, img); - } - - if (stream.readBytes(reinterpret_cast(&img->dib.bmiHeader), - sizeof(bmp_infoheader)) != sizeof(bmp_infoheader)) { - return BmpCleanup(nullptr, img); - } - - if (img->dib.bmiHeader.biSize >= BMP_V4HEADER) { - if (stream.readBytes(reinterpret_cast(&img->dib.bmiv4Header), - sizeof(bmp_v4header)) != sizeof(bmp_v4header)) { - return BmpCleanup(nullptr, img); - } - } - - if (img->dib.bmiHeader.biSize >= BMP_V5HEADER) { - if (stream.readBytes(reinterpret_cast(&img->dib.bmiv5Header), - sizeof(bmp_v5header)) != sizeof(bmp_v5header)) { - return BmpCleanup(nullptr, img); - } - } - - if (BmpCheckheaders(img) == 0) { - return BmpCleanup(nullptr, img); - } - - uint32_t palettesize = BmpGetpalettesize(img); - - switch (img->dib.bmiHeader.biBitCount) { - case BMP_1_BIT: - case BMP_4_BITS: - case BMP_8_BITS: - img->dib.bmiColors = static_cast(BmpMalloc(palettesize)); - if (stream.readBytes(reinterpret_cast(img->dib.bmiColors), palettesize) != - palettesize) { - return BmpCleanup(nullptr, img); - } - break; - case BMP_16_BITS: - case BMP_32_BITS: - if (img->dib.bmiHeader.biCompression == BMP_BI_BITFIELDS) { - img->dib.bmiColors = static_cast(BmpMalloc(palettesize)); - if (stream.readBytes(reinterpret_cast(img->dib.bmiColors), palettesize) != - palettesize) { - return BmpCleanup(nullptr, img); - } - } - break; - case BMP_24_BITS: - // never expect a BMP color palette - default: - break; - } - - uint32_t datasize = BmpGetdatasize(img); - - img->ciPixelArray = static_cast(BmpMalloc(sizeof(uint8_t) * datasize)); - - if (img->ciPixelArray == nullptr) { - return BmpCleanup(nullptr, img); - } - - if (stream.readBytes(img->ciPixelArray, sizeof(uint8_t) * datasize) != - sizeof(uint8_t) * datasize) { - return BmpCleanup(nullptr, img); - } - - return img; -} - -auto BmpGetpixelcolor(bmp_image *img, int x, int y, bmp_color color) -> uint8_t { - switch (img->dib.bmiHeader.biBitCount) { - case BMP_0_BITS: - case BMP_1_BIT: - case BMP_2_BITS: - case BMP_4_BITS: - // TODO: add support for these formats. - return 0; - break; - case BMP_8_BITS: - return img->ciPixelArray[y * img->dib.bmiHeader.biWidth + x]; - break; - case BMP_16_BITS: - // TODO: add support for this format. - return 0; - break; - case BMP_24_BITS: - return img->ciPixelArray[3 * (y * img->dib.bmiHeader.biWidth + x) + color]; - break; - case BMP_32_BITS: - return img->ciPixelArray[4 * (y * img->dib.bmiHeader.biWidth + x) + color]; - break; - - default: - return 0; - break; - } -} - -auto BmpFindgray(uint8_t red, uint8_t green, uint8_t blue) -> uint8_t { - double gray = 0.2989 * red + 0.5870 * green + 0.1140 * blue; - return static_cast(gray); -} - -void BmpFiltercolor(bmp_image *img, bmp_color color) { - // TODO: add support for 16 and 32 bits per pixel images. - if (img->dib.bmiHeader.biBitCount != BMP_24_BITS) { - return; - } - - // TODO: add support for compressed images. - if (img->dib.bmiHeader.biCompression != BMP_BI_RGB) { - return; - } - - uint32_t datasize = BmpGetdatasize(img); - - for (unsigned int pixel = color; pixel < datasize; pixel++) { - if ((pixel % 3) != color) { - img->ciPixelArray[pixel] = 0; - } - } -} - -auto BmpCleanup(FILE *fptr, bmp_image *img) -> bmp_image * { - if (fptr != nullptr) { - if (fclose(fptr) != 0) { - log_e("Failed to close file"); - } - } - - if (img != nullptr) { - if (img->dib.bmiColors != nullptr) { - free(img->dib.bmiColors); - } - if (img->ciPixelArray != nullptr) { - free(img->ciPixelArray); - } - free(img); - } - - return nullptr; -} - -auto BmpCheckheaders(bmp_image *img) -> int { - if (img->fileheader.bfType != BMP_FILETYPE_BM) { - return 0; - } - - switch (img->dib.bmiHeader.biBitCount) { - case BMP_0_BITS: - case BMP_1_BIT: - case BMP_2_BITS: - case BMP_4_BITS: - case BMP_8_BITS: - case BMP_16_BITS: - case BMP_24_BITS: - case BMP_32_BITS: - break; - default: - return 0; - } - - if (img->dib.bmiHeader.biPlanes != BMP_DEFAULT_COLORPLANES) { - return 0; - } - - switch (img->dib.bmiHeader.biCompression) { - case BMP_BI_RGB: - case BMP_BI_RLE8: - case BMP_BI_RLE4: - case BMP_BI_BITFIELDS: - case BMP_BI_JPEG: - case BMP_BI_PNG: - case BMP_BI_ALPHABITFIELDS: - case BMP_BI_CMYK: - case BMP_BI_CMYKRLE8: - case BMP_BI_CMYKRLE4: - break; - default: - return 0; - } - - if (img->dib.bmiHeader.biSize >= BMP_V4HEADER) { - switch (img->dib.bmiv4Header.bV4CSType) { - case BMP_LCS_CALIBRATED_RGB: - case BMP_LCS_sRGB: - case BMP_LCS_WINDOWS_COLOR_SPACE: - case BMP_LCS_PROFILE_LINKED: - case BMP_LCS_PROFILE_EMBEDDED: - break; - default: - return 0; - } - } - - return 1; -} - -auto BmpGetfilesize(bmp_image *img) -> uint32_t { return img->fileheader.bfSize; } - -auto BmpGetoffset(bmp_image *img) -> uint32_t { return img->fileheader.bfOffBits; } - -auto BmpGetdatasize(bmp_image *img) -> uint32_t { - return img->fileheader.bfSize - img->fileheader.bfOffBits; -} - -auto BmpGetdibformat(bmp_image *img) -> uint32_t { - switch (img->dib.bmiHeader.biSize) { - case BMP_INFOHEADER: - return BMP_INFOHEADER; - case BMP_V4HEADER: - return BMP_V4HEADER; - case BMP_V5HEADER: - return BMP_V5HEADER; - default: - return BMP_UNKNOWN_HEADER; - } -} - -auto BmpGetbitcount(bmp_image *img) -> uint16_t { return img->dib.bmiHeader.biBitCount; } - -auto BmpGetpalettesize(bmp_image *img) -> uint32_t { - switch (img->dib.bmiHeader.biBitCount) { - case BMP_0_BITS: - case BMP_1_BIT: - case BMP_2_BITS: - case BMP_4_BITS: - case BMP_8_BITS: - if (img->dib.bmiHeader.biClrUsed == 0) { - return sizeof(bmp_rgbquad) * pow(2, img->dib.bmiHeader.biBitCount); - } else { - return sizeof(bmp_rgbquad) * img->dib.bmiHeader.biClrUsed; - } - break; - case BMP_16_BITS: - return sizeof(bmp_rgbquad) * 3; - break; - case BMP_32_BITS: - return sizeof(bmp_rgbquad) * 4; - break; - case BMP_24_BITS: - // never expect color palette - default: - return 0; - } -} - -auto BmpGetcompression(bmp_image *img) -> uint32_t { - switch (img->dib.bmiHeader.biCompression) { - case BMP_BI_RGB: - return BMP_BI_RGB; - case BMP_BI_RLE8: - return BMP_BI_RLE8; - case BMP_BI_RLE4: - return BMP_BI_RLE4; - case BMP_BI_BITFIELDS: - return BMP_BI_BITFIELDS; - case BMP_BI_JPEG: - return BMP_BI_JPEG; - case BMP_BI_PNG: - return BMP_BI_PNG; - case BMP_BI_ALPHABITFIELDS: - return BMP_BI_ALPHABITFIELDS; - case BMP_BI_CMYK: - return BMP_BI_CMYK; - case BMP_BI_CMYKRLE8: - return BMP_BI_CMYKRLE8; - case BMP_BI_CMYKRLE4: - return BMP_BI_CMYKRLE4; - default: - return BMP_UNKNOWN_COMPRESSION; - } -} - -auto BmpGetnpixels(bmp_image *img) -> uint32_t { - return img->dib.bmiHeader.biWidth * img->dib.bmiHeader.biHeight; -} - -auto BmpGetncolors(bmp_image *img) -> uint32_t { - switch (img->dib.bmiHeader.biBitCount) { - case BMP_0_BITS: - case BMP_1_BIT: - case BMP_2_BITS: - case BMP_4_BITS: - case BMP_8_BITS: - if (img->dib.bmiHeader.biClrUsed == 0) { - return pow(2, img->dib.bmiHeader.biBitCount); - } else { - return img->dib.bmiHeader.biClrUsed; - } - break; - case BMP_16_BITS: - return 3; - break; - case BMP_32_BITS: - return 4; - break; - case BMP_24_BITS: - // never expect color palette - default: - return 0; - } -} \ No newline at end of file diff --git a/src/Misc/bmp.h b/src/Misc/bmp.h deleted file mode 100644 index 412b696e3..000000000 --- a/src/Misc/bmp.h +++ /dev/null @@ -1,402 +0,0 @@ -// Adapted from https://github.com/niloedson/Bitmap-C-Library (MIT license) -// Changed to support stream reading, and removed unused sections - -/** - * @file bmp.h - * @author Nilo Edson (niloedson.ms@gmail.com) - * @brief Bitmap C library - * @version 0.7 - * @date 2022-04-06 - * - * @copyright Copyright (c) 2022 - */ - -#pragma once - -#include "Renderers/RendererStream.hpp" - -/* Includes -------------------------------------------------------------------*/ -#include -#include -#include -#include -#include - -/* Defines --------------------------------------------------------------------*/ -#define BMP_FILETYPE_BM 0x4d42 -#define BMP_FILETYPE_BA 0x4142 -#define BMP_FILETYPE_CI 0x4943 -#define BMP_FILETYPE_CP 0x5043 -#define BMP_FILETYPE_IC 0x4349 -#define BMP_FILETYPE_PT 0x5054 - -#define BMP_DEFAULT_COLORPLANES 1 - -#define BMP_FILEHEADER_SIZE 14 - -// 16bpp bit masks ======================================== -#define BMP_BITFIELDS_R5G5B5_R5 0x7C00 -#define BMP_BITFIELDS_R5G5B5_G5 0x03E0 -#define BMP_BITFIELDS_R5G5B5_B5 0x001F - -#define BMP_BITFIELDS_R5G6B5_R5 0xF800 -#define BMP_BITFIELDS_R5G6B5_G6 0x07E0 -#define BMP_BITFIELDS_R5G6B5_B5 0x001F - -#define BMP_ALPHABITFIELDS_R4G4B4A4_R4 0x0F00 -#define BMP_ALPHABITFIELDS_R4G4B4A4_G4 0x00F0 -#define BMP_ALPHABITFIELDS_R4G4B4A4_B4 0x000F -#define BMP_ALPHABITFIELDS_R4G4B4A4_A4 0xF000 - -// 32bpp bit masks ======================================== -#define BMP_ALPHABITFIELDS_R8G8B8A8_R8 0x00FF0000 -#define BMP_ALPHABITFIELDS_R8G8B8A8_G8 0x0000FF00 -#define BMP_ALPHABITFIELDS_R8G8B8A8_B8 0x000000FF -#define BMP_ALPHABITFIELDS_R8G8B8A8_A8 0xFF000000 - -#define BMP_ALPHABITFIELDS_R8G7B9A5_R8 0x0001FE00 -#define BMP_ALPHABITFIELDS_R8G7B9A5_G7 0x00FE0000 -#define BMP_ALPHABITFIELDS_R8G7B9A5_B9 0x000001FF -#define BMP_ALPHABITFIELDS_R8G7B9A5_A5 0x1F000000 - -#pragma pack(push, 1) - -/* Enumerations ---------------------------------------------------------------*/ - -typedef enum bmp_dibformat { - BMP_UNKNOWN_HEADER = 0, - BMP_INFOHEADER = 40, - BMP_V4HEADER = 108, - BMP_V5HEADER = 124 -} bmp_dibformat; - -typedef enum bmp_bitcount { - BMP_32_BITS = 32, - BMP_24_BITS = 24, - BMP_16_BITS = 16, - BMP_8_BITS = 8, - BMP_4_BITS = 4, - BMP_2_BITS = 2, - BMP_1_BIT = 1, - BMP_0_BITS = 0 -} bmp_bitcount; - -typedef enum bmp_compression { - BMP_UNKNOWN_COMPRESSION = 32, - BMP_BI_RGB = 0, - BMP_BI_RLE8 = 1, - BMP_BI_RLE4 = 2, - BMP_BI_BITFIELDS = 3, - BMP_BI_JPEG = 4, - BMP_BI_PNG = 5, - BMP_BI_ALPHABITFIELDS = 6, - BMP_BI_CMYK = 11, - BMP_BI_CMYKRLE8 = 12, - BMP_BI_CMYKRLE4 = 13 -} bmp_compression; - -typedef enum bmp_color { - BMP_COLOR_ALPHA = 3, - BMP_COLOR_RED = 2, - BMP_COLOR_GREEN = 1, - BMP_COLOR_BLUE = 0 -} bmp_color; - -typedef enum bmp_setncolours { - BMP_SET_256_COLOURS = 256, - BMP_SET_128_COLOURS = 128, - BMP_SET_64_COLOURS = 64, - BMP_SET_32_COLOURS = 32, - BMP_SET_16_COLOURS = 16, - BMP_SET_8_COLOURS = 8, - BMP_SET_4_COLOURS = 4, - BMP_SET_2_COLOURS = 2 -} bmp_setncolours; - -typedef enum bmp_padtype { BMP_PADTYPE_ZEROS, BMP_PADTYPE_REPLICATE } bmp_padtype; - -// (from https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logcolorspacea) -// (from -// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/eb4bbd50-b3ce-4917-895c-be31f214797f) -typedef enum bmp_bv4cstype { - BMP_LCS_CALIBRATED_RGB = 0x00000000, - BMP_LCS_sRGB = 0x73524742, - BMP_LCS_WINDOWS_COLOR_SPACE = 0x57696E20 -} bmp_bv4cstype; - -// (from -// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/3c289fe1-c42e-42f6-b125-4b5fc49a2b20) -typedef enum bmp_bv5cstype { - BMP_LCS_PROFILE_LINKED = 0x4C494E4B, - BMP_LCS_PROFILE_EMBEDDED = 0x4D424544 -} bmp_bv5cstype; - -// (from https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header) -typedef enum bmp_bv5intent { - BMP_LCS_GM_ABS_COLORIMETRIC, - BMP_LCS_GM_BUSINESS, - BMP_LCS_GM_GRAPHICS, - BMP_LCS_GM_IMAGES -} bmp_bv5intent; - -#pragma pack(pop) - -/* RLE structures -------------------------------------------------------------*/ - -#pragma pack(push, 1) -typedef struct BmpRle8duo { - uint8_t count; - uint8_t value; -} bmp_rle8duo; -#pragma pack(pop) - -/* Coordinates structures -----------------------------------------------------*/ - -// CIEXYZ structure -#pragma pack(push, 1) -typedef struct BmpCiexyz { - uint32_t ciexyzX; // all 3 are FXPT2DOT30 type variables. - uint32_t ciexyzY; // 32-bit floats - uint32_t ciexyzZ; // still need to clarify these types -} bmp_ciexyz; -#pragma pack(pop) - -// CIEXYZTRIPLE structure -#pragma pack(push, 1) -typedef struct BmpCiexyztriple { - bmp_ciexyz ciexyzRed; - bmp_ciexyz ciexyzGreen; - bmp_ciexyz ciexyzBlue; -} bmp_ciexyztriple; -#pragma pack(pop) - -/* FILE Header ----------------------------------------------------------------*/ - -// BITMAPFILEHEADER structure -#pragma pack(push, 1) -typedef struct BmpFileheader { - uint16_t bfType; - uint32_t bfSize; - uint16_t bfReserved1; - uint16_t bfReserved2; - uint32_t bfOffBits; -} bmp_fileheader; -#pragma pack(pop) - -/* DIB Headers ----------------------------------------------------------------*/ - -// BITMAPINFOHEADER structure -#pragma pack(push, 1) -typedef struct BmpInfoheader { - uint32_t biSize; - int32_t biWidth; - int32_t biHeight; - uint16_t biPlanes; - uint16_t biBitCount; - uint32_t biCompression; - uint32_t biSizeImage; - int32_t biXPelsPerMeter; - int32_t biYPelsPerMeter; - uint32_t biClrUsed; - uint32_t biClrImportant; -} bmp_infoheader; -#pragma pack(pop) - -// BITMAPV4HEADER structure complementary piece -#pragma pack(push, 1) -typedef struct BmpV4header { - uint32_t bV4RedMask; - uint32_t bV4GreenMask; - uint32_t bV4BlueMask; - uint32_t bV4AlphaMask; - uint32_t bV4CSType; - bmp_ciexyztriple bV4Endpoints; - uint32_t bV4GammaRed; - uint32_t bV4GammaGreen; - uint32_t bV4GammaBlue; -} bmp_v4header; -#pragma pack(pop) - -// BITMAPV5HEADER structure complementary piece -#pragma pack(push, 1) -typedef struct BmpV5header { - uint32_t bV5Intent; - uint32_t bV5ProfileData; - uint32_t bV5ProfileSize; - uint32_t bV5Reserved; -} bmp_v5header; -#pragma pack(pop) - -/* Colour Palettes ------------------------------------------------------------*/ - -// RGBQUAD structure -#pragma pack(push, 1) -typedef struct BmpRgbquad { - uint8_t rgbBlue; - uint8_t rgbGreen; - uint8_t rgbRed; - uint8_t rgbReserved; -} bmp_rgbquad; -#pragma pack(pop) - -// RGBTRIPLE structure -#pragma pack(push, 1) -typedef struct BmpRgbtriple { - uint8_t rgbBlue; - uint8_t rgbGreen; - uint8_t rgbRed; -} bmp_rgbtriple; -#pragma pack(pop) - -/* Joint structures -----------------------------------------------------------*/ - -// BITMAPINFO structure bmp_dibheader -#pragma pack(push, 1) -typedef struct BmpDibheader { - bmp_infoheader bmiHeader; - bmp_v4header bmiv4Header; - bmp_v5header bmiv5Header; - bmp_rgbquad *bmiColors; -} bmp_dibheader; -#pragma pack(pop) - -/* Main Structure -------------------------------------------------------------*/ -// (from https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-storage?redirectedfrom=MSDN) - -#pragma pack(push, 1) -typedef struct BmpImage { - bmp_fileheader fileheader; - bmp_dibheader dib; - uint8_t *ciPixelArray; -} bmp_image; -#pragma pack(pop) - -/* Functions ------------------------------------------------------------------*/ - -/* file related functions -----------------------------------------------------*/ - -/** - * @brief Read a Bitmap image and returns its metadata organized - * in a struct pointer. - * - * @param filename string specifying the filename to be read. - * @return bmp_image* - pointer to the struct loaded with the - * image data. - */ -auto BmpRead(RendererStream &stream) -> bmp_image *; - -/* RGB functions --------------------------------------------------------------*/ - -/** - * @brief Get the specified color value from the image[x,y] pixel. - * - * @param img pointer to the metadata. - * @param x the pixel x position. - * @param y the pixel y position. - * @param color the specified color. - * @return uint8_t - color value retrieved. - */ -auto BmpGetpixelcolor(bmp_image *img, int x, int y, bmp_color color) -> uint8_t; - -/** - * @brief Find the gray-scale equivalent value from RGB entries. - * - * @param red red color entry value. - * @param green green color entry value. - * @param blue blue color entry value. - * @return uint8_t - calculated gray-scale equivalent value. - */ -auto BmpFindgray(uint8_t red, uint8_t green, uint8_t blue) -> uint8_t; - -/* helper functions -----------------------------------------------------------*/ - -/** - * @brief Clean both pointers. - * - * @param fptr file pointer. - * @param img pointer. - * @return bmp_image* always NULL. - */ -auto BmpCleanup(FILE *fptr, bmp_image *img) -> bmp_image *; - -/** - * @brief Check file headers to evaluate Bitmap conformity. - * - * @param img pointer to the metadata. - * @return int - returns 0 if headers have some problem, 1 otherwise. - */ -auto BmpCheckheaders(bmp_image *img) -> int; - -/* utils ----------------------------------------------------------------------*/ - -/** - * @brief Get file size from the metadata. - * - * @param img pointer. - * @return uint32_t - the file size. - */ -auto BmpGetfilesize(bmp_image *img) -> uint32_t; - -/** - * @brief Get offset from the metadata. - * - * @param img pointer. - * @return uint32_t - the offset. - */ -auto BmpGetoffset(bmp_image *img) -> uint32_t; - -/** - * @brief Get datasize from the metadata. - * - * @param img pointer. - * @return uint32_t - the data size. - */ -auto BmpGetdatasize(bmp_image *img) -> uint32_t; - -/** - * @brief Get DIB header format from the metadata. - * - * @param img pointer. - * @return uint32_t - the DIB header format. - */ -auto BmpGetdibformat(bmp_image *img) -> uint32_t; - -/** - * @brief Get bit count from the metadata. - * - * @param img pointer. - * @return uint16_t - the bit count. - */ -auto BmpGetbitcount(bmp_image *img) -> uint16_t; - -/** - * @brief Get palette size from the metadata. - * - * @param img pointer. - * @return uint32_t - the palette size. - */ -auto BmpGetpalettesize(bmp_image *img) -> uint32_t; - -/** - * @brief Get compression type from the metadata. - * - * @param img pointer. - * @return uint32_t - the compression type. - */ -auto BmpGetcompression(bmp_image *img) -> uint32_t; - -/** - * @brief Get how many pixels are specified for this image. - * - * @param img pointer. - * @return uint32_t - the number of pixels. - */ -auto BmpGetnpixels(bmp_image *img) -> uint32_t; - -/** - * @brief Get how many colors are being used by this image. - * - * @param img pointer. - * @return uint32_t - the number of colors - */ -auto BmpGetncolors(bmp_image *img) -> uint32_t; \ No newline at end of file diff --git a/src/Models/DocType.hpp b/src/Models/DocType.hpp deleted file mode 100644 index 0e00fa767..000000000 --- a/src/Models/DocType.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -enum class DocType : uint8_t { TXT, EPUB, NONE }; diff --git a/src/Models/KindlePageFlipper.hpp b/src/Models/KindlePageFlipper.hpp index ce103c586..73a9dfd92 100644 --- a/src/Models/KindlePageFlipper.hpp +++ b/src/Models/KindlePageFlipper.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -9,8 +11,6 @@ #include "Misc/Telemetry.hpp" #include "Models/KindleBook.hpp" #include "Models/KindlePageResponse.hpp" -#include "Models/Place.hpp" -#include "Renderers/RendererStream.hpp" class DispatchQueue; diff --git a/src/Models/Place.cpp b/src/Models/Place.cpp deleted file mode 100644 index 220911077..000000000 --- a/src/Models/Place.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "Place.hpp" - -#include -#include -#include - -#include "sindarin-debug.h" - -auto Place::factory(const std::string &placeStr, DocType docType) -> std::shared_ptr { - - uint32_t offset = 0; - std::string href; - - const char *s = placeStr.c_str(); - - if (*s++ == '[') { - if (strncmp(s, "TXT;offset:", 11) == 0) { - s += 11; - const char *e = s; - while (*e && (*e != ']')) { - e++; - } - if (*e == ']') { - offset = strtol(s, nullptr, 10); - return SpiramPtr(offset); - } - } else if (strncmp(s, "EPUB;offset:", 12) == 0) { - s += 12; - const char *e = s; - while (*e && (*e != ',')) { - e++; - } - if (*e++ == ',') { - offset = strtol(s, nullptr, 10); - s = e; - if (strncmp(s, "href:", 5) == 0) { - s += 5; - e = placeStr.c_str() + placeStr.length() - 1; - if ((*e == ']') && (e > s)) { - href.assign(s, e - s); - return SpiramPtr(offset, href); - } - } - } - } - } else if (placeStr.length() > 0) { - offset = strtol(placeStr.c_str(), nullptr, 10); - return SpiramPtr(offset); - } - - if (docType == DocType::TXT) { - return SpiramPtr(0); - } else if (docType == DocType::EPUB) { - return SpiramPtr(0, ""); - } else { - log_e("Unable to parse place string properly: \"%s\"", placeStr.c_str()); - if constexpr (REND_ON_ERROR_EXIT) { - assert(false && "Unable to parse place string properly"); - } - return SpiramPtr(); - } -} - -auto Place::factory(uint32_t offset, const std::string &href) -> std::shared_ptr { - return SpiramPtr(offset, href); -} - -auto Place::factory(uint32_t offset) -> std::shared_ptr { - return SpiramPtr(offset); -} diff --git a/src/Models/Place.hpp b/src/Models/Place.hpp deleted file mode 100644 index bb7653422..000000000 --- a/src/Models/Place.hpp +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "DocType.hpp" -#include "sindarin-debug.h" - -class Place { -private: -#if SIMULATOR && !TESTS - static constexpr bool REND_ON_ERROR_EXIT = true; -#else - static constexpr bool REND_ON_ERROR_EXIT = false; -#endif - -public: - virtual ~Place() = default; - [[nodiscard]] virtual auto isFor(DocType docType) const -> bool = 0; - [[nodiscard]] virtual auto getDocType() const -> DocType = 0; - [[nodiscard]] virtual auto str() const -> std::string = 0; - [[nodiscard]] virtual auto isZero() const -> bool = 0; - [[nodiscard]] inline auto getOffset() const -> uint32_t { return offset_; } - inline void setOffset(uint32_t offset) { offset_ = offset; } - - static auto factory(const std::string &placeStr, DocType docType = DocType::NONE) - -> std::shared_ptr; - static auto factory(uint32_t offset) -> std::shared_ptr; - static auto factory(uint32_t offset, const std::string &href) -> std::shared_ptr; - - // The following are good for All subclasses BUT the EPubPlace class... - - [[nodiscard]] virtual auto operator==(const Place &rhs) const -> bool { - return (offset_ == rhs.getOffset()); - } - - [[nodiscard]] virtual auto operator!=(const Place &rhs) const -> bool { - return (offset_ != rhs.getOffset()); - } - -protected: - uint32_t offset_{0}; -}; - -// The use of NonePlace will happen if a bug is present in the code. -class NonePlace : public Place { -public: - ~NonePlace() override = default; - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - return docType == DocType::NONE; - } - [[nodiscard]] auto getDocType() const -> DocType override { return DocType::NONE; }; - [[nodiscard]] auto str() const -> std::string override { return "[NONE;INVALID PLACE]"; } - [[nodiscard]] auto isZero() const -> bool override { return true; }; -}; - -// A TxtPlace is the location inside a .txt/.txt.gz document. As the document is represented by a -// single file, the offset_ is a displacement from the beginning of the file. -class TxtPlace : public Place { -public: - TxtPlace(uint32_t offset) { setOffset(offset); } - TxtPlace() : Place() { setOffset(0); } - ~TxtPlace() override = default; - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - return docType == DocType::TXT; - } - [[nodiscard]] auto getDocType() const -> DocType override { return DocType::TXT; }; - [[nodiscard]] auto getOffset() const -> uint32_t { return offset_; } - - [[nodiscard]] auto str() const -> std::string override { - std::string s; - s = "[TXT;offset:"; - s += std::to_string(offset_); - s.append("]"); - return s; - } - [[nodiscard]] auto isZero() const -> bool override { return offset_ == 0; }; -}; - -// An EPubPlace is the location inside a .epub document. The href_ field relates to the href -// located in the EPub opf's manifest. The offset_ field is a displacement inside the file -// identified by the href_ field. -class EPubPlace : public Place { -public: - EPubPlace(uint32_t offset, const std::string_view &href) : href_(href) { setOffset(offset); } - EPubPlace() : Place() { setOffset(0); } - ~EPubPlace() override = default; - - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - return docType == DocType::EPUB; - } - [[nodiscard]] auto getDocType() const -> DocType override { return DocType::EPUB; }; - [[nodiscard]] auto getOffset() const -> uint32_t { return offset_; } - [[nodiscard]] auto getHRef() const -> const SpiramString & { return href_; } - - [[nodiscard]] auto str() const -> std::string override { - std::string s; - s = "[EPUB;offset:"; - s += std::to_string(offset_); - s.append(",href:"); - s += href_; - s.append("]"); - return s; - } - - [[nodiscard]] auto isZero() const -> bool override { return href_ == "null"; }; - - void setHRef(const std::string_view &href) { href_ = href; }; - - [[nodiscard]] auto operator==(const EPubPlace &rhs) const -> bool { - return rhs.isFor(DocType::EPUB) && (offset_ == rhs.getOffset()) && (href_ == rhs.getHRef()); - } - - [[nodiscard]] auto operator==(const Place &rhs) const -> bool override { - return rhs.isFor(DocType::EPUB) && (*this == reinterpret_cast(rhs)); - } - -private: - SpiramString href_; -}; diff --git a/src/Models/Readable.cpp b/src/Models/Readable.cpp index 2556e2cdb..ec607231a 100644 --- a/src/Models/Readable.cpp +++ b/src/Models/Readable.cpp @@ -1,6 +1,6 @@ #include "Readable.hpp" -#include "ReadableTableOfContentsChapter.hpp" +#include // NOLINTNEXTLINE(readability-identifier-naming) - ArduinoJson requires this name auto convertToJson(const Readable &src, JsonVariant dst) -> bool { diff --git a/src/Models/Readable.hpp b/src/Models/Readable.hpp index fa4cf2c23..777db56b3 100644 --- a/src/Models/Readable.hpp +++ b/src/Models/Readable.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -15,13 +16,11 @@ #include "Models/Author.hpp" #include "Models/Json.hpp" #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" #include "Models/ReadableArchive.hpp" #include "Models/ReadableBookmark.hpp" #include "Models/ReadableDictionary.hpp" #include "Models/ReadableFile.hpp" #include "Models/ReadablePlacemark.hpp" -#include "Models/ReadableTableOfContentsChapter.hpp" // This is #define because there might be different types passed in #define SOL_PAGE_NUMBER(x) ((x) / 1000 + 1) @@ -33,7 +32,6 @@ using ReadableFileVector = SpiramVector; using ReadableArchivesVector = SpiramVector; using ReadablePlacemarksVector = SpiramVector; using ReadableBookmarksVector = SpiramVector; -using ReadableTableOfContentsChapterVector = SpiramVector; class Readable : JsonSerializable { public: diff --git a/src/Models/ReadableBookmark.cpp b/src/Models/ReadableBookmark.cpp index 3e092d1a7..f912d2a5a 100644 --- a/src/Models/ReadableBookmark.cpp +++ b/src/Models/ReadableBookmark.cpp @@ -1,6 +1,6 @@ #include "ReadableBookmark.hpp" -#include "Models/Place.hpp" +#include // Simply reads from the bookmark file for this Readable and returns whether it was // a turn on or turn off bookmark. diff --git a/src/Models/ReadableBookmark.hpp b/src/Models/ReadableBookmark.hpp index b331e9c0e..2f08edfdb 100644 --- a/src/Models/ReadableBookmark.hpp +++ b/src/Models/ReadableBookmark.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" #include "Storage/SFile.hpp" enum BookmarkStatus { BOOKMARK_OFF = 0, BOOKMARK_ON = 1 }; diff --git a/src/Models/ReadablePlacemark.hpp b/src/Models/ReadablePlacemark.hpp index 92a29154f..894eecd94 100644 --- a/src/Models/ReadablePlacemark.hpp +++ b/src/Models/ReadablePlacemark.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" class ReadablePlacemark : public JsonSerializable { public: diff --git a/src/Models/ReadableTableOfContentsChapter.cpp b/src/Models/ReadableTableOfContentsChapter.cpp deleted file mode 100644 index 16754fa37..000000000 --- a/src/Models/ReadableTableOfContentsChapter.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "ReadableTableOfContentsChapter.hpp" diff --git a/src/Models/ReadableTableOfContentsChapter.hpp b/src/Models/ReadableTableOfContentsChapter.hpp deleted file mode 100644 index bb99ba334..000000000 --- a/src/Models/ReadableTableOfContentsChapter.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "Models/JsonSerializable.hpp" -#include "Models/ReadableDictionary.hpp" - -struct ReadableTableOfContentsChapter { - SpiramString title; - SpiramString href; - uint32_t epubOffset{}; - uint32_t endEpubOffset{}; - SpiramString inSubsectionHref; -}; diff --git a/src/Models/UserReadable.hpp b/src/Models/UserReadable.hpp index 7264544e7..d70666649 100644 --- a/src/Models/UserReadable.hpp +++ b/src/Models/UserReadable.hpp @@ -1,11 +1,11 @@ #pragma once +#include #include #include #include "Models/Json.hpp" #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" #include "Models/Readable.hpp" #include "Models/UserReadablePartialChapterSummary.hpp" #include "Models/UserReadablePlacemarkHistory.hpp" diff --git a/src/Models/UserReadablePartialChapterSummary.hpp b/src/Models/UserReadablePartialChapterSummary.hpp index ef72eb245..574828f4c 100644 --- a/src/Models/UserReadablePartialChapterSummary.hpp +++ b/src/Models/UserReadablePartialChapterSummary.hpp @@ -1,11 +1,11 @@ #pragma once #include +#include #include #include #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" #include "Storage/FileSystem.hpp" class UserReadablePartialChapterSummary : public JsonSerializable { diff --git a/src/Models/UserReadablePlacemarkHistory.hpp b/src/Models/UserReadablePlacemarkHistory.hpp index 3bfee8599..86180bc11 100644 --- a/src/Models/UserReadablePlacemarkHistory.hpp +++ b/src/Models/UserReadablePlacemarkHistory.hpp @@ -1,12 +1,12 @@ #pragma once #include +#include #include #include #include #include "Models/JsonSerializable.hpp" -#include "Models/Place.hpp" #include "Storage/FileSystem.hpp" class UserReadablePlacemarkHistory : public JsonSerializable { diff --git a/src/Renderers/EPubTokenizer.cpp b/src/Renderers/EPubTokenizer.cpp deleted file mode 100644 index ff5af0d6b..000000000 --- a/src/Renderers/EPubTokenizer.cpp +++ /dev/null @@ -1,1918 +0,0 @@ -#include "EPubTokenizer.hpp" - -#include - -// clang-format off - -// ToDo: Some cleanup required... there maybe more tags than required here - -EPubTokenizer::Tags EPubTokenizer::tags - = {{"p", Tag::P}, {"div", Tag::DIV}, {"span", Tag::SPAN}, - {"br", Tag::BREAK}, {"h1", Tag::H1}, {"h2", Tag::H2}, - {"h3", Tag::H3}, {"h4", Tag::H4}, {"h5", Tag::H5}, - {"h6", Tag::H6}, {"b", Tag::B}, {"i", Tag::I}, - {"em", Tag::EM}, {"a", Tag::A}, {"img", Tag::IMG}, - {"picture", Tag::PICTURE}, {"source", Tag::SOURCE}, {"li", Tag::LI}, - {"blockquote", Tag::BLOCKQUOTE}, {"pre", Tag::PRE}, {"strong", Tag::STRONG}, - {"figure", Tag::FIGURE}, {"sup", Tag::SUP}, {"hr", Tag::HR}, - {"figcaption", Tag::FIGCAPTION}, {"*", Tag::ANY}, {"ol", Tag::OL}, - {"header", Tag::HEADER}, {"sub", Tag::SUB}, {"ul", Tag::UL}, - {"table", Tag::TABLE}, {"th", Tag::TH}, {"tr", Tag::TR}, - {"td", Tag::TD}, {"thead", Tag::THEAD} - }; - -// ToDo: Only put the ones that are defined in the Sol Font - -EPubTokenizer::Entities EPubTokenizer::entities - = { - {"nbsp", "\xC2\xA0"}, // no-break space = non-breaking space, U+00A0 ISOnum - {"iexcl", "\xC2\xA1"}, // inverted exclamation mark, U+00A1 ISOnum - {"cent", "\xC2\xA2"}, // cent sign, U+00A2 ISOnum - {"pound", "\xC2\xA3"}, // pound sign, U+00A3 ISOnum - {"curren", "\xC2\xA4"}, // currency sign, U+00A4 ISOnum - {"yen", "\xC2\xA5"}, // yen sign = yuan sign, U+00A5 ISOnum - {"brvbar", "\xC2\xA6"}, // broken bar = broken vertical bar, U+00A6 ISOnum - {"sect", "\xC2\xA7"}, // section sign, U+00A7 ISOnum - {"uml", "\xC2\xA8"}, // diaeresis = spacing diaeresis, U+00A8 ISOdia - {"copy", "\xC2\xA9"}, // copyright sign, U+00A9 ISOnum - {"ordf", "\xC2\xAA"}, // feminine ordinal indicator, U+00AA ISOnum - {"laquo", "\xC2\xAB"}, // left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum - {"not", "\xC2\xAC"}, // not sign = angled dash, U+00AC ISOnum - {"shy", "\xC2\xAD"}, // soft hyphen = discretionary hyphen, U+00AD ISOnum - {"reg", "\xC2\xAE"}, // registered sign = registered trade mark sign, U+00AE ISOnum - {"macr", "\xC2\xAF"}, // macron = spacing macron = overline = APL overbar, U+00AF ISOdia - {"deg", "\xC2\xB0"}, // degree sign, U+00B0 ISOnum - {"plusmn", "\xC2\xB1"}, // plus-minus sign = plus-or-minus sign, U+00B1 ISOnum - {"sup2", "\xC2\xB2"}, // superscript two = superscript digit two = squared, U+00B2 ISOnum - {"sup3", "\xC2\xB3"}, // superscript three = superscript digit three = cubed, U+00B3 ISOnum - {"acute", "\xC2\xB4"}, // acute accent = spacing acute, U+00B4 ISOdia - {"micro", "\xC2\xB5"}, // micro sign, U+00B5 ISOnum - {"para", "\xC2\xB6"}, // pilcrow sign = paragraph sign, U+00B6 ISOnum - {"middot", "\xC2\xB7"}, // middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum - {"cedil", "\xC2\xB8"}, // cedilla = spacing cedilla, U+00B8 ISOdia - {"sup1", "\xC2\xB9"}, // superscript one = superscript digit one, U+00B9 ISOnum - {"ordm", "\xC2\xBA"}, // masculine ordinal indicator, U+00BA ISOnum - {"raquo", "\xC2\xBB"}, // right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum - {"frac14", "\xC2\xBC"}, // vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum - {"frac12", "\xC2\xBD"}, // vulgar fraction one half = fraction one half, U+00BD ISOnum - {"frac34", "\xC2\xBE"}, // vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum - {"iquest", "\xC2\xBF"}, // inverted question mark = turned question mark, U+00BF ISOnum - {"Agrave", "\xC3\x80"}, // latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 - {"Aacute", "\xC3\x81"}, // latin capital letter A with acute, U+00C1 ISOlat1 - {"Acirc", "\xC3\x82"}, // latin capital letter A with circumflex, U+00C2 ISOlat1 - {"Atilde", "\xC3\x83"}, // latin capital letter A with tilde, U+00C3 ISOlat1 - {"Auml", "\xC3\x84"}, // latin capital letter A with diaeresis, U+00C4 ISOlat1 - {"Aring", "\xC3\x85"}, // latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 - {"AElig", "\xC3\x86"}, // latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 - {"Ccedil", "\xC3\x87"}, // latin capital letter C with cedilla, U+00C7 ISOlat1 - {"Egrave", "\xC3\x88"}, // latin capital letter E with grave, U+00C8 ISOlat1 - {"Eacute", "\xC3\x89"}, // latin capital letter E with acute, U+00C9 ISOlat1 - {"Ecirc", "\xC3\x8A"}, // latin capital letter E with circumflex, U+00CA ISOlat1 - {"Euml", "\xC3\x8B"}, // latin capital letter E with diaeresis, U+00CB ISOlat1 - {"Igrave", "\xC3\x8C"}, // latin capital letter I with grave, U+00CC ISOlat1 - {"Iacute", "\xC3\x8D"}, // latin capital letter I with acute, U+00CD ISOlat1 - {"Icirc", "\xC3\x8E"}, // latin capital letter I with circumflex, U+00CE ISOlat1 - {"Iuml", "\xC3\x8F"}, // latin capital letter I with diaeresis, U+00CF ISOlat1 - {"ETH", "\xC3\x90"}, // latin capital letter ETH, U+00D0 ISOlat1 - {"Ntilde", "\xC3\x91"}, // latin capital letter N with tilde, U+00D1 ISOlat1 - {"Ograve", "\xC3\x92"}, // latin capital letter O with grave, U+00D2 ISOlat1 - {"Oacute", "\xC3\x93"}, // latin capital letter O with acute, U+00D3 ISOlat1 - {"Ocirc", "\xC3\x94"}, // latin capital letter O with circumflex, U+00D4 ISOlat1 - {"Otilde", "\xC3\x95"}, // latin capital letter O with tilde, U+00D5 ISOlat1 - {"Ouml", "\xC3\x96"}, // latin capital letter O with diaeresis, U+00D6 ISOlat1 - {"times", "\xC3\x97"}, // multiplication sign, U+00D7 ISOnum - {"Oslash", "\xC3\x98"}, // latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 - {"Ugrave", "\xC3\x99"}, // latin capital letter U with grave, U+00D9 ISOlat1 - {"Uacute", "\xC3\x9A"}, // latin capital letter U with acute, U+00DA ISOlat1 - {"Ucirc", "\xC3\x9B"}, // latin capital letter U with circumflex, U+00DB ISOlat1 - {"Uuml", "\xC3\x9C"}, // latin capital letter U with diaeresis, U+00DC ISOlat1 - {"Yacute", "\xC3\x9D"}, // latin capital letter Y with acute, U+00DD ISOlat1 - {"THORN", "\xC3\x9E"}, // latin capital letter THORN, U+00DE ISOlat1 - {"szlig", "\xC3\x9F"}, // latin small letter sharp s = ess-zed, U+00DF ISOlat1 - {"agrave", "\xC3\xA0"}, // latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 - {"aacute", "\xC3\xA1"}, // latin small letter a with acute, U+00E1 ISOlat1 - {"acirc", "\xC3\xA2"}, // latin small letter a with circumflex, U+00E2 ISOlat1 - {"atilde", "\xC3\xA3"}, // latin small letter a with tilde, U+00E3 ISOlat1 - {"auml", "\xC3\xA4"}, // latin small letter a with diaeresis, U+00E4 ISOlat1 - {"aring", "\xC3\xA5"}, // latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 - {"aelig", "\xC3\xA6"}, // latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 - {"ccedil", "\xC3\xA7"}, // latin small letter c with cedilla, U+00E7 ISOlat1 - {"egrave", "\xC3\xA8"}, // latin small letter e with grave, U+00E8 ISOlat1 - {"eacute", "\xC3\xA9"}, // latin small letter e with acute, U+00E9 ISOlat1 - {"ecirc", "\xC3\xAA"}, // latin small letter e with circumflex, U+00EA ISOlat1 - {"euml", "\xC3\xAB"}, // latin small letter e with diaeresis, U+00EB ISOlat1 - {"igrave", "\xC3\xAC"}, // latin small letter i with grave, U+00EC ISOlat1 - {"iacute", "\xC3\xAD"}, // latin small letter i with acute, U+00ED ISOlat1 - {"icirc", "\xC3\xAE"}, // latin small letter i with circumflex, U+00EE ISOlat1 - {"iuml", "\xC3\xAF"}, // latin small letter i with diaeresis, U+00EF ISOlat1 - {"eth", "\xC3\xB0"}, // latin small letter eth, U+00F0 ISOlat1 - {"ntilde", "\xC3\xB1"}, // latin small letter n with tilde, U+00F1 ISOlat1 - {"ograve", "\xC3\xB2"}, // latin small letter o with grave, U+00F2 ISOlat1 - {"oacute", "\xC3\xB3"}, // latin small letter o with acute, U+00F3 ISOlat1 - {"ocirc", "\xC3\xB4"}, // latin small letter o with circumflex, U+00F4 ISOlat1 - {"otilde", "\xC3\xB5"}, // latin small letter o with tilde, U+00F5 ISOlat1 - {"ouml", "\xC3\xB6"}, // latin small letter o with diaeresis, U+00F6 ISOlat1 - {"divide", "\xC3\xB7"}, // division sign, U+00F7 ISOnum - {"oslash", "\xC3\xB8"}, // latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 - {"ugrave", "\xC3\xB9"}, // latin small letter u with grave, U+00F9 ISOlat1 - {"uacute", "\xC3\xBA"}, // latin small letter u with acute, U+00FA ISOlat1 - {"ucirc", "\xC3\xBB"}, // latin small letter u with circumflex, U+00FB ISOlat1 - {"uuml", "\xC3\xBC"}, // latin small letter u with diaeresis, U+00FC ISOlat1 - {"yacute", "\xC3\xBD"}, // latin small letter y with acute, U+00FD ISOlat1 - {"thorn", "\xC3\xBE"}, // latin small letter thorn, U+00FE ISOlat1 - {"yuml", "\xC3\xBF"}, // latin small letter y with diaeresis, U+00FF ISOlat1 - {"quot", R"(")"}, // quotation mark, U+0022 ISOnum - {"amp", R"(&)"}, // ampersand, U+0026 ISOnum - {"lt", R"(<)"}, // less-than sign, U+003C ISOnum - {"gt", R"(>)"}, // greater-than sign, U+003E ISOnum - {"apos", R"(')"}, // apostrophe = APL quote, U+0027 ISOnum - {"OElig", "\xC5\x92"}, // latin capital ligature OE, U+0152 ISOlat2 - {"oelig", "\xC5\x93"}, // latin small ligature oe, U+0153 ISOlat2 - {"Scaron", "\xC5\xA0"}, // latin capital letter S with caron, U+0160 ISOlat2 - {"scaron", "\xC5\xA1"}, // latin small letter s with caron, U+0161 ISOlat2 - {"Yuml", "\xC5\xB8"}, // latin capital letter Y with diaeresis, U+0178 ISOlat2 - {"circ", "\xCB\x86"}, // modifier letter circumflex accent, U+02C6 ISOpub - {"tilde", "\xCB\x9C"}, // small tilde, U+02DC ISOdia - {"ensp", "\xE2\x80\x82"}, // en space, U+2002 ISOpub - {"emsp", "\xE2\x80\x83"}, // em space, U+2003 ISOpub - {"thinsp", "\xE2\x80\x89"}, // thin space, U+2009 ISOpub - {"zwnj", "\xE2\x80\x8C"}, // zero width non-joiner, U+200C NEW RFC 2070 - {"zwj", "\xE2\x80\x8D"}, // zero width joiner, U+200D NEW RFC 2070 - {"lrm", "\xE2\x80\x8E"}, // left-to-right mark, U+200E NEW RFC 2070 - {"rlm", "\xE2\x80\x8F"}, // right-to-left mark, U+200F NEW RFC 2070 - {"ndash", "\xE2\x80\x93"}, // en dash, U+2013 ISOpub - {"mdash", "\xE2\x80\x94"}, // em dash, U+2014 ISOpub - {"lsquo", "\xE2\x80\x98"}, // left single quotation mark, U+2018 ISOnum - {"rsquo", "\xE2\x80\x99"}, // right single quotation mark, U+2019 ISOnum - {"sbquo", "\xE2\x80\x9A"}, // single low-9 quotation mark, U+201A NEW - {"ldquo", "\xE2\x80\x9C"}, // left double quotation mark, U+201C ISOnum - {"rdquo", "\xE2\x80\x9D"}, // right double quotation mark, U+201D ISOnum - {"bdquo", "\xE2\x80\x9E"}, // double low-9 quotation mark, U+201E NEW - {"dagger", "\xE2\x80\xA0"}, // dagger, U+2020 ISOpub - {"Dagger", "\xE2\x80\xA1"}, // double dagger, U+2021 ISOpub - {"permil", "\xE2\x80\xB0"}, // per mille sign, U+2030 ISOtech - {"lsaquo", "\xE2\x80\xB9"}, // single left-pointing angle quotation mark, U+2039 ISO proposed - {"rsaquo", "\xE2\x80\xBA"}, // single right-pointing angle quotation mark, U+203A ISO proposed - {"euro", "\xE2\x80\xAC"}, // euro sign, U+20AC NEW -}; - -#if CONFIG_TINYFONT_IBMF -EPubTokenizer::Characters EPubTokenizer::superscriptCharacters -{ - {"0", "\xE2\x81\xB0"}, - {"1", "\xC2\xB9"}, - {"2", "\xC2\xB2"}, - {"3", "\xC2\xB3"}, - {"4", "\xE2\x81\xB4"}, - {"5", "\xE2\x81\xB5"}, - {"6", "\xE2\x81\xB6"}, - {"7", "\xE2\x81\xB7"}, - {"8", "\xE2\x81\xB8"}, - {"9", "\xE2\x81\xB9"}, - - {"A", "\xEE\x81\x81"}, - {"B", "\xEE\x81\x82"}, - {"C", "\xEE\x81\x83"}, - {"D", "\xEE\x81\x84"}, - {"E", "\xEE\x81\x85"}, - {"F", "\xEE\x81\x86"}, - {"G", "\xEE\x81\x87"}, - {"H", "\xEE\x81\x88"}, - {"I", "\xEE\x81\x89"}, - {"J", "\xEE\x81\x8A"}, - {"K", "\xEE\x81\x8B"}, - {"L", "\xEE\x81\x8C"}, - {"M", "\xEE\x81\x8D"}, - {"N", "\xEE\x81\x8E"}, - {"O", "\xEE\x81\x8F"}, - {"P", "\xEE\x81\x90"}, - {"Q", "\xEE\x81\x91"}, - {"R", "\xEE\x81\x92"}, - {"S", "\xEE\x81\x93"}, - {"T", "\xEE\x81\x94"}, - {"U", "\xEE\x81\x95"}, - {"V", "\xEE\x81\x96"}, - {"W", "\xEE\x81\x97"}, - {"X", "\xEE\x81\x98"}, - {"Y", "\xEE\x81\x99"}, - {"Z", "\xEE\x81\x9A"}, - - {"a", "\xEE\x81\x9F"}, - {"b", "\xEE\x81\xA0"}, - {"c", "\xEE\x81\xA1"}, - {"d", "\xEE\x81\xA2"}, - {"e", "\xEE\x81\xA3"}, - {"f", "\xEE\x81\xA4"}, - {"g", "\xEE\x81\xA5"}, - {"h", "\xEE\x81\xA6"}, - {"i", "\xEE\x81\xA7"}, - {"j", "\xEE\x81\xA8"}, - {"k", "\xEE\x81\xA9"}, - {"l", "\xEE\x81\xAA"}, - {"m", "\xEE\x81\xAB"}, - {"n", "\xEE\x81\xAC"}, - {"o", "\xEE\x81\xAD"}, - {"p", "\xEE\x81\xAE"}, - {"q", "\xEE\x81\xAF"}, - {"r", "\xEE\x81\xB0"}, - {"s", "\xEE\x81\xB1"}, - {"t", "\xEE\x81\xB2"}, - {"u", "\xEE\x81\xB3"}, - {"v", "\xEE\x81\xB4"}, - {"w", "\xEE\x81\xB5"}, - {"x", "\xEE\x81\xB6"}, - {"y", "\xEE\x81\xB7"}, - {"z", "\xEE\x81\xB8"}, - - {"*", "\xEE\x81\xB9"}, - {"+", "\xE2\x81\xBA"}, - {"-", "\xE2\x81\xBB"}, - {"=", "\xE2\x81\xBC"}, - {"(", "\xE2\x81\xBD"}, - {")", "\xE2\x81\xBE"}, - {"[", "\xEE\x81\xBB"}, - {"]", "\xEE\x81\xBC"}, - {",", "\xEE\x81\x9B"}, - {".", "\xEE\x81\x9C"}, - {"/", "\xEE\x81\x9D"}, -}; - -EPubTokenizer::Characters EPubTokenizer::subscriptCharacters -{ - {"0", "\xE2\x82\x80"}, - {"1", "\xE2\x82\x81"}, - {"2", "\xE2\x82\x82"}, - {"3", "\xE2\x82\x83"}, - {"4", "\xE2\x82\x84"}, - {"5", "\xE2\x82\x85"}, - {"6", "\xE2\x82\x86"}, - {"7", "\xE2\x82\x87"}, - {"8", "\xE2\x82\x88"}, - {"9", "\xE2\x82\x89"}, - - {"A", "\xEE\x80\x85"}, - {"B", "\xEE\x80\x86"}, - {"C", "\xEE\x80\x87"}, - {"D", "\xEE\x80\x88"}, - {"E", "\xEE\x80\x89"}, - {"F", "\xEE\x80\x8A"}, - {"G", "\xEE\x80\x8B"}, - {"H", "\xEE\x80\x8C"}, - {"I", "\xEE\x80\x8D"}, - {"J", "\xEE\x80\x8E"}, - {"K", "\xEE\x80\x8F"}, - {"L", "\xEE\x80\x90"}, - {"M", "\xEE\x80\x91"}, - {"N", "\xEE\x80\x92"}, - {"O", "\xEE\x80\x93"}, - {"P", "\xEE\x80\x94"}, - {"Q", "\xEE\x80\x95"}, - {"R", "\xEE\x80\x96"}, - {"S", "\xEE\x80\x97"}, - {"T", "\xEE\x80\x98"}, - {"U", "\xEE\x80\x99"}, - {"V", "\xEE\x80\x9A"}, - {"W", "\xEE\x80\x9B"}, - {"X", "\xEE\x80\x9C"}, - {"Y", "\xEE\x80\x9D"}, - {"Z", "\xEE\x80\x9E"}, - - {"a", "\xEE\x80\xA3"}, - {"b", "\xEE\x80\xA4"}, - {"c", "\xEE\x80\xA5"}, - {"d", "\xEE\x80\xA6"}, - {"e", "\xEE\x80\xA7"}, - {"f", "\xEE\x80\xA8"}, - {"g", "\xEE\x80\xA9"}, - {"h", "\xEE\x80\xAA"}, - {"i", "\xEE\x80\xAB"}, - {"j", "\xEE\x80\xAC"}, - {"k", "\xEE\x80\xAD"}, - {"l", "\xEE\x80\xAE"}, - {"m", "\xEE\x80\xAF"}, - {"n", "\xEE\x80\xB0"}, - {"o", "\xEE\x80\xB1"}, - {"p", "\xEE\x80\xB2"}, - {"q", "\xEE\x80\xB3"}, - {"r", "\xEE\x80\xB4"}, - {"s", "\xEE\x80\xB5"}, - {"t", "\xEE\x80\xB6"}, - {"u", "\xEE\x80\xB7"}, - {"v", "\xEE\x80\xB8"}, - {"w", "\xEE\x80\xB9"}, - {"x", "\xEE\x80\xBA"}, - {"y", "\xEE\x80\xBB"}, - {"z", "\xEE\x80\xBC"}, - - {"*", "\xEE\x80\xBD"}, - {"+", "\xE2\x82\x8A"}, - {"-", "\xE2\x82\x8B"}, - {"=", "\xE2\x82\x8C"}, - {"(", "\xE2\x82\x8D"}, - {")", "\xE2\x82\x8E"}, - {"[", "\xEE\x80\xBF"}, - {"]", "\xEE\x81\x80"}, - {",", "\xEE\x80\x9F"}, - {".", "\xEE\x80\xA0"}, - {"/", "\xEE\x80\xA1"}, -}; -#endif - -// clang-format on - -auto EPubTokenizer::searchStart(pugi::xml_node node) -> uint32_t { - uint32_t offset = 0; - if ((node.type() == pugi::xml_node_type::node_pcdata) && - !EPubTokenizer::isSpaces(node.value())) { - // log_w("[%s]...", node.value()); - const pugi::char_t *v = node.value(); - offset = node.offset_debug(); - while ((*v != '\0') && isspace(*v)) { - v++; - offset++; - } - if (*v != '\0') { - return offset; - } - } else if (node.type() == pugi::xml_node_type::node_element) { - // log_w("<%s>...", node.name()); - auto tagIt = tags.find(node.name()); - if (tagIt != tags.end()) { - switch (tagIt->second) { - case Tag::PICTURE: - return node.offset_debug(); - case Tag::SUP: - case Tag::SUB: - return 0; - default: - break; - } - } - - for (auto subNode : node.children()) { - offset = searchStart(subNode); - if (offset != 0) { - return offset; - } - } - } - return 0; -} - -// Find the offset where the first element to be put on a page is located. -auto EPubTokenizer::computeStartingOffset(StreamPlace streamPlace) -> uint32_t { - - pugi::xml_document &doc = loadXHTMLAt(streamPlace.getSpineIdx()); - - uint32_t offset = 0; - for (auto node : doc.child("html").child("body").children()) { - offset = searchStart(node); - if (offset != 0) { - break; - } - } - return offset == 0 ? END_OFFSET : offset; -} - -// Checks if a node is empty (i.e. there is no displayable -// character and no picture node in it), return true if so. -auto EPubTokenizer::isEmpty(pugi::xml_node node) -> bool { - - struct Walker : pugi::xml_tree_walker { - auto for_each(pugi::xml_node &node) -> bool override { - if (node.type() == pugi::xml_node_type::node_pcdata) { - if (!EPubTokenizer::isSpaces(node.value())) { - empty = false; - return false; - } - } else if ((node.type() == pugi::xml_node_type::node_element) && - (strcmp(node.name(), "span") != 0)) { - empty = false; - return false; - } - return true; - } - - bool empty{}; - } walker; - - walker.empty = true; - node.traverse(walker); - - return walker.empty; -} - -// goBackward() is used in the context of backwards page displacement. For an EPub -// document, it is rather complex. If the current place is at the first displayable character -// of an XHTML file, we have to move to the preceding file, at the beginning of it. -auto EPubTokenizer::goBackward(StreamPlace streamPlace, uint32_t offsetToSubtract) -> StreamPlace { - - uint32_t fileStartingOffset = computeStartingOffset(streamPlace); - - REND_WLOG("StreamPlace Offset: %" PRIu32 " vs fileStartingOffset: %" PRIu32, - streamPlace.getOffset(), fileStartingOffset); - - if (streamPlace.getOffset() <= fileStartingOffset) { - if (streamPlace.getSpineIdx() > 0) { - return {0, static_cast(streamPlace.getSpineIdx() - 1)}; - } else { - return streamPlace; // don't move - } - } else { - return {0, streamPlace.getSpineIdx()}; - } -} - -auto EPubTokenizer::toUtf8(uint32_t val) -> char * { - char *s = utf8Ch_; - if (val <= 0x7F) { - // Plain ASCII - *s++ = static_cast(val); - *s = 0; - } else if (val <= 0x07FF) { - // 2-byte unicode - *s++ = static_cast(((val >> 6) & 0x1F) | 0xC0); - *s++ = static_cast(((val >> 0) & 0x3F) | 0x80); - *s = 0; - } else if (val <= 0xFFFF) { - // 3-byte unicode - *s++ = static_cast(((val >> 12) & 0x0F) | 0xE0); - *s++ = static_cast(((val >> 6) & 0x3F) | 0x80); - *s++ = static_cast(((val >> 0) & 0x3F) | 0x80); - *s = 0; - } else if (val <= 0x10FFFF) { - // 4-byte unicode - *s++ = static_cast(((val >> 18) & 0x07) | 0xF0); - *s++ = static_cast(((val >> 12) & 0x3F) | 0x80); - *s++ = static_cast(((val >> 6) & 0x3F) | 0x80); - *s++ = static_cast(((val >> 0) & 0x3F) | 0x80); - *s = 0; - } else { - // error - use replacement character - *s++ = static_cast(0xEF); - *s++ = static_cast(0xBF); - *s++ = static_cast(0xBD); - *s = 0; - } - return utf8Ch_; -} - -// From the string that follow the '&' character, retrieve the entity's UTF8 or the unicode UTF8 -// corresponding to the decimal (&#...;) or hexadecimal number (&#x...;). It returns the -// corresponding UTF8 translation and the next position in the input string. -auto EPubTokenizer::getCharEntity(const char *str) -> std::pair { - unsigned int len = 0; - bool number = *str == '#'; - if (number) { - str++; - } - bool hexNumber = *str == 'x'; - if (hexNumber) { - str++; - } - const char *start = str; - while ((len++ < 10) && (*str != '\0') && (*str != ';')) { - str++; - } - std::string theStr = std::string(start, str - start); - if (*str == ';') { - str++; - if (hexNumber) { - return {toUtf8(strtol(theStr.c_str(), nullptr, 16)), str}; - } else if (number) { - return {toUtf8(strtol(theStr.c_str(), nullptr, 10)), str}; - } else { - auto search = entities.find(static_cast(theStr)); - if (search != entities.end()) { - return {search->second, str}; - } - } - } - - return {"", str}; -} - -auto EPubTokenizer::loadXHTMLAt(Idx spineIdx) -> pugi::xml_document & { - const EPubOpf::SpineItem &spineItem = stream_->getSpine(spineIdx); - return stream_->getXHTMLFile(static_cast(spineItem.item->href)); -} - -#if 0 -// Keep it just in case it is required again... -// -// Find the next offset where the first character/picture will come from for the next page. This is -// required to synchronize a backward search: The last location of a page could be at a different -// location as the first location of next page because of xml in-between elements - -auto EPubTokenizer::forwardToNextPageOffset(pugi::xml_node node) const -> uint32_t { - - struct Walker : pugi::xml_tree_walker { - auto for_each(pugi::xml_node &node) -> bool override { - if (started) { - if (node.type() == pugi::xml_node_type::node_pcdata) { - if (!EPubTokenizer::isSpaces(node.value())) { - offset = theStream->getOffset(node.value()); - return false; - } - } - } else { - if (node.type() == pugi::xml_node_type::node_pcdata) { - if (!EPubTokenizer::isSpaces(node.value())) { - uint32_t location = theStream->getOffset(node.value()); - uint32_t length = strlen(node.value()); - if ((location + length) >= offset) { - started = true; - offset = (location >= offset) ? location : (offset + 1); - return false; - } - } - } else if (node.type() == pugi::xml_node_type::node_element) { - uint32_t location = theStream->getOffset(node.name()); - // log_w("Node: %s, location: %" PRIu32, node.name(), location); - if (location >= offset) { - started = true; - } - } - } - return true; - } - - bool started; - uint32_t offset; - std::shared_ptr theStream; - } walker; - - walker.started = false; - walker.offset = fromPlace_.getOffset(); - walker.theStream = stream_; - - node.parent().traverse(walker); - - uint32_t theOffset = walker.started ? walker.offset : END_OFFSET; - - REND_WLOG("Computed offset: %" PRIu32 ", search from offset: %" PRIu32, theOffset, - fromPlace_.getOffset()); - - return theOffset; -} - -#endif - -// Tokenize the document (through the Tokens class instance) and arrange the stream of tokens in -// lines and pages (through the Pager class). -// -// Can deal with forward and backward displacements. Page breaks are done between xhtml files -// that are commonly separated into chapters. -// -// As the XML parser generates a DOM like structure of a xhtml file, every call to this method -// will start from the beginning of the DOM toward the end of the targetted page. -// -// For forward displacements, the fromPlace argument is the location where the page will start. -// The algorithm will go as quick as possible to that location and start tokenizing there. -// -// For backward displacements, parsing starts at the beginning of the DOM and will end when the -// toPlace argument is reached (it is the start of the following page). -// -// ---- -// -// Here are the elements that constitute the global state of the tokenizer. This state must be -// maintained up to date in both forward and backward displacements. The objectives are -// -// 1) To control when it's time to start creating tokens -// -// 2) To permit the page to be displayed to have proper indentation -// -// 3) To limit empty vertical space at beginning of pages -// -// 4) To reconduct the last token not part of the preceding page -// -// As the process of generating a page uses a one token look-ahead approach, reconduct the -// last token processed by the pager at the end of the preceding page to be the first one on -// the new page -// -// 5) To have header paragraphs (

      ..

      ) not being orphaned -// -// Ensure that headers are displayed on the same page of the following first -// line of text. A TokenId::HTEXT will be used for the first header word. -// -// 6) To have spaces properly added between text tokens (words) -// -// 7) To have supercript/subscript properly processed -// -// As superscripts/subscripts consitute special kind of words, the state of displaying them -// must be propagated through the interpreter recursion -// -// 8) To add brackets around hyperlinks -// -// 9) To maintain the current bytes offset in the current chapter -// -// 10) To remove paragraph indentation in tables' rows -// -// 11) To complete last word token in pcdata -// -// The last word present in a string needs to be completed only when it is safe to do so. -// Some formatting tags like , , may cut a word, having an impact of misplaced -// newlines if the first part of a word is found at the end of a line. -// -// 12) When the page to be displayed is ready -// -// 13) If menu parameters must be retrieved or not -// -// ---- - -auto EPubTokenizer::loadAndTokenizePage(StreamPlace fromPlace, bool backwards, StreamPlace *toPlace, - bool retrieveMenuParameters) - -> std::pair { - - // if (stream_->isAnArticle()) log_w("article"); - - // Not pretty, but limits the number of parameters to pass to the interpret() method as it is - // recursively called - fromPlace_ = fromPlace; - toPlace_ = toPlace; - backwards_ = backwards; - - REND_WLOG("Opening file at streamPlace %s, backwards: %s%s", fromPlace.str().c_str(), - (backwards ? "YES, Up to: " : "NO"), - (toPlace == nullptr ? "" : toPlace->str().c_str())); - - // ----> 1) to control when it's time to start creating tokens - - tokenizingStarted_ = backwards; - currentStreamOffset_ = 0; - - // ----> 2) to permit the page to be displayed to have proper indentation - - listLevel_ = -1; - - savedBlockquoteLeftIndent_ = 0; - savedBlockquoteRightIndent_ = 0; - blockquoteLevel_ = -1; - - savedRowIndent_ = 0; - - // ----> 3) to limit empty vertical space at beginning of pages - - beginningOfPage_ = backwards; - - // ----> 4) To reconduct the last token not part of the preceding page - - // ----> 5) To have header paragraphs (

      ..

      ) not being orphaned - - startOfHeader_ = false; - - // ----> 6) To have spaces properly added between text tokens (words) - - lastToken_ = nullptr; // To which token spaces will be attached to (as a count) - - // ----> 7) To have supercript/subscript properly processed - - superscript_ = false; - subscript_ = false; - - // ----> 8) To add brackets around hyperlinks - - preText_[0] = postText_[0] = '\0'; - - // ----> 9) To maintain the current bytes offset in the current chapter - - currentTxtOffset_ = 0; - currentPageTxtOffset_ = 0; - - // ----> 10) To remove paragraph indentation in tables' rows - - inTableRow_ = false; - - // ----> 11) To complete last word token in pcdata - - textToken_ = nullptr; - - // ----> 12) When the page to be displayed is ready - - pageCompleted_ = false; - - // ----> 13) If menu parameters must be retrieved or not - - retrieveMenuParameters_ = retrieveMenuParameters; - - // --- - - if (stream_->isAnArticle()) { - - REND_WLOG("Reading an article..."); - - currentSpineIdx_ = 0; - - REND_PROFILE_START(tokenizeProcess); - - bool completed = false; - - do { - currentStreamOffset_ = 0; - - REND_PROFILE_START(loadXHTMLFile) - pugi::xml_document &doc = loadXHTMLAt(currentSpineIdx_); - REND_PROFILE_END(loadXHTMLFile) - - body_ = doc.child("html").child("body"); - pugi::xml_node node = body_.first_child(); - - if (node) { - do { - if (!interpret(node)) { - completed = true; - } else { - node = node.next_sibling(); - } - } while (node && !completed); - } - - if (!node) { - if (!completed) { - if (++currentSpineIdx_ >= stream_->getSpineCount()) { - if (currentPageTxtOffset_ <= currentTxtOffset_) { - currentTxtOffset_ -= currentPageTxtOffset_; - } else { - currentTxtOffset_ = 0; - } - REND_WLOG("End of Article, end of page: currentTxtOffset: %" PRIu32 - ", currentPageTxtOffset: %" PRIu32, - currentTxtOffset_, currentPageTxtOffset_); - currentPageTxtOffset_ = 0; - pager_.setEndOfFile(); - break; - } - } else { - if ((fromPlace.getSpineIdx() + 1) < stream_->getSpineCount()) { - pager_.setNextPagePlace( - {0, static_cast(fromPlace.getSpineIdx() + 1)}); - } - } - } - - } while (!completed); - - REND_PROFILE_END(tokenizeProcess); - - } else { - - REND_WLOG("Reading a book..."); - - currentSpineIdx_ = stream_->getChapterFirstSpineIdx(fromPlace.getSpineIdx()); - - REND_PROFILE_START(tokenizeProcess); - - bool completed = false; - - do { - currentStreamOffset_ = 0; - - REND_PROFILE_START(loadXHTMLFile) - pugi::xml_document &doc = loadXHTMLAt(currentSpineIdx_); - REND_PROFILE_END(loadXHTMLFile) - - body_ = doc.child("html").child("body"); - pugi::xml_node node = body_.first_child(); - - if (node) { - do { - if (!interpret(node)) { - completed = true; // The page is complete - } else { - node = node.next_sibling(); - } - } while (node && !completed); - } - - if (!node) { - // We are at the end of the current file - // Pointing at next file, we will try to complete the current - // page if it was not completely processed by the current file. - currentSpineIdx_ += 1; - - // We break the process of paging if - // - we are passed the end of the book - // - the new current file is the beginning of a chapter - if (stream_->spineIdxIsChapterStart(currentSpineIdx_)) { - log_w("We are at the beginning of a new chapter..."); - } - if (!completed) { - if ((currentSpineIdx_ >= stream_->getSpineCount()) || - (stream_->spineIdxIsChapterStart(currentSpineIdx_))) { - if (currentSpineIdx_ >= stream_->getSpineCount()) { - if (currentPageTxtOffset_ <= currentTxtOffset_) { - currentTxtOffset_ -= currentPageTxtOffset_; - } else { - currentTxtOffset_ = 0; - } - REND_WLOG("End of Chapter, end of page: currentTxtOffset: %" PRIu32 - ", currentPageTxtOffset: %" PRIu32, - currentTxtOffset_, currentPageTxtOffset_); - currentPageTxtOffset_ = 0; - pager_.setEndOfFile(); - break; - // } else { - // pager_.completeLastLine(); - // break; - } else { - completed = true; - pager_.setNextPagePlace({0, currentSpineIdx_}); - } - } - } else { - if (currentSpineIdx_ < stream_->getSpineCount()) { - pager_.setNextPagePlace({0, currentSpineIdx_}); - } - } - } - - } while (!completed); - - REND_PROFILE_END(tokenizeProcess); - } - - REND_WLOG("End of job: Page start: %s, next page at: %s", - pager_.getStreamPlaceOfFirstLine().str().c_str(), - pager_.getNextPageStreamPlace().str().c_str()); - - return {pager_.getStreamPlaceOfFirstLine(), pager_.getNextPageStreamPlace()}; -} - -auto EPubTokenizer::toStreamPlace(const std::shared_ptr place) const -> StreamPlace { - if (place->isFor(DocType::EPUB)) { - std::shared_ptr ePubPlace = std::dynamic_pointer_cast(place); - int spineIdx = stream_->getSpineIdx(ePubPlace->getHRef()); - if (spineIdx == -1) { - return getPlaceAtBeginningOfDocument(); - } - return {place->getOffset(), spineIdx}; - } else { - log_e("Wrong Place Type: %" PRIu8, static_cast(place->getDocType())); - REND_EXIT_SIM; - } - - return {}; -} - -void EPubTokenizer::processPictureTag(pugi::xml_node node) { - - pugi::xml_node source = node.find_child_by_attribute("source", "class", "dithered"); - - if (source) { - Token &pictureToken = tokens_.getNextAvailable(); - pictureToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - pictureToken.tokenId = TokenId::PICTURE; - - std::string pictureFilePath = stream_->getRelativeFilePath( - source.attribute("srcset").value(), fromPlace_.getSpineIdx()); - tokens_.addStr(pictureToken, pictureFilePath); - - pictureToken.pixWidth = pictureToken.pixHeight = 0; - - // Optimization: if contains the width and height, the picture will - // be loaded at the page generation time. - if (source.attribute("width") && source.attribute("height")) { - pictureToken.pixWidth = strtol(source.attribute("width").value(), nullptr, 10); - pictureToken.pixHeight = strtol(source.attribute("height").value(), nullptr, 10); - pictureToken.picture = nullptr; - } else { - REND_ELOG("Loading picture at the wrong moment!"); - - REND_PROFILE_START(ReadPictureFromEPub); - auto [buffer, len] = stream_->getFile(pictureFilePath); - REND_PROFILE_END(ReadPictureFromEPub); - - if (buffer != nullptr) { - pictureToken.picture = std::make_shared(); - if (!pictureToken.picture->readFromBytes(buffer.get(), len)) { - log_e("Unable ro retrieve the BMP picture from EPub"); - REND_EXIT_SIM; - } else { - pictureToken.pixHeight = pictureToken.picture->getHeight(); - pictureToken.pixWidth = pictureToken.picture->getWidth(); - } - } else { - REND_ELOG("buffer is empty!"); - } - } - - if (((pictureToken.pixHeight + - pager_.deciToPix(NEWLINE_DECI_LINE_HEIGHT + ENDPARAGRAPH_DECI_LINE_HEIGHT))) < - rect_.height) { - pictureToken.tokenId = TokenId::EPICTURE; - } - - lastToken_ = nullptr; - - // After the following call, pictureToken may become irrelevant - addTokenToPage(pictureToken); - REND_WLOG("After picture processing, pageCompleted_: %s", pageCompleted_ ? "Yes" : "No"); - } -} - -// Start a new page. As the token parameter is a reference into the tokens_[] array that was not -// injected in last page, we need to take a copy of it to re-inject into the tokens_[] after it -// was cleared (the token parameter was not accepted to be part of the previous page, so we need -// to keep it alive in the next page to come). This must also take care of the cStr pool pointer -// unless a str parameter is present, meaning a constant string that is outside of the pool. -void EPubTokenizer::startNewPageWithToken(Token &token, const char *str, uint8_t strSize) { - - Token tempToken; - std::string tempStr; - std::shared_ptr picture = std::move(token.picture); - if (str == nullptr && (token.length != 0)) { - tempStr = token.cStr; - token.cStr = nullptr; - token.length = 0; - } - tempToken = token; - tokens_.clear(); - pager_.clear(); - - updatePagerIndent(tempToken.tokenId == TokenId::ITEXT); - - Token &newToken = tokens_.getNextAvailable(); - newToken = tempToken; - newToken.picture = std::move(picture); - - // lastToken_ must have been properly set BEFORE a call to this method. - if (lastToken_ != nullptr) { - lastToken_ = &newToken; - } - if (!tempStr.empty()) { - tokens_.addStr(newToken, tempStr); - } - - // Recursive call - // After the following call, newToken may become irrelevant - addTokenToPage(newToken, str, strSize); -} - -// Integrates the token to the tokens_[] array and to the page. -// -// Returns true if the page is ready. -// -// Takes into account the need, in the context of backward movements, to wait until the target -// location is reached. If the target location wasn't reached and the pager_ returns with a -// completed page signal (pager_.addLastTokenToPage(false) returns false), the last token -// entered is saved to be pushed back in the tokens_ stream. Note that the token parameter may -// then become irrelevant as it could have been moved (very high probability) through the -// process of initializing the objects for next page preparation. -auto EPubTokenizer::addTokenToPage(Token &token, const char *str, uint8_t strSize) -> bool { - if (tokens_.add(token, str, strSize)) { - // log_w("Token Id: %" PRIu8 " at idx: %" PRIu16, (uint8_t)token.tokenId, - // tokens_.getLastIdx()); - if (tokens_.getTokenCount() == 1) { - if (!backwards_) { - // This is the first token added, so we are at the beginning of a page - updatePagerIndent(token.tokenId == TokenId::ITEXT); - } else { - currentPageTxtOffset_ = token.length; - } - } - - if (!pager_.addLastTokenToPage(false)) { - if ((static_cast(token.tokenId) & - (static_cast(TokenId::NEWLINE) | - static_cast(TokenId::ENDPARAGRAPH))) == 0) { - if (backwards_) { - if (token.streamPlace >= *toPlace_) { - // So we are at the location corresponding to the end of the requested - // page. - pageCompleted_ = true; - if (currentPageTxtOffset_ <= currentTxtOffset_) { - currentTxtOffset_ -= currentPageTxtOffset_; - } else { - currentTxtOffset_ = 0; - } - REND_WLOG("Backward end of page: currentTxtOffset: %" PRIu32 - ", currentPageTxtOffset: %" PRIu32, - currentTxtOffset_, currentPageTxtOffset_); - currentPageTxtOffset_ = 0; - } else { - startNewPageWithToken(token, str, strSize); - } - } else { - pageCompleted_ = true; - REND_WLOG("Forward end of page: currentTxtOffset: %" PRIu32 - ", currentPageTxtOffset: %" PRIu32, - currentTxtOffset_, currentPageTxtOffset_); - } - } - } else { - beginningOfPage_ = false; // pager_.linesIsEmpty() && tokens_.isEmpty(); - } - } else { - - log_e("Internal error. Tokens::tokens_[] circular array not expected to be full. Not " - "large " - "enough!"); - REND_EXIT_SIM; - - pageCompleted_ = true; - } - return pageCompleted_; -} - -// pugixml doesnt supply the length of a tag in the buffer or the end-tag location. To find the -// offset, we have to find the next node that follow the one we are interested in through the -// sibling. If none exists, we have to go up in the chain of parents in the tree. -auto EPubTokenizer::findNodeEndOffset(pugi::xml_node node) -> uint32_t { - while ((node == node.parent().last_child()) && (node != body_)) { - node = node.parent(); - } - uint32_t offset = (node == body_) ? END_OFFSET : node.next_sibling().offset_debug() - 1; - return offset; -} - -// Adjust pager indentation -void EPubTokenizer::updatePagerIndent(bool iText) { - if (listLevel_ == -1) { - pager_.setLineIndent(savedBlockquoteLeftIndent_ + savedRowIndent_, - savedBlockquoteRightIndent_); - // } else if (iText) { - // if (listLevel_ > 0) { - // pager_.setLineIndent(listIndent_[listLevel_ - 1] + savedBlockquoteLeftIndent_ + - // savedRowIndent_, - // savedBlockquoteRightIndent_); - // } else { - // pager_.setLineIndent(savedBlockquoteLeftIndent_ + savedRowIndent_, - // savedBlockquoteRightIndent_); - // } - } else { - if (iText) { - pager_.setLineIndent(listIndent_[listLevel_] + savedBlockquoteLeftIndent_ + - savedRowIndent_, - savedBlockquoteRightIndent_); - } else { - pager_.setLineIndent(listIndent_[listLevel_] + listUndent_[listLevel_] + - savedBlockquoteLeftIndent_ + savedRowIndent_, - savedBlockquoteRightIndent_); - } - } -} - -// Retrieve menu parameters of the following form: -// -//
      • value
      • ...
      -// -// Returns false if no valid menu parameter present. -auto EPubTokenizer::processMenuParameters(pugi::xml_node node) -> bool { - // We know we got a div node, so we are looking for a ul node - // Iterate from the first child of the div node until we find a pugi::node_element - // (node with the name "ul") - while ((node.type() != pugi::node_element) && (node != body_)) { - node = node.next_sibling(); - } - - if (node == body_) { - return false; - } - - auto tagIt = tags.find(node.name()); - - if (tagIt->second != Tag::UL) { - return false; - } - - bool atLeastOnce = false; - - for (pugi::xml_node liNode : node.children("li")) { - const pugi::char_t *key = liNode.attribute("id").value(); - const pugi::xml_text text = liNode.text(); - if ((key != nullptr) && !text.empty()) { - atLeastOnce = true; - pager_.addMenuParameter(key, text.as_string()); - } - } - - return atLeastOnce; -} - -// Process node content, going recursively for all its parts. Returns true if parsing must -// continue, false if its time to stop (We are at the end of a page (going forward) or the -// toPlace_ has been reached (going backward)) -auto EPubTokenizer::interpret(pugi::xml_node node) -> bool { - // If tags are nested more than ~50 tags deep we will run out of stack. Proactively crash so - // it's easier to diagnose next time. - if (xTaskGetCurrentTaskHandle() != nullptr) { - assert(uxTaskGetStackHighWaterMark(nullptr) >= 128 && - "Stack low in EPubTokenizer::interpret"); - } - - currentStreamOffset_ = node.offset_debug(); - - if (node.type() == pugi::node_element) { - - // log_w("Interpreting node element <%s> at offset %" PRIu32, node.name(), - // currentStreamOffset_); - - if (!tokenizingStarted_) { - if (((currentSpineIdx_ == fromPlace_.getSpineIdx()) && - (currentStreamOffset_ >= fromPlace_.getOffset())) || - (currentSpineIdx_ > fromPlace_.getSpineIdx())) { - tokenizingStarted_ = true; - beginningOfPage_ = true; - REND_WLOG("Started at [offset:%" PRIu32 ",spineIdx:%" PRIu16 "]", - currentStreamOffset_, currentSpineIdx_); - } - } - - auto tagIt = tags.find(node.name()); - - // if (tagIt != tags_.end()) { - // log_e("Tag: %d (%s) at offset %" PRIu32, (int)tag, tagIt->first.c_str(), - // currentStreamOffset_); - // } - - // Start of a node element processing - - if (tagIt != tags.end()) { - - if ((tagIt->second == Tag::DIV) && - (strcmp(node.attribute("id").value(), "sol-menu") == 0)) { - if ((!backwards_) && tokenizingStarted_ && retrieveMenuParameters_ && - processMenuParameters(node.first_child())) { - return false; - } - return interpret(node.next_sibling()); - } - - switch (tagIt->second) { - case Tag::H1: - case Tag::H2: - case Tag::H3: - case Tag::H4: - case Tag::H5: - case Tag::H6: - startOfHeader_ = true; - [[fallthrough]]; - case Tag::P: - case Tag::DIV: - case Tag::TD: - case Tag::TH: - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - if (isEmpty(node)) { - startOfHeader_ = false; - return true; - } - break; - - case Tag::BLOCKQUOTE: - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - - blockquoteLevel_ += 1; - savedBlockquoteLeftIndent_ += (blockquoteLevel_ == 0) ? 12 : 8; - savedBlockquoteRightIndent_ += 8; - - updatePagerIndent(false); - break; - - case Tag::PICTURE: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - // log_w("Picture found..."); - currentStreamOffset_ = node.offset_debug(); - processPictureTag(node); - return !pageCompleted_; - } - currentStreamOffset_ = findNodeEndOffset(node); - return true; // skip children - - case Tag::SUP: - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - superscript_ = true; - break; - - case Tag::SUB: - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - subscript_ = true; - break; - - case Tag::OL: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - - if (listLevel_ >= 0) { - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::NEWLINE; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - } - - if (listLevel_ < (MAX_LEVEL_COUNT - 2)) { - listLevel_ += 1; - listType_[listLevel_] = ListType::ORDERED; - pugi::xml_attribute attr = node.attribute("start"); - listSeqNbr_[listLevel_] = attr ? strtol(attr.value(), nullptr, 10) : 1; - listIndent_[listLevel_] = 0; // To be computed in
    1. processing - } - break; - - case Tag::UL: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (listLevel_ >= 0) { - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::NEWLINE; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - } - - if (listLevel_ < (MAX_LEVEL_COUNT - 2)) { - listLevel_ += 1; - listType_[listLevel_] = ListType::UNORDERED; - listSeqNbr_[listLevel_] = 1; - listIndent_[listLevel_] = 0; // To be computed in
    2. processing - } - break; - - case Tag::LI: { - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - - auto listLineStart = std::make_unique(50); - - if (listLevel_ >= 0) { - if (listType_[listLevel_] == ListType::ORDERED) { - pugi::xml_attribute attr = node.attribute("value"); - int32_t seqNbr = - attr ? strtol(attr.value(), nullptr, 10) : listSeqNbr_[listLevel_]; - (void)snprintf(listLineStart.get(), 49, "%" PRIi32 ". ", seqNbr); - listSeqNbr_[listLevel_] = ++seqNbr; - } else { - (void)snprintf(listLineStart.get(), 49, "%c ", LEVEL_CHAR[listLevel_]); - } - listIndent_[listLevel_] = (listLevel_ * 15) + 5; - listUndent_[listLevel_] = font_->getTextWidth(listLineStart.get()); - } - - if (tokenizingStarted_ && (listLevel_ >= 0)) { - updatePagerIndent(true); - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::ITEXT; - - tokens_.addStr(newToken, listLineStart.get()); - lastToken_ = nullptr; - - if (addTokenToPage(newToken)) { - return false; - } - } - } break; - - case Tag::A: { - const pugi::char_t *v = node.child_value(); - if ((v[0] != '\0') && tokenizingStarted_ && closeTextToken()) { - return false; - } - if (!superscript_ && !subscript_ && (v[0] != '\0') && (v[0] != '(') && - (v[0] != '[') && (v[0] != '<') && (v[0] != '{')) { - preText_[0] = '['; - postText_[0] = ']'; - preText_[1] = postText_[1] = '\0'; - } - } break; - - case Tag::TABLE: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::BTABLE; - - lastToken_ = nullptr; - - if (addTokenToPage(newToken, "{TABLE}", 7)) { - return false; - } - } - break; - - case Tag::TR: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::HRULE; - - lastToken_ = nullptr; - - if (addTokenToPage(newToken)) { - return false; - } - } - inTableRow_ = true; - break; - - default: - break; - } - } - - // Recurse on all children elements - - for (pugi::xml_node subNode : node.children()) { - if (!interpret(subNode)) { - return false; - } - } - - // End of the node element processing - - currentStreamOffset_ = findNodeEndOffset(node); - // REND_WLOG("Offset at the end of the node: %s = %" PRIu32, node.name(), - // currentStreamOffset_); - - if (!tokenizingStarted_) { - if (((currentSpineIdx_ == fromPlace_.getSpineIdx()) && - (currentStreamOffset_ >= fromPlace_.getOffset())) || - (currentSpineIdx_ > fromPlace_.getSpineIdx())) { - tokenizingStarted_ = true; - beginningOfPage_ = true; - REND_WLOG("Started at [offset:%" PRIu32 ",spineIdx:%" PRIu16 "]", - currentStreamOffset_, currentSpineIdx_); - } - } - - if (!pageCompleted_) { - if (tagIt != tags.end()) { - switch (tagIt->second) { - case Tag::H1: - case Tag::H2: - case Tag::H3: - case Tag::H4: - case Tag::H5: - case Tag::H6: - startOfHeader_ = false; // Needed if going forward - [[fallthrough]]; - case Tag::P: - case Tag::DIV: - case Tag::HEADER: - case Tag::TD: - case Tag::TH: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = - inTableRow_ ? TokenId::NEWLINE : TokenId::ENDPARAGRAPH; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - break; - - case Tag::BLOCKQUOTE: - savedBlockquoteLeftIndent_ = - (savedBlockquoteLeftIndent_ > 0) - ? savedBlockquoteLeftIndent_ - ((blockquoteLevel_ == 0) ? 12 : 8) - : 0; - savedBlockquoteRightIndent_ = - (savedBlockquoteRightIndent_ > 0) ? savedBlockquoteRightIndent_ - 8 : 0; - - if (blockquoteLevel_ >= 0) { - blockquoteLevel_--; - } - - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = - inTableRow_ ? TokenId::NEWLINE : TokenId::ENDPARAGRAPH; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - updatePagerIndent(false); - break; - - case Tag::SUP: - superscript_ = false; - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - break; - - case Tag::SUB: - subscript_ = false; - if (tokenizingStarted_ && closeTextToken()) { - return false; - } - break; - - case Tag::BREAK: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - // put a SPACE - if (lastToken_ != nullptr) { - lastToken_->spacesAfter++; - if (lastToken_->spacesAfter == 1) { - pager_.addSpaceWidth(); - } - } - } - break; - - case Tag::HR: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::HRULE; - - lastToken_ = nullptr; - if (addTokenToPage(newToken, "\n", 1)) { - return false; - } - } - break; - - case Tag::OL: - if (listLevel_ >= 0) { - listLevel_ -= 1; - } - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::ENDPARAGRAPH; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - updatePagerIndent(false); - break; - - case Tag::UL: - if (listLevel_ >= 0) { - listLevel_ -= 1; - } - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::ENDPARAGRAPH; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - updatePagerIndent(false); - break; - - case Tag::LI: - // log_w("
    3. "); - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - if (!beginningOfPage_) { - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::NEWLINE; - - lastToken_ = nullptr; - - addTokenToPage(newToken, "\n", 1); - } - } - break; - - case Tag::A: - if (tokenizingStarted_) { - if ((postText_[0] != '\0') && closeTextToken(true)) { - return false; - } - // if (postText_[0] != '\0') { - // Token &token = tokens_.getLast(); - // tokens_.insertBytesAtEnd(token, postText_); - // } - } - preText_[0] = postText_[0] = '\0'; - break; - - case Tag::TABLE: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {currentStreamOffset_, currentSpineIdx_}; - newToken.tokenId = TokenId::ETABLE; - - lastToken_ = nullptr; - - if (addTokenToPage(newToken, "{END TABLE}", 11)) { - return false; - } - } - break; - - case Tag::TR: - if (tokenizingStarted_) { - if (closeTextToken()) { - return false; - } - } - inTableRow_ = false; - break; - - default: - break; - } - } - } else { - return false; - } - } else if (node.type() == pugi::node_pcdata) { - // log_w("Interpreting pcdata '%s'", node.value()); - - const pugi::char_t *str = node.value(); - uint32_t len = strlen(str); // don't like that. Lenght not available from pugixml... - // Count forward the number of non-tag bytes (for estimating page number) - // currentTxtOffset_ = 0; - - if (!tokenizingStarted_) { - if (((currentSpineIdx_ == fromPlace_.getSpineIdx()) && - ((currentStreamOffset_ + len) >= fromPlace_.getOffset())) || - (currentSpineIdx_ > fromPlace_.getSpineIdx())) { - tokenizingStarted_ = true; - beginningOfPage_ = true; - uint32_t bypass = (fromPlace_.getOffset() > currentStreamOffset_) - ? fromPlace_.getOffset() - currentStreamOffset_ - : 0; - - REND_WLOG("Starting at %" PRIu32 ", bypass: %" PRIu32 ", length: %" PRIu32, - currentStreamOffset_ + bypass, bypass, len); - - currentStreamOffset_ += bypass; - if (str[bypass] == '\0') { - preText_[0] = postText_[0] = '\0'; - currentTxtOffset_ += len; - return true; - } else { - if (bypass > 0) { - preText_[0] = '\0'; - } - currentTxtOffset_ += bypass; - return addStrToPage(str + bypass, len - bypass); - } - } else { - currentStreamOffset_ += len; - currentTxtOffset_ += len; - } - } else { - return addStrToPage(str, len); - } - } - - return true; -} - -auto EPubTokenizer::addStrToPage(const pugi::char_t *str, uint32_t len) -> bool { - - uint32_t streamOffset = currentStreamOffset_; - - // REND_WLOG("Started at offset: %" PRIu32, streamOffset); - - uint8_t curChar = *str++; - len--; - - // Keep it DRY - - auto nextByte = [&curChar, &str, &len, &streamOffset, this]() -> void { - if (len == 0) { - curChar = 0; - } else { - curChar = *str++; - len--; - streamOffset++; - if (backwards_) { - currentTxtOffset_++; - currentPageTxtOffset_++; - } - } - }; - - if (beginningOfPage_) { - while ((curChar != 0) && isspace(curChar)) { - nextByte(); - } - } - - if (curChar != 0) { - beginningOfPage_ = false; - } - - // REND_WLOG("First Character: %c (%02" PRIX8 "H)", - // (((curChar >= ' ') && (curChar < 127)) ? curChar : ' '), curChar); - - while (curChar != 0) { - - // REND_WLOG("Set the stream place to %s", - // addOffset(fromPlace, streamOffset).str().c_str()); - // REND_WLOG("Current Character: %c (%02" PRIX8 "H)", - // (((curChar >= ' ') && (curChar < 127)) ? curChar : ' '), curChar); - -#if CONFIG_TINYFONT_IBMF - if (superscript_) { - superscript_ = false; - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {streamOffset, currentSpineIdx_}; - newToken.tokenId = TokenId::TEXT; - - do { - auto chIt = - superscriptCharacters.find(SpiramString(reinterpret_cast(&curChar), 1)); - if (chIt != superscriptCharacters.end()) { - const char *script = chIt->second; - while (*script) { - if (!tokens_.cStrIsFull()) { - tokens_.addByte(newToken, *script++); - } else { - break; - } - } - } - nextByte(); - } while (curChar != 0); - - if (newToken.length > 0) { - tokens_.endOfCStr(); - lastToken_ = &newToken; // Must be done before calling addTokenToPage() - - // After the following call, newToken may become irrelevant - addTokenToPage(newToken); - } - - } else if (subscript_) { - subscript_ = false; - Token &newToken = tokens_.getNextAvailable(); - newToken.streamPlace = {streamOffset, currentSpineIdx_}; - newToken.tokenId = TokenId::TEXT; - - do { - auto chIt = - subscriptCharacters.find(SpiramString(reinterpret_cast(&curChar), 1)); - if (chIt != subscriptCharacters.end()) { - const char *script = chIt->second; - while (*script) { - if (!tokens_.cStrIsFull()) { - tokens_.addByte(newToken, *script++); - } else { - break; - } - } - } - nextByte(); - } while (curChar != 0); - - if (newToken.length > 0) { - tokens_.endOfCStr(); - lastToken_ = &newToken; // Must be done before calling addTokenToPage() - - // After the following call, newToken may become irrelevant - addTokenToPage(newToken); - } - - } else -#endif - if (curChar > ' ') { -#if CONFIG_TINYFONT_TTF - if (superscript_) { - superscript_ = false; - closeTextToken(); - textToken_ = &tokens_.getNextAvailable(); - textToken_->streamPlace = {streamOffset, currentSpineIdx_}; - textToken_->tokenId = TokenId::SUPTEXT; - - } else if (subscript_) { - subscript_ = false; - closeTextToken(); - textToken_ = &tokens_.getNextAvailable(); - textToken_->streamPlace = {streamOffset, currentSpineIdx_}; - textToken_->tokenId = TokenId::SUBTEXT; - - } else -#endif - - if (textToken_ == nullptr) { - textToken_ = &tokens_.getNextAvailable(); - textToken_->streamPlace = {streamOffset, currentSpineIdx_}; - textToken_->tokenId = TokenId::TEXT; - - textCharCount_ = 0; - } - - if ((textToken_->length == 0) && (preText_[0] != '\0')) { - tokens_.addBytes(*textToken_, preText_); - preText_[0] = '\0'; - } - - if (curChar == '-') { - textToken_->tokenId = startOfHeader_ ? TokenId::HTEXT : TokenId::HYPHEN; - startOfHeader_ = false; - tokens_.addByte(*textToken_, '-'); - nextByte(); - closeTextToken(); - } else { - if (startOfHeader_) { - textToken_->tokenId = TokenId::HTEXT; - startOfHeader_ = false; - } - - // Count the number of nbsp in a row, to limit the number of them that - // could be present in a word. The current limit is 4 - const uint8_t nbspCountLimit = 4; - uint8_t nbspCount = 0; - - do { - // log_d("Current Character: %c (%02" PRIX8 "H)", - // (((curChar >= ' ') && (curChar < 127)) ? curChar : ' '), - // curChar); - - if (curChar == '&') { - auto [trans, newStrLocation] = getCharEntity(str); - if (*trans != '\0') { - len -= (newStrLocation - str); - streamOffset += (newStrLocation - str); - if ((static_cast(trans[0]) == 0xC2U) && - (static_cast(trans[1]) == 0xA0U)) { - // That is a nbsp character, limit to 4 in a row - if (nbspCount < nbspCountLimit) { - nbspCount += 1; - if (!tokens_.cStrIsFull()) { - tokens_.addByte(*textToken_, ' '); - textCharCount_ += 1; - } - } - } else { - while (*trans && !tokens_.cStrIsFull()) { - if (*trans > ' ') { - tokens_.addByte(*textToken_, *trans); - } - trans++; - } - textCharCount_ += 1; - nbspCount = 0; // not a nbsp, reset counter - } - - str = newStrLocation; - nextByte(); - } else { - if (!tokens_.cStrIsFull()) { - tokens_.addByte(*textToken_, curChar); - textCharCount_ += 1; - } else { - break; - } - nbspCount = 0; // not a nbsp, reset counter - nextByte(); - } - } else { - if ((curChar <= 0x7FU) || (curChar >= 0xC0U)) { - textCharCount_ += 1; - } - if (!tokens_.cStrIsFull()) { - if (curChar == 0xC2U) { - nextByte(); - if (curChar == 0xA0U) { - // This is a nbsp character, limit to 4 in a row - if (nbspCount < nbspCountLimit) { - nbspCount += 1; - tokens_.addByte(*textToken_, ' '); - } else { - textCharCount_ -= 1; - } - } else { - tokens_.addByte(*textToken_, 0xC2U); - tokens_.addByte(*textToken_, curChar); - nbspCount = 0; // not a nbsp, reset counter - } - } else { - tokens_.addByte(*textToken_, curChar); - nbspCount = 0; // not a nbsp, reset counter - } - } else { - break; - } - if (curChar == '-') { - nextByte(); - break; - } - nextByte(); - } - } while ((curChar > ' ') && - ((textCharCount_ < maxTextTokenCharSize_) || ((curChar & 0xC0U) == 0x80))); - - // The last test of the while above is to get a complete UTF8 multi-bytes - // character if the maximum word size is reached - - // if we reach the end of the pcdata, we keep the textToken open. It will be - // completed with other pcdata, or by the next tag that is known to require that - // all remainig text was processed. This is the case of all the tags that are - // considered by the tokenizer. - if ((curChar != 0) && (textToken_->length > 0)) { - closeTextToken(); - // tokens_.endOfCStr(); - // log_w("Text Token: %s", newToken.cStr); - // After the following call, newToken may become irrelevant - // pageCompleted_ = addTokenToPage(newToken); - } - } - } else if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n')) { - closeTextToken(); - if (lastToken_ != nullptr) { - lastToken_->spacesAfter++; - if (lastToken_->spacesAfter == 1) { - pager_.addSpaceWidth(); - } - // } else { - // log_e("Lost a space...."); - } - nextByte(); - } else { - nextByte(); - } - - if (pageCompleted_) { - break; - } - } - - // if (!pageCompleted_ && tokens_.cStrIsFull()) { - // // There is a potential that the last token is not complete (e.g. a TEXT or ENDASH or - // // EMDASH), so forget it - // tokens_.forgetLast(); - // } - - // REND_WLOG("Computed Word Count: %" PRIu16, pager_.getWordCount()); - // REND_WLOG("Tokens Count: %" PRIu16, tokens_.getTokenCount()); - - currentStreamOffset_ = streamOffset + 1; - - if constexpr (REND_DEBUG) { - if (tokens_.isFull()) { - REND_ELOG("Tokens_[] is full"); - } - } - - return !pageCompleted_; -} diff --git a/src/Renderers/EPubTokenizer.hpp b/src/Renderers/EPubTokenizer.hpp deleted file mode 100644 index 0aad68c08..000000000 --- a/src/Renderers/EPubTokenizer.hpp +++ /dev/null @@ -1,293 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "EPub/EPubFile.hpp" -#include "Tokenizer.hpp" - -// EPubTokenizer -// -// This class is responsible of transforming the content of a XHTML file into a stream of tokens, -// calling the Pager class when appropriate to transform the stream to a page displayable on the Sol -// Screen. -// -// For eBooks, chapters (that is, individual entries/XHTML files in the ePub opf's Spine structure) -// are always starting on a new blank page. For articles, there is no such distinction -// -// A subset of the HTML Tags is exploited by the tokenizer to render the document in a reasonable -// time scale. No CSS is present in the ePub (all CSS was removed by the server), the application -// uses some heuristic to decide how to present the information to the user. Some of these -// heuristics are described below. -// -// The XML parser in use is PugiXML (https://pugixml.org/). it's a small/fast parser written in C++ -// that supply a tree structured projection of the document, with a variaty of methods that permits -// navigating the tree efficiently. -// -// To accomplish it's task, the tokenizer always start it's duty from the begining of the XHTML -// file. The main entry point is the loadAndTokenizePage(). It is normally called by the Renderer -// class. It is used for two purposes: -// -// - Forward page displacement: Starting at the beginning of the file, the method navigates toward -// the requested *fromPlace* location offset. From there, it starts tokenizing the content -// transmitting the information to the Pager class. The displacement to the target location is -// fast as no tokenizing is done. The *fromPlace*, by convention, is the start of a page. -// -// - Backward page displacement: Starting at the beginning of the file, the method navigate toward -// the requested *toPlace* location offset. That offset is the end location of the page to be -// displayed. As the application doesn't keep a list of page locations, the method tokenizes and -// build pages one at a time until the *toPlace* is reached. The last page built is then the one -// to be displayed. -// -// See the documentation about the tokenizer global state variables in the EPubTokenizer.cpp file. -// -// Some caution about modifying the EPub tokenizer: -// -// - Always respect the ONE TOKEN LOOKEHEAD principle: The Pager expects to take the proper action -// to -// format the current page using only one token look-ahead. You will not get a good result if more -// than one token is used to format information at a specific offset in the XHTML input file. -// -// The only case that is allowed to bypass this rule is when sending NEWLINE and ENDPARAGRAPH -// tokens. Those must be included only when not at beginning of a page being constructed. See the -// existing code for examples on how to ensure that using the beginingOfPage_ variable. -// -// - The Tokens class is both responsible of managing the tokens vector and the characters pool. The -// only information that can be sent to the characters pool is the strings built in support of the -// tokens that are defined with the following Token types: TEXT, ITEXT, HTEXT, PICTURE and -// EPICTURE. For these types, characters that are added to the token must use on of the following -// Tokens class methods: -// -// void addByte(Token &token, uint8_t b); -// void addBytes(Token &token, const char *str); -// void addStr(Token &token, const std::string &str); -// void insertBytesAtEnd(Token &token, const char *str); -// -// For the first two methods, the corresponding string created in the pool *must* be closed once -// it is complete with a call to the following Tokens method (will add a null character to the end -// of the string): -// -// inline void endOfCStr(); -// -// -// For all the other token types, the text *must* come from constant expressions and added to the -// token when calling the addTokenToPage() method. -// -// - When creating a new Token, *NEVER* initialize the .cStr and .length to something else than -// nullptr and 0. -// -// TBC - -class EPubTokenizer : public Tokenizer { - -public: - EPubTokenizer(Font *font, DisplayRect &rect, Tokens &tokens, Pager &pager) - : Tokenizer(font, rect, tokens, pager) {} - ~EPubTokenizer() override = default; - - [[nodiscard]] auto loadAndTokenizePage(StreamPlace fromPlace, bool backwards = false, - StreamPlace *toPlace = nullptr, - bool retrieveMenuParameters = false) - -> std::pair override; - - [[nodiscard]] auto toStreamPlace(const std::shared_ptr place) const - -> StreamPlace override; - - [[nodiscard]] inline auto toPlace(const StreamPlace &streamPlace) const - -> std::shared_ptr override { - const EPubOpf::SpineItem &spineItem = stream_->getSpine(streamPlace.getSpineIdx()); - return std::make_shared(streamPlace.getOffset(), - static_cast(spineItem.item->href)); - } - - [[nodiscard]] auto streamSize() const -> size_t override { - return stream_ != nullptr ? stream_->size() : 0; - } - - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - return stream_->isFor(docType); - } - - void setStream(std::shared_ptr stream) override { - stream_ = std::static_pointer_cast(stream); - } - - [[nodiscard]] auto getStream() -> std::shared_ptr override { return stream_; } - - [[nodiscard]] auto computeStartingOffset(StreamPlace streamPlace) -> uint32_t; - - [[nodiscard]] auto isEmpty(pugi::xml_node node) -> bool; - - [[nodiscard]] auto goBackward(StreamPlace streamPlace, uint32_t offsetToSubtract) - -> StreamPlace override; - - [[nodiscard]] inline auto isAnArticle() const -> bool override { - return stream_->isAnArticle(); - } - - [[nodiscard]] inline auto isReady() const -> bool override { - return (stream_ != nullptr) && stream_->isOpen(); - } - - [[nodiscard]] inline auto getTxtOffset() const -> uint32_t override { - return currentTxtOffset_; - } - -private: - std::shared_ptr stream_; - pugi::xml_node body_; - - // clang-format off - - // All the potential tags to do some work on. Not all of them are being processed at this point - // in time. Some cleanup maybe required for the ones that are not used. - enum class Tag : uint8_t { P, LI, BREAK, H1, H2, H3, H4, H5, H6, - B, I, A, IMG, PICTURE, SOURCE, EM, DIV, SPAN, PRE, - BLOCKQUOTE, STRONG, ANY, SUB, SUP, HR, OL, UL, - HEADER, FIGURE, FIGCAPTION, TABLE, TH, TR, TD, THEAD }; - // clang-format on - - using Tags = SpiramMap; - using Entities = SpiramMap; - using Characters = SpiramMap; - - // Hash HTML tags - static Tags tags; - - // Hash XHTML character entities (like  ) and their UTF-8 codification - static Entities entities; - - // Superscript Characters UTF-8 codification - static Characters superscriptCharacters; - - // Subscript Characters UTF-8 codification - static Characters subscriptCharacters; - - // Where the tokenizer will start to parse tokens - StreamPlace fromPlace_; - - // Where it will end in case of backward movements - StreamPlace *toPlace_{}; - bool backwards_{}; - - // true when a if being processed - bool superscript_{}; - - // true when a if being processed - bool subscript_{}; - - // Used to add space qty that follow a token - Token *lastToken_{}; - - // true if we are tokenizing/paging the content - bool tokenizingStarted_{}; - - // true if at beginning of page and no tokens / lines where kept. - bool beginningOfPage_{}; - - // true if the page is complete - bool pageCompleted_{}; - - // used for

      ..

      - bool startOfHeader_{}; - - // The offset where we are tokenizing - uint32_t currentStreamOffset_{}; - uint16_t currentSpineIdx_{}; - - // The byte offset ignoring all tag bytes. - uint32_t currentTxtOffset_ = 0; - - // Only used when going backwards - uint32_t currentPageTxtOffset_ = 0; - - // Not null if there is a text token to be closed. This is required to take into account - // words that could be containing tags like etc. These will be processed - // recursively, the content potentially part of a word. - Token *textToken_{}; - uint16_t textCharCount_{}; - - // If true, menu parameters will be retrieved if present in the page - // They will have the following format: - // - //
      • value
      • ...
      - bool retrieveMenuParameters_{}; - - static constexpr const uint8_t MAX_LEVEL_COUNT = 5; - - enum ListType { ORDERED, UNORDERED }; - uint16_t listSeqNbr_[MAX_LEVEL_COUNT]{}; - ListType listType_[MAX_LEVEL_COUNT]{}; - int8_t listLevel_{}; - - uint16_t listIndent_[MAX_LEVEL_COUNT]{}; - uint16_t listUndent_[MAX_LEVEL_COUNT]{}; - - uint16_t savedBlockquoteLeftIndent_{}; - uint16_t savedBlockquoteRightIndent_{}; - int8_t blockquoteLevel_{}; - - uint16_t savedRowIndent_{}; // table rows; - - bool inTableRow_{}; - - static const constexpr char *LEVEL_CHAR = "-o-o-"; - - // Could eventually be made smaller. - // For now, used only for bracketing tags pcdata - char preText_[21]{}; - char postText_[21]{}; - - // used to transform HTML character entities to their UTF8 char. - char utf8Ch_[6]{}; - auto toUtf8(uint32_t val) -> char *; - - [[nodiscard]] auto getCharEntity(const char *str) -> std::pair; - [[nodiscard]] auto loadXHTMLAt(Idx spineIdx) -> pugi::xml_document &; - [[nodiscard]] auto interpret(pugi::xml_node node) -> bool; - [[nodiscard]] auto addStrToPage(const pugi::char_t *str, uint32_t len) -> bool; - [[nodiscard]] auto searchStart(pugi::xml_node node) -> uint32_t; - - // [[nodiscard]] auto forwardToNextPageOffset(pugi::xml_node node) const -> uint32_t; - - void processPictureTag(pugi::xml_node node); - void updatePagerIndent(bool iText); - - auto processMenuParameters(pugi::xml_node node) -> bool; - - [[nodiscard]] static inline auto isSpaces(const char *cstr) -> bool { - while (*cstr) { - if (!isspace(*cstr)) { - if ((*cstr != '\xC2') || (*++cstr != '\xA0')) { - return false; - } - } - cstr++; - } - return true; - } - - // Called when it's time to close the last textToken, if there is any waiting to be closed. - // Returns true if page is completed. - auto closeTextToken(bool addPostText = false) -> bool { - if (textToken_ != nullptr) { - if (addPostText && (postText_[0] != '\0')) { - tokens_.addBytes(*textToken_, postText_); - } - tokens_.endOfCStr(); - lastToken_ = textToken_; - addTokenToPage(*textToken_); - textToken_ = nullptr; - return pageCompleted_; - } else { - return false; - } - } - - void startNewPageWithToken(Token &token, const char *str, uint8_t strSize); - - auto addTokenToPage(Token &token, const char *str = nullptr, uint8_t strSize = 0) -> bool; - auto findNodeEndOffset(pugi::xml_node node) -> uint32_t; -}; diff --git a/src/Renderers/NullTokenizer.hpp b/src/Renderers/NullTokenizer.hpp deleted file mode 100644 index 6d43efdca..000000000 --- a/src/Renderers/NullTokenizer.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "Models/DocType.hpp" -#include "Tokenizer.hpp" - -class NullTokenizer : public Tokenizer { - -public: - NullTokenizer(Font *font, DisplayRect &rect, Tokens &tokens, Pager &pager) - : Tokenizer(font, rect, tokens, pager) {} - ~NullTokenizer() override = default; - - [[nodiscard]] auto loadAndTokenizePage(StreamPlace fromPlace, bool backwards = false, - StreamPlace *toPlace = nullptr, - bool retrieveMenuParameters = false) - -> std::pair override { - error(); - return {}; - }; - - // ToDo: To be adjusted related to the upcoming Place definition - [[nodiscard]] auto toStreamPlace(const std::shared_ptr place) const - -> StreamPlace override { - error(); - return {}; - } - - [[nodiscard]] inline auto toPlace(const StreamPlace &streamPlace) const - -> std::shared_ptr override { - error(); - return std::make_shared(); - } - - [[nodiscard]] auto streamSize() const -> size_t override { - error(); - return 0; - } - - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - error(); - return false; - } - - void setStream(std::shared_ptr stream) override { error(); } - - [[nodiscard]] auto getStream() -> std::shared_ptr override { return nullptr; } - -private: - void error() const { - log_e("There is no stream defined for the Renderer"); - REND_EXIT_SIM; - } -}; \ No newline at end of file diff --git a/src/Renderers/Pager.cpp b/src/Renderers/Pager.cpp deleted file mode 100644 index 1875777b3..000000000 --- a/src/Renderers/Pager.cpp +++ /dev/null @@ -1,655 +0,0 @@ -#include "Pager.hpp" - -// Takes the last token entered in the tokens_[] array and include it in the page. -// -// The dontStop parameter is used in the context of the TXT file format when going backward, such -// that lines will be continuously added to the lines_[] circular array. The caller method is then -// responsible to stop the process of adding tokens when the target location is reached. -// -// returns true if there is still some room in the page or dontStop is true, false if the page is -// complete. -auto Pager::addLastTokenToPage(bool dontStop) -> bool { - - Token &token = tokens_.getLast(); - - REND_WLOG("%s...", getTokenIdStr(token.tokenId)); - - // REND_WLOG("[%" PRIu16 "](%" PRIu16 ") tokenIdx:%" PRIu16 ", Id:%" PRIu8 ", cStrPool:%" - // PRIi32, - // linesTailIdx_, lines_[linesTailIdx_].tokenCount, tokens_.getLastIdx(), - // (uint8_t)token.tokenId, tokens_.getCStrPoolCount()); - - switch (token.tokenId) { - case TokenId::TEXT: - case TokenId::ITEXT: - case TokenId::HTEXT: - case TokenId::NTEXT: - -#if CONFIG_TINYFONT_TTF - case TokenId::SUPTEXT: - case TokenId::SUBTEXT: -#endif - - case TokenId::HYPHEN: - case TokenId::ENDASH: // Not used for EPub, used for TXT - case TokenId::EMDASH: // Not used for EPub, used for TXT - { - if (token.tokenId == TokenId::ITEXT) { - leftIndent_ += token.pixWidth; // For next lines - // log_w("IText (%s) new left indent %" PRIu16 ", Current Line Width: %" PRIu16, - // token.cStr, leftIndent_, currentLineWidth_); - } - uint16_t heightRequired = - (token.tokenId == TokenId::HTEXT) - ? (NEWLINE_DECI_LINE_HEIGHT << 1) + ENDPARAGRAPH_DECI_LINE_HEIGHT - : NEWLINE_DECI_LINE_HEIGHT; - if ((token.pixWidth + currentLineWidth_) > (maxLineWidth_ - rightIndent_)) { - // We are at the end of a line - - // log_w("--> End of line, Line width: %" PRIu16, currentLineWidth_); - // Complete the line, passing to the next one - newLine(NEWLINE_DECI_LINE_HEIGHT); - if (!dontStop && ((currentPageDecilineHeight_ + heightRequired) > maxDeciLines_)) { - // The page is complete. The current token will be the first to next page - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - // The page is not complete, the current token is the first to appear on next line - currentLineWidth_ += token.pixWidth; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - lines_[linesTailIdx_].tokenCount++; - lines_[linesTailIdx_].length += token.length; - } else if (lines_[linesTailIdx_].tokenCount == 0) { - if (!dontStop && ((currentPageDecilineHeight_ + heightRequired) > maxDeciLines_)) { - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].length += token.length; - currentLineWidth_ += token.pixWidth; - } else { - currentLineWidth_ += token.pixWidth; - lines_[linesTailIdx_].tokenCount++; - lines_[linesTailIdx_].length += token.length; - } - - if (static_cast(token.tokenId) & - (static_cast(TokenId::TEXT) | static_cast(TokenId::ITEXT) | - static_cast(TokenId::HTEXT) | static_cast(TokenId::NTEXT) -#if CONFIG_TINYFONT_TTF - | static_cast(TokenId::SUBTEXT) | static_cast(TokenId::SUPTEXT) -#endif - )) { - wordCount_ += 1; - } - } break; - - case TokenId::NEWLINE: - tokens_.forgetLast(); - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - break; - - case TokenId::HRULE: { - uint16_t height = 0; - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - height = ENDPARAGRAPH_DECI_LINE_HEIGHT; - } - if (!dontStop && - ((currentPageDecilineHeight_ + height + HRULE_DECI_LINE_HEIGHT) > maxDeciLines_)) { - currentPageDecilineHeight_ += ENDPARAGRAPH_DECI_LINE_HEIGHT; - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(height + HRULE_DECI_LINE_HEIGHT); - } break; - - case TokenId::ENDPARAGRAPH: - if (tokens_.getTokenCount() > 1) { - Token &token = tokens_.getBeforeLast(); - if (token.tokenId == TokenId::ENDPARAGRAPH) { - tokens_.forgetLast(); - return true; - } - } - - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - if (!dontStop && - ((currentPageDecilineHeight_ + ENDPARAGRAPH_DECI_LINE_HEIGHT) > maxDeciLines_)) { - currentPageDecilineHeight_ += ENDPARAGRAPH_DECI_LINE_HEIGHT; - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16 " token at %s", - currentPageDecilineHeight_, maxDeciLines_, token.streamPlace.str().c_str()); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(ENDPARAGRAPH_DECI_LINE_HEIGHT); - break; - - case TokenId::PICTURE: - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - if (!dontStop && (currentPageDecilineHeight_ > 0)) { - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(maxDeciLines_); - break; - - case TokenId::EPICTURE: { - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - // Try to fit the picture and some place for at least one line of caption into the current - // page. The picture will go on next page if this doesn't fit. - - uint16_t deciHeight = pixToDeci(token.pixHeight); - if (!dontStop && ((currentPageDecilineHeight_ + deciHeight + ENDPARAGRAPH_DECI_LINE_HEIGHT + - NEWLINE_DECI_LINE_HEIGHT) > maxDeciLines_)) { - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(deciHeight + 4); - } break; - - case TokenId::BTABLE: { - // Will show the cStr (normally {TABLE}) followed by a newline. Add an ENDPARAGRAPH before - // if not at beginning of a page and the previous line is to be completed. - uint16_t height = NEWLINE_DECI_LINE_HEIGHT; - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - height += ENDPARAGRAPH_DECI_LINE_HEIGHT; - } - if (!dontStop && ((currentPageDecilineHeight_ + height) > maxDeciLines_)) { - currentPageDecilineHeight_ += ENDPARAGRAPH_DECI_LINE_HEIGHT; - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(height); - } break; - - case TokenId::ETABLE: { - // Will show a HRULE followed with the cStr text (normally {END TABLE}) and an ENDPARAGRAPH - // if enough space on page. - uint16_t height = NEWLINE_DECI_LINE_HEIGHT + HRULE_DECI_LINE_HEIGHT; - if (lines_[linesTailIdx_].tokenCount > 0) { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - if (!dontStop && ((currentPageDecilineHeight_ + height) > maxDeciLines_)) { - currentPageDecilineHeight_ += ENDPARAGRAPH_DECI_LINE_HEIGHT; - setNextPagePlace(token.streamPlace); - REND_WLOG("Page completed: %" PRIu16 " decilines. Max: %" PRIu16, - currentPageDecilineHeight_, maxDeciLines_); - return false; - } - lines_[linesTailIdx_].tokenCount = 1; - lines_[linesTailIdx_].tokenIdx = tokens_.getLastIdx(); - newLine(height + ENDPARAGRAPH_DECI_LINE_HEIGHT); - } break; - } - - // log_w("Current line width: %" PRIu16, currentLineWidth_); - return true; -} - -void Pager::newLine(uint16_t height) { - - REND_WLOG("[%" PRIu16 "](%" PRIu16 ") NewLine, first tokenidx %" PRIu16 " %s", linesTailIdx_, - lines_[linesTailIdx_].tokenCount, lines_[linesTailIdx_].tokenIdx, - tokens_[lines_[linesTailIdx_].tokenIdx].streamPlace.str().c_str()); - - currentPageDecilineHeight_ += height; - lines_[linesTailIdx_].deciHeight = height; - - incrLinesTailIdx(); - - currentLineWidth_ = leftIndent_; - lines_[linesTailIdx_] = {0, 0, 0, 0, leftIndent_}; - - // log_w("NewLine left Indent: %" PRIu16, leftIndent_); -}; - -// Called when going back and the end place is found. Their may -// remains a line that was not completed. -void Pager::completeLastLine() { - if (lines_[linesTailIdx_].tokenCount > 0) { - if ((lines_[linesTailIdx_].tokenCount == 1) && - (tokens_[lines_[linesTailIdx_].tokenIdx].tokenId == TokenId::ENDPARAGRAPH)) { - newLine(ENDPARAGRAPH_DECI_LINE_HEIGHT); - } else { - newLine(NEWLINE_DECI_LINE_HEIGHT); - } - } -} - -auto Pager::computeWordCount() const -> uint16_t { - uint16_t wordCount = 0; - if (!linesIsEmpty()) { - Idx lastLineIdx = getLastLineIdx(); - Idx firstTokenIdx = lines_[firstPageLineIdx_].tokenIdx; - Idx lastTokenIdx = - tokens_.addToTokenIdx(lines_[lastLineIdx].tokenIdx, lines_[lastLineIdx].tokenCount); - for (Idx tokenIdx = firstTokenIdx; tokenIdx != lastTokenIdx; - tokens_.nextTokenIdx(tokenIdx)) { - if (static_cast(tokens_[tokenIdx].tokenId) & - (static_cast(TokenId::TEXT) | static_cast(TokenId::ITEXT) | - static_cast(TokenId::HTEXT) | static_cast(TokenId::NTEXT) -#if CONFIG_TINYFONT_TTF - | static_cast(TokenId::SUBTEXT) | static_cast(TokenId::SUPTEXT) -#endif - )) { - wordCount += 1; - } - } - } - return wordCount; -} - -auto Pager::getPageLength() const -> uint16_t { - uint16_t length = 0; - for (Idx lineIdx = firstPageLineIdx_; lineIdx != linesTailIdx_; nextLineIdx(lineIdx)) { - length += lines_[lineIdx].length; - } - return length; -} - -#if CONFIG_TINYFONT_TTF - -// Retrieve a single segment from the linsinfo. superscript and subscripts are cutting lines in -// multiple segments. -auto Pager::getLineSegment(LineInfo &lineInfo, Idx nextTokenIdx, Idx endTokenIdx, - Line &lineSegment) const -> Idx { - lineSegment[0] = 0; - Idx lineIdx = 0; - - Idx tokenIdx = nextTokenIdx; - bool first = true; - while (tokenIdx != endTokenIdx) { - - auto tokenId = tokens_[tokenIdx].tokenId; - - if (!first && - (static_cast(tokenId) & (static_cast(TokenId::SUBTEXT) | - static_cast(TokenId::SUPTEXT))) != 0) { - break; - } - - if ((static_cast(tokenId) & (static_cast(TokenId::NEWLINE) | - static_cast(TokenId::ENDPARAGRAPH))) == - 0) { - if (tokens_[tokenIdx].cStr != nullptr) { - // strcat(line.data(), tokens_[tokenIdx].cStr); - memcpy(lineSegment.data() + lineIdx, tokens_[tokenIdx].cStr, - tokens_[tokenIdx].length); - lineIdx += tokens_[tokenIdx].length; - } - - if (tokens_[tokenIdx].spacesAfter) { - lineSegment[lineIdx++] = ' '; - } - } - - tokens_.nextTokenIdx(tokenIdx); - - if (first && - (static_cast(tokenId) & (static_cast(TokenId::SUBTEXT) | - static_cast(TokenId::SUPTEXT))) != 0) { - break; - } - - first = false; - } - - lineSegment[lineIdx] = 0; - - return tokenIdx; -} - -// Draw a line on screen. A line is composed of potentially multiple segments, some are subscript or -// superscript for which a y offset must be applied. -void Pager::drawSingleLine(LineInfo &lineInfo, int yPos) const { - - Line lineSegment; - int xPos = lineInfo.leftIndent + rect_.x; - yPos += rect_.y; - Idx nextTokenIdx = lineInfo.tokenIdx; - Idx endTokenIdx = tokens_.addToTokenIdx(lineInfo.tokenIdx, lineInfo.tokenCount); - - while (nextTokenIdx != endTokenIdx) { - - auto tokenId = tokens_[nextTokenIdx].tokenId; - - nextTokenIdx = getLineSegment(lineInfo, nextTokenIdx, endTokenIdx, lineSegment); - - int theYPos = yPos; - - if ((static_cast(tokenId) & (static_cast(TokenId::SUBTEXT) | - static_cast(TokenId::SUPTEXT))) != 0) { - - int32_t yOffset = getLineHeightPlusSpacing() / 5; - auto normalHeight = getLineHeightPlusSpacing(); - - font_->setSupSubFontSize(); - - if (tokenId == TokenId::SUBTEXT) { - theYPos += normalHeight - getLineHeightPlusSpacing() + yOffset; - } else { // TokenId::SUPTEXT - theYPos += normalHeight - getLineHeightPlusSpacing() - yOffset; - } - xPos = display_.drawSingleLineOfText(lineSegment.data(), xPos, theYPos, false, font_); - font_->setNormalFontSize(); - } else { - xPos = display_.drawSingleLineOfText(lineSegment.data(), xPos, yPos, false, font_); - } - } -} - -#else - -// Prepare a line to be displayed on screen -void Pager::getLine(LineInfo &lineStruct, Line &line) const { - line[0] = 0; - Idx endTokenIdx = tokens_.addToTokenIdx(lineStruct.tokenIdx, lineStruct.tokenCount); - Idx lineIdx = 0; - for (Idx tokenIdx = lineStruct.tokenIdx; tokenIdx != endTokenIdx; - tokens_.nextTokenIdx(tokenIdx)) { - - auto tokenId = tokens_[tokenIdx].tokenId; - if ((static_cast(tokenId) & (static_cast(TokenId::NEWLINE) | - static_cast(TokenId::ENDPARAGRAPH))) == - 0) { - if (tokens_[tokenIdx].cStr != nullptr) { - // strcat(line.data(), tokens_[tokenIdx].cStr); - memcpy(line.data() + lineIdx, tokens_[tokenIdx].cStr, tokens_[tokenIdx].length); - lineIdx += tokens_[tokenIdx].length; - } - if (tokens_[tokenIdx].spacesAfter) { - line[lineIdx++] = ' '; - } - // for (int i = 0; i < tokens_[tokenIdx].spacesAfter; i++) { - // line[lineIdx++] = ' '; - // } - } - } - line[lineIdx] = 0; -} -#endif - -auto Pager::computeDarkPercent(const uint8_t *bitmap, uint16_t width, uint16_t height) const - -> uint8_t { - static uint8_t pixelsDensity[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, - 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, - 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, - 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, - 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, - 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, - 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, - 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, - 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; - - uint16_t size = ((width + 7) >> 3) * height; - int32_t sum = 0; - for (int i = 0; i < size; i++) { - sum += pixelsDensity[bitmap[i]]; - } - return (sum * 100) / 65536; -} - -// Returns true if page contains picture(s) requiring constrast adjustment (dark pictures) -auto Pager::displayPage(int16_t numSkipLines) -> uint8_t { - - REND_WLOG("First Line Idx: %" PRIu16 ", Last Line Idx: %" PRIu16, firstPageLineIdx_, - linesTailIdx_); - - uint16_t lineNbr = 0; - int deciY = 0; - uint8_t darkPicturePercent = 0; - - for (Idx lineIdx = firstPageLineIdx_; lineIdx != linesTailIdx_; - nextLineIdx(lineIdx), lineNbr++) { - - Token &token = tokens_.tokenAt(lines_[lineIdx].tokenIdx); - - // log_w("TokenId: %" PRIu8, (uint8_t) token.tokenId); - - switch (token.tokenId) { - case TokenId::PICTURE: - REND_WLOG("[%" PRIu16 "] %s: %s", lineIdx, token.streamPlace.str().c_str(), - token.cStr); - if ((token.picture == nullptr) || (token.picture == nullptr)) { - REND_PROFILE_START(ReadPictureFromEPub); - auto [buffer, len] = stream_->getFile(token.cStr); - REND_PROFILE_END(ReadPictureFromEPub); - - if (buffer != nullptr) { - token.picture = std::make_shared(); - if (!token.picture->readFromBytes(buffer.get(), len)) { - log_e("Unable ro retrieve the BMP picture from EPub"); - REND_EXIT_SIM; - } - } else { - REND_ELOG("buffer is empty!"); - } - } - if ((token.picture != nullptr) && (token.picture != nullptr)) { - auto bitmap = token.picture->getBuffer(); - uint16_t width = token.pixWidth; - uint16_t height = token.pixHeight; - - darkPicturePercent = computeDarkPercent(bitmap, width, height); - REND_WLOG("Dark Picture Percentage: %" PRIu8, darkPicturePercent); - - display_.fillScreen(display_.bg); - display_.drawXBitmap((display_.width() - width) / 2, - (display_.height() - height) / 2, bitmap, width, height, - display_.fg); - - break; - } else { - log_e("Picture not there!!"); - REND_EXIT_SIM; - } - break; - - case TokenId::EPICTURE: - REND_WLOG("[%" PRIu16 "] %s: %s", lineIdx, token.streamPlace.str().c_str(), - token.cStr); - if ((token.picture == nullptr) || (token.picture == nullptr)) { - REND_PROFILE_START(ReadPictureFromEPub); - auto [buffer, len] = stream_->getFile(token.cStr); - REND_PROFILE_END(ReadPictureFromEPub); - - if (buffer != nullptr) { - token.picture = std::make_shared(); - if (!token.picture->readFromBytes(buffer.get(), len)) { - log_e("Unable ro retrieve the BMP picture from EPub"); - REND_EXIT_SIM; - } - } else { - REND_ELOG("buffer is empty!"); - } - } - if ((token.picture != nullptr) && (token.picture != nullptr)) { - auto bitmap = token.picture->getBuffer(); - uint16_t width = token.pixWidth; - uint16_t height = token.pixHeight; - // int16_t y = deciToPix(deci_y + ENDPARAGRAPH_DECI_LINE_HEIGHT) + height / 2; - // uint8_t darkPercent = computeDarkPercent(bitmap, width, height); - // if (darkPercent > darkPicturePercent) { - // darkPicturePercent = darkPercent; - // } - display_.drawXBitmap(rect_.x + (rect_.width - width) / 2, - rect_.y + deciToPix(deciY), bitmap, width, height, - display_.fg); - deciY += pixToDeci(height + 4); - } else { - log_e("Picture not there!!"); - REND_EXIT_SIM; - } - break; - - case TokenId::ENDPARAGRAPH: - if (deciY == 0) { - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, - token.streamPlace.str().c_str()); - continue; - } - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, token.streamPlace.str().c_str()); - deciY += lines_[lineIdx].deciHeight; - break; - - case TokenId::HRULE: { - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, token.streamPlace.str().c_str()); - uint16_t y = deciToPix(deciY + (lines_[lineIdx].deciHeight / 2)); - display_.drawLine(rect_.x, rect_.y + y, rect_.x + rect_.width, rect_.y + y, - display_.fg); - deciY += lines_[lineIdx].deciHeight; - } break; - - case TokenId::BTABLE: - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, token.streamPlace.str().c_str()); - if (lines_[lineIdx].deciHeight > NEWLINE_DECI_LINE_HEIGHT) { - deciY += ENDPARAGRAPH_DECI_LINE_HEIGHT; - } - -#if CONFIG_TINYFONT_TTF - drawSingleLine(lines_[lineIdx], deciToPix(deciY)); -#else - Line line; - getLine(lines_[lineIdx], line); - display_.drawSingleLineOfText(line.data(), rect_.x + lines_[lineIdx].leftIndent, - rect_.y + deciToPix(deciY), false, font_); -#endif - deciY += NEWLINE_DECI_LINE_HEIGHT; - break; - - case TokenId::ETABLE: { - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, token.streamPlace.str().c_str()); - uint16_t y = deciToPix(deciY + (HRULE_DECI_LINE_HEIGHT / 2)); - display_.drawLine(rect_.x, rect_.y + y, rect_.x + rect_.width, rect_.y + y, - display_.fg); - deciY += HRULE_DECI_LINE_HEIGHT; - -#if CONFIG_TINYFONT_TTF - drawSingleLine(lines_[lineIdx], deciToPix(deciY)); -#else - Line line; - getLine(lines_[lineIdx], line); - display_.drawSingleLineOfText(line.data(), rect_.x + lines_[lineIdx].leftIndent, - rect_.y + deciToPix(deciY), false, font_); -#endif - deciY += NEWLINE_DECI_LINE_HEIGHT + ENDPARAGRAPH_DECI_LINE_HEIGHT; - } break; - - default: - // Only draw the lines if it's not skipped from the caller. - if (lineNbr >= numSkipLines) { -#if CONFIG_TINYFONT_TTF - drawSingleLine(lines_[lineIdx], deciToPix(deciY)); -#else - Line line; - getLine(lines_[lineIdx], line); - // log_w("Real line width: %" PRIu16 " indent: %" PRIu16, - // display_.getTextWidth(line.data(), font_) + lines_[lineIdx].leftIndent, - // lines_[lineIdx].leftIndent); - REND_WLOG("[%" PRIu16 "] %s: <%s>", lineIdx, token.streamPlace.str().c_str(), - line.data()); - display_.drawSingleLineOfText(line.data(), rect_.x + lines_[lineIdx].leftIndent, - rect_.y + deciToPix(deciY), false, font_); -#endif - } else { - REND_WLOG("[%" PRIu16 "] %s: ", lineIdx, token.streamPlace.str().c_str()); - } - deciY += NEWLINE_DECI_LINE_HEIGHT; - break; - } - } - - return darkPicturePercent; -} - -// Used in the TXT book/article context to find the beginning of the page to show when going -// backward. -void Pager::setFirstLineIdxBackward() { - REND_WLOG("Start of First Line Idx search..."); - Idx lineIdx = linesTailIdx_; - if (lineIdx != linesHeadIdx_) { - uint16_t deciLines = 0; - do { - prevLineIdx(lineIdx); - TokenId tokenId = tokens_[lines_[lineIdx].tokenIdx].tokenId; - uint16_t height = (tokenId == TokenId::ENDPARAGRAPH) ? ENDPARAGRAPH_DECI_LINE_HEIGHT - : (tokenId == TokenId::PICTURE) ? maxDeciLines_ - : NEWLINE_DECI_LINE_HEIGHT; - if ((deciLines + height) > maxDeciLines_) { - nextLineIdx(lineIdx); - break; - } - deciLines += height; - } while (lineIdx != linesHeadIdx_); - } else { - REND_ELOG("Lines[] is empty!!"); - } - firstPageLineIdx_ = lineIdx; -} - -void Pager::addMenuParameter(const std::string &key, const std::string &value) { - menuParameters_[key] = value; -} - -auto Pager::getMenuParameters() const -> const MenuParameters & { return menuParameters_; } - -// Debugging stuff -void Pager::show() const { - if constexpr (REND_DEBUG) { - std::cout << "head:" << linesHeadIdx_ << ", Tail:" << linesTailIdx_ << std::endl; - for (Idx lineIdx = linesHeadIdx_; lineIdx != linesTailIdx_; nextLineIdx(lineIdx)) { - std::cout << lineIdx << " " << tokens_[lines_[lineIdx].tokenIdx].streamPlace.str() - << ": <"; - Idx lastTokenIdx = - tokens_.addToTokenIdx(lines_[lineIdx].tokenIdx, lines_[lineIdx].tokenCount); - for (Idx tokenIdx = lines_[lineIdx].tokenIdx; tokenIdx != lastTokenIdx; - tokens_.nextTokenIdx(tokenIdx)) { - if ((static_cast(tokens_[tokenIdx].tokenId) & - (static_cast(TokenId::NEWLINE) | - static_cast(TokenId::ENDPARAGRAPH))) == 0) { - std::cout << tokens_[tokenIdx].cStr; - if (tokens_[tokenIdx].spacesAfter) { - std::cout << " "; - } - } - } - std::cout << ">" << std::endl; - } - std::cout << "Next Page Place: " << nextPagePlace_.str().c_str() << std::endl; - std::cout << "Page Word Count: " << wordCount_ << std::endl; - } -} diff --git a/src/Renderers/Pager.hpp b/src/Renderers/Pager.hpp deleted file mode 100644 index a658e1ee8..000000000 --- a/src/Renderers/Pager.hpp +++ /dev/null @@ -1,236 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "EPub/EPubFile.hpp" -#include "Misc/BMPImage.hpp" -#include "RendererDefs.hpp" -#include "Tokens.hpp" -#include "config.h" - -// From the token stream, generate pages content, separating tokens into lines and pages that fit -// the current display region. The last page retrieved is found in the lines_[] circular array. -class Pager { -private: - Tokens &tokens_; - DisplaySystem &display_; - DisplayRect rect_; - std::shared_ptr stream_; - - uint16_t wordCount_{0}; - uint16_t currentPageDecilineHeight_{0}; - uint16_t currentLineWidth_{0}; - Idx firstPageLineIdx_{0}; - - Lines lines_{}; - Idx linesHeadIdx_{0}; - Idx linesTailIdx_{0}; - - uint16_t leftIndent_{0}; - uint16_t rightIndent_{0}; - - uint16_t maxDeciLines_{0}; - uint16_t maxLineWidth_{0}; - StreamPlace nextPagePlace_; - bool atEOF_{false}; - - uint16_t spaceWidth_{0}; - - MenuParameters menuParameters_; - - int lineSpacing_{0}; - - Font *font_{nullptr}; - - [[nodiscard]] inline auto lastLineTokenIdx(Idx lineIdx) const -> Idx { - return lines_[lineIdx].tokenIdx + lines_[lineIdx].tokenCount - 1; - } - - [[nodiscard]] inline auto getLineCount() const -> uint16_t { - return (linesHeadIdx_ <= linesTailIdx_) ? (linesTailIdx_ - linesHeadIdx_) - : (linesTailIdx_ + (LINES_SIZE - linesHeadIdx_)); - } - - [[nodiscard]] inline auto getLastLineIdx() const -> Idx { - return (linesTailIdx_ == 0) ? (LINES_SIZE - 1) : (linesTailIdx_ - 1); - } - - inline void nextLineIdx(Idx &idx) const { idx = (idx + 1) % LINES_SIZE; } - inline void prevLineIdx(Idx &idx) const { idx = (idx == 0) ? (LINES_SIZE - 1) : (idx - 1); } - - inline void incrLinesHeadIdx() { linesHeadIdx_ = (linesHeadIdx_ + 1) % LINES_SIZE; } - inline void incrLinesTailIdx() { - if (linesIsFull()) { - freeLineHead(); - } - linesTailIdx_ = (linesTailIdx_ + 1) % LINES_SIZE; - } - -#if CONFIG_TINYFONT_TTF - auto getLineSegment(LineInfo &lineInfo, Idx tokenIdx, Idx endTokenIdx, Line &line) const -> Idx; - void drawSingleLine(LineInfo &lineInfo, int yPos) const; -#else - void getLine(LineInfo &lineStruct, Line &line) const; -#endif - - auto computeDarkPercent(const uint8_t *bitmap, uint16_t width, uint16_t height) const - -> uint8_t; - void newLine(uint16_t height); - -public: - Pager(Tokens &tokens, DisplaySystem &display, DisplayRect &rect) - : tokens_(tokens), display_(display), rect_(rect) {} - Pager(const Pager &) = delete; - - void setStream(const std::shared_ptr &stream) { - stream_ = std::static_pointer_cast(stream); - } - - [[nodiscard]] auto getLineSpacing() const -> int { return lineSpacing_; } - void setLineSpacing(int newSpacing) { lineSpacing_ = newSpacing; } - - void setFont(Font *font) { font_ = font; } - - [[nodiscard]] auto getLineHeightPlusSpacing() const -> int { - return font_->lineHeight() + lineSpacing_; - } - // Switch to 1/10 of a line as this gives us more flexibilty in rendering - // precision between lines. - [[nodiscard]] auto getMaxDeciLines() const -> int16_t { - return (rect_.height * NEWLINE_DECI_LINE_HEIGHT) / (getLineHeightPlusSpacing()); - } - - inline void clear() { - - leftIndent_ = 0; - rightIndent_ = 0; - atEOF_ = false; - wordCount_ = 0; - linesHeadIdx_ = linesTailIdx_ = 0; - firstPageLineIdx_ = 0; - currentLineWidth_ = 0; - currentPageDecilineHeight_ = 0; - maxDeciLines_ = getMaxDeciLines(); - maxLineWidth_ = rect_.width; - lines_[0] = {0, 0, 0, 0, 0}; - uint16_t notUsed; - display_.getTextSize(" ", &spaceWidth_, ¬Used); - menuParameters_.clear(); - } - - inline void setLineIndent(uint16_t leftIndent = 0, uint16_t rightIndent = 0) { - leftIndent_ = leftIndent; - lines_[linesTailIdx_].leftIndent = leftIndent_; - currentLineWidth_ = leftIndent_; - - rightIndent_ = rightIndent; - maxLineWidth_ = rect_.width - rightIndent; - - // log_w("Set Line Indent: %" PRIu16 " - %" PRIu16, leftIndent_, rightIndent_); - } - - inline auto getLineIndent() -> std::pair { - return {leftIndent_, rightIndent_}; - } - - // Returns false if the last token is not part of the page. - // The token will be the first to appear on next page. - [[nodiscard]] auto addLastTokenToPage(bool dontStop = false) -> bool; - [[nodiscard]] auto computeWordCount() const -> uint16_t; - - [[nodiscard]] inline auto linesIsFull() const -> bool { - return getLineCount() > (LINES_SIZE - 2); - } - [[nodiscard]] inline auto linesIsEmpty() const -> bool { - return linesHeadIdx_ == linesTailIdx_; - } - - [[nodiscard]] inline auto getPageLineCount() const -> uint16_t { - return (firstPageLineIdx_ < linesTailIdx_) - ? (linesTailIdx_ - firstPageLineIdx_) - : (linesTailIdx_ + (LINES_SIZE - firstPageLineIdx_)); - } - - [[nodiscard]] inline auto getWordCount() const -> uint16_t { - return firstPageLineIdx_ == 0 ? wordCount_ : computeWordCount(); - } - [[nodiscard]] inline auto getLineLength(Idx lineIdx) const -> uint16_t { - return lines_[firstPageLineIdx_ + lineIdx].length; - } - [[nodiscard]] auto getPageLength() const -> uint16_t; - - [[nodiscard]] inline auto getNextPageStreamPlace() const -> StreamPlace { - log_d("getNextPageStreamPlace: (%s) next: %s", atEOF_ ? "at EOF" : "not at eof", - nextPagePlace_.str().c_str()); - return nextPagePlace_; - } - - inline void setNextPagePlace(StreamPlace place) { nextPagePlace_ = place; } - - void setFirstLineIdxBackward(); - - [[nodiscard]] inline auto getStreamPlaceOfFirstLine() -> StreamPlace { - return tokens_[lines_[firstPageLineIdx_].tokenIdx].streamPlace; - } - - [[nodiscard]] inline auto getFirstLineIdx() const -> Idx { return firstPageLineIdx_; } - - auto displayPage(int16_t numSkipLines) -> uint8_t; - - void setEndOfFile() { - atEOF_ = true; - if (lines_[linesTailIdx_].tokenCount != 0) { - incrLinesTailIdx(); - } - } - - [[nodiscard]] auto atEndOfFile() const -> bool { return atEOF_; } - - void completeLastLine(); - - inline void freeLineHead() { - if (linesHeadIdx_ != linesTailIdx_) { - - // REND_WLOG("Freeing line at idx %" PRIu16, linesHeadIdx_); - - tokens_.freeTokens(lines_[linesHeadIdx_].tokenIdx, lines_[linesHeadIdx_].tokenCount); - - incrLinesHeadIdx(); - if constexpr (REND_DEBUG) { - if (linesHeadIdx_ == linesTailIdx_) { - REND_ELOG("lines_[] is empty!!"); - } - } - } - } - - inline void addSpaceWidth() { currentLineWidth_ += spaceWidth_; } - - [[nodiscard]] inline auto deciToPix(uint16_t decilineValue) const -> uint16_t { - return (getLineHeightPlusSpacing() * decilineValue) / NEWLINE_DECI_LINE_HEIGHT; - } - - [[nodiscard]] inline auto pixToDeci(uint16_t pixValue) const -> uint16_t { - return ((NEWLINE_DECI_LINE_HEIGHT * pixValue)) / getLineHeightPlusSpacing(); - } - - void addMenuParameter(const std::string &key, const std::string &value); - [[nodiscard]] auto getMenuParameters() const -> const MenuParameters &; - - void show() const; - - void setRect(DisplayRect rect) { rect_ = rect; } - -#if REND_DEBUGGING - auto getTokenIdStr(TokenId id) const -> const char * { - for (int idx = 0; idx < 16; idx++) { - if ((uint16_t)id & (1 << idx)) { - return TokenIdStr[idx]; - } - } - return "UNKNOWN"; - } -#endif -}; diff --git a/src/Renderers/Renderer.cpp b/src/Renderers/Renderer.cpp deleted file mode 100644 index e0d5061e4..000000000 --- a/src/Renderers/Renderer.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "Renderer.hpp" - -#include -#include - -#include "UI/Fonts.hpp" - -// Retrieve and show the page at fromPlace location. -auto Renderer::renderPage(std::shared_ptr fromPlace, int16_t numSkipLines, bool forceRefresh, - bool retrieveMenuParameters) - -> std::pair, uint8_t> { - - // if no fromPlace is received (null) or the place's DocType is wrong, get the first page - // pointer, else convert fromPlace to a StreamPlace - StreamPlace startPlace = (fromPlace == nullptr) - ? tokenizer_->getPlaceAtBeginningOfDocument() - : tokenizer_->toStreamPlace(std::move(fromPlace)); - - REND_WLOG("%s is place in file being requested.", startPlace.str().c_str()); - - tokens_.clear(); - pager_.clear(); - - tokenizer_->computeMaxTextTokenCharSize(); - - // std::cout << "Pager Line Count: " << pager_.getLineCount() << std::endl; - // std::cout << "currentPlace: " << currentPlace_.str() - // << ", Pager first Place: " << pager_.getStreamPlaceOfFirstLine().str() << - // std::endl; - - PROFILE_START(loadAndTokenizePage); - - // std::cout << "Pager Line Count: " << pager_.getLineCount() << std::endl; - - // We only re-tokenize if required (or forced through the forceRefresh param). May already been - // done from the getPreviousPlace() method. - - if (pager_.linesIsEmpty() || (!(startPlace == pager_.getStreamPlaceOfFirstLine())) || - forceRefresh) { - std::tie(currentPlace_, nextPlace_) = - tokenizer_->loadAndTokenizePage(startPlace, false, nullptr, retrieveMenuParameters); - - if (retrieveMenuParameters && !pager_.getMenuParameters().empty()) { - return {fromPlace, 0}; - } - } - - PROFILE_END_LOG_MS(loadAndTokenizePage); - - // pager_.show(); - - uint8_t darkPicturePercent = pager_.displayPage(numSkipLines); - - REND_WLOG("Returning next page place to be %s", nextPlace_.str().c_str()); - - if constexpr (REND_STATS) { - showStats(); - } - - return {tokenizer_->toPlace(nextPlace_), darkPicturePercent}; -} - -// Seeks backwards and returns the place of one page previous. -auto Renderer::goBackwardsOnePage(StreamPlace endPlace) -> StreamPlace { - tokens_.clear(); - pager_.clear(); - - tokenizer_->computeMaxTextTokenCharSize(); - - // The following is done to compute the maximum number of characters a display - // screen can get. The character "G" is a good compromize between smaller - // ones (line lowercases) and bigger ones (like "M"). The obtained value added - // to the maximum number of bytes in a paragraph will be used to return back in - // the document to find the preceeding page start. - - uint16_t charWidth, charHeight; - display_.getTextSize("G", &charWidth, &charHeight, font_); - - uint16_t maxPageCount = ((rect_.width / charWidth) + 1) * ((rect_.height / charHeight) + 1); - if (maxPageCount > MAX_BYTES_PER_DISPLAY_PAGE) { - maxPageCount = MAX_BYTES_PER_DISPLAY_PAGE; - } - - // std::cout << "Max Page Count: " << maxPageCount << std::endl; - - // Figure out how much to read before the current place. If we're too - // close to the beginning, we just read from the beginning. - - // log_e("Before goBackward(): endPlace: %s", endPlace.str().c_str()); - - StreamPlace seekPlace = - tokenizer_->goBackward(endPlace, maxPageCount + MAX_BYTES_PER_PARAGRAPH); - - // log_e("After goBackward(): seekPlace: %s", seekPlace.str().c_str()); - - // Tokenize it the same as you would for a forwards render. seekPlace will be returned to be at - // a UTF8 character boundary - - if (!(seekPlace == endPlace)) { - StreamPlace lastPlace = endPlace; - if (seekPlace.getSpineIdx() != endPlace.getSpineIdx()) { - lastPlace = {END_OFFSET, seekPlace.getSpineIdx()}; - } - std::tie(currentPlace_, nextPlace_) = tokenizer_->loadAndTokenizePage( - seekPlace, true, - &lastPlace); // NOLINT(clang-analyzer-deadcode.DeadStores) - } else { - std::tie(currentPlace_, nextPlace_) = tokenizer_->loadAndTokenizePage( - seekPlace); // NOLINT(clang-analyzer-deadcode.DeadStores) - } - - // pager_.show(); - - // std::cout << "Characters count in page: " << pager_.getPageLength() << std::endl; - - if (currentPlace_.getOffset() == 0) { - // We are now at the beginning of the current file. If the user seeked to some - // position that is not the beginning of a page, this will insure than the - // renderPage() method will rebuild the whole first page. - pager_.clear(); - } - - return currentPlace_; -} - -auto Renderer::getPreviousPlace() -> std::shared_ptr { - - PROFILE_START(goBackwardsOnePage); - - previousPlace_ = goBackwardsOnePage(currentPlace_); - - PROFILE_END_LOG_MS(goBackwardsOnePage); - - REND_WLOG("Computed place: %s", previousPlace_.str().c_str()); - - return tokenizer_->toPlace(previousPlace_); -} diff --git a/src/Renderers/Renderer.hpp b/src/Renderers/Renderer.hpp deleted file mode 100644 index c12ef27f5..000000000 --- a/src/Renderers/Renderer.hpp +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - -#include - -#include "Displays/TextInsets.hpp" -#include "NullTokenizer.hpp" -#include "Pager.hpp" -#include "RendererDefs.hpp" -#include "Tokenizer.hpp" -#include "Tokens.hpp" - -using namespace renderer_defs; - -class Renderer { - -public: - Renderer(DisplaySystem &display, DisplayRect rect, Font *font) - : display_(display), rect_(rect), tokens_(font), pager_(tokens_, display, rect), - font_(font) { - // log_w("constructor..."); - tokenizer_ = std::make_shared(font_, rect_, tokens_, pager_); - // Be sure the pager and tokenizer are set up with the correct rect and font - setRect(rect); - setFont(font); - } - - auto setRect(DisplayRect rect) -> void { - rect_ = rect; - pager_.setRect(rect); - tokenizer_->setRect(rect); - } - - [[nodiscard]] auto getLineSpacing() const -> int { return pager_.getLineSpacing(); } - void setLineSpacing(int newSpacing) { pager_.setLineSpacing(newSpacing); } - - auto setFont(Font *font) -> void { - font_ = font; - pager_.setFont(font_); - tokenizer_->setFont(font_); - tokens_.setFont(font_); - } - - [[nodiscard]] auto getFont() const -> Font * { return font_; } - - auto setStream(const std::shared_ptr &stream) -> void { - // log_w("setStream..."); - tokenizer_ = Tokenizer::factory(stream, font_, rect_, tokens_, pager_); - } - - [[nodiscard]] inline auto getStream() -> std::shared_ptr { - return tokenizer_->getStream(); - } - - auto setCurrentPlace(std::shared_ptr newPlace) -> void { - // log_w("setCurrentPlace..."); - currentPlace_ = tokenizer_->toStreamPlace(std::move(newPlace)); - } - - [[nodiscard]] auto getStreamPlace(std::shared_ptr place) -> StreamPlace { - // log_w("setCurrentPlace..."); - return tokenizer_->toStreamPlace(std::move(place)); - } - - [[nodiscard]] auto getPreviousPlace() -> std::shared_ptr; - - [[nodiscard]] inline auto getCurrentPlace() const -> std::shared_ptr { - return tokenizer_->toPlace(currentPlace_); - } - - [[nodiscard]] inline auto getTxtOffset() const -> uint32_t { - return tokenizer_->getTxtOffset(); - } - - [[nodiscard]] inline auto size() const -> uint32_t { return tokenizer_->streamSize(); } - - // Render a page of text with the current place - // returns the next place for reading so the calling function can - // track place. - // - // numSkipLines allows us to not render the first X lines of the page - // for the speed reading functionality. - - // NOLINTNEXTLINE(modernize-use-nodiscard) - auto renderPage(std::shared_ptr fromPlace, int16_t numSkipLines = 0, - bool forceRefresh = false, bool retrieveMenuParameters = false) - -> std::pair, uint8_t>; - - void showStats() { - log_w("Circular buffer max lenghts: Tokens: %" PRIu16 ", CStrPool: %" PRIu16, - tokens_.getMaxTokensCount(), tokens_.getMaxCStrPoolCount()); - } - - // NOLINTNEXTLINE(modernize-use-nodiscard) - inline auto renderFirstPage(int16_t numSkipLines = 0, bool forceRefresh = false) - -> std::pair, bool> { - return renderPage(tokenizer_->toPlace(tokenizer_->getPlaceAtBeginningOfDocument()), - numSkipLines, forceRefresh); - } - - [[nodiscard]] inline auto getTotalLengthOfLines() const -> uint16_t { - return pager_.getPageLength(); - } - - [[nodiscard]] inline auto getLengthOfLine(Idx lineIdx) const -> uint16_t { - return pager_.getLineLength(lineIdx); - } - - [[nodiscard]] inline auto getNumRenderedLines() const -> uint16_t { - return pager_.getPageLineCount(); - } - - [[nodiscard]] inline auto getCurrentPageWordCount() const -> uint16_t { - return pager_.getWordCount(); - } - - [[nodiscard]] inline auto atEndOfFile() const -> bool { return pager_.atEndOfFile(); } - - [[nodiscard]] inline auto getMenuParameters() const -> const MenuParameters & { - return pager_.getMenuParameters(); - } - -private: - DisplaySystem &display_; - DisplayRect rect_; - Tokens tokens_; - Pager pager_; - Font *font_; - - std::shared_ptr tokenizer_{nullptr}; - - StreamPlace currentPlace_{}; - StreamPlace previousPlace_{}; - StreamPlace nextPlace_{}; - - auto goBackwardsOnePage(StreamPlace endPlace) -> StreamPlace; -}; diff --git a/src/Renderers/RendererDefs.hpp b/src/Renderers/RendererDefs.hpp deleted file mode 100644 index 106f3ac18..000000000 --- a/src/Renderers/RendererDefs.hpp +++ /dev/null @@ -1,254 +0,0 @@ -#pragma once - -#include - -#include "Displays/DisplaySystem.hpp" -#include "EPub/EPubDefs.hpp" -#include "Misc/BMPImage.hpp" -#include "Misc/Platform.hpp" -#include "Models/Place.hpp" -#include "config.h" -#include "sindarin-debug.h" - -namespace renderer_defs { - -// The following defines are helpers for code development -// That is specific to the renderer - -#define REND_DEBUGGING 0 // Show debugging messages where appropriate -#define REND_EXIT_ON_ERROR 0 // Simulator Only!! -#define REND_PROFILING 0 // Speed measurements -#define REND_STATISTICS 0 // Stats on Tokens size and Lines size - -#if !SIMULATOR -#undef REND_EXIT_ON_ERROR // To be sure there will be no exit() call on the Sol device -#endif - -#if REND_EXIT_ON_ERROR -static constexpr bool REND_ON_ERROR_EXIT = true; -#define REND_EXIT_SIM exit(1) -#define REND_ELOG(f, ...) \ - { \ - log_e(f, ##__VA_ARGS__); \ - assert(false && "REND_ELOG"); \ - } -#else -static constexpr bool REND_ON_ERROR_EXIT = false; -#define REND_EXIT_SIM -#endif - -#if REND_DEBUGGING -static constexpr bool REND_DEBUG = true; -#define REND_WLOG(f, ...) log_w(f, ##__VA_ARGS__); -#ifndef REND_ELOG -#define REND_ELOG(f, ...) log_e(f, ##__VA_ARGS__); -#endif -#else -static constexpr bool REND_DEBUG = false; -#define REND_WLOG(...) -#ifndef REND_ELOG -#define REND_ELOG(...) -#endif -#endif - -#if REND_PROFILING -#define REND_PROFILE_START(v) PROFILE_START(v) -#define REND_PROFILE_END(v) PROFILE_END_LOG_MS(v) -#else -#define REND_PROFILE_START(v) -#define REND_PROFILE_END(v) -#endif - -#if REND_STATISTICS -static constexpr bool REND_STATS = true; -#else -static constexpr bool REND_STATS = false; -#endif - -// Used by the Tokenizer::getFileLocation() method in case of error -static constexpr uint32_t LOCATION_ERROR = 0xFFFFFFFF; - -// This is the number of SUB-lines we want to split a line into to be able to render bare -// newlines as less than a full line height of the text. (Deci was chosen as 1/10) So what the -// two lines below mean is that a new line by itself on a line will render as 4/10s of a full -// line height. Theoretically we can do any percentage we want here, but there is a lot of -// testing to do for that! - -static constexpr uint16_t NEWLINE_DECI_LINE_HEIGHT = 10; -static constexpr uint16_t ENDPARAGRAPH_DECI_LINE_HEIGHT = 4; -static constexpr uint16_t HRULE_DECI_LINE_HEIGHT = 4; - -// A Page array must be large enough to accommodate the need to load a complete display page, -// but also able to load a complete paragraph that can precede the page when the back page -// command is issued by the user. By design a paragraph cannot have more than 1500 bytes, -// followed by a NEWLINE, an ENDOFPARAGRAPH, and a null. - -static constexpr uint16_t MAX_BYTES_PER_PARAGRAPH = CONFIG_MAX_BYTES_PER_PARAGRAPH; -static constexpr uint16_t MAX_BYTES_PER_DISPLAY_PAGE = CONFIG_MAX_BYTES_PER_DISPLAY_PAGE; -static constexpr uint16_t MAX_BYTES_TO_RETRIEVE = - (MAX_BYTES_PER_PARAGRAPH + MAX_BYTES_PER_DISPLAY_PAGE); -static constexpr uint16_t CSTR_POOL_SIZE = CONFIG_CSTR_POOL_SIZE; -static constexpr uint16_t TOKENS_SIZE = CONFIG_TOKENS_SIZE; -static constexpr uint16_t MAX_BYTES_PER_LINE = CONFIG_MAX_BYTES_PER_LINE; -static constexpr uint16_t LINES_SIZE = CONFIG_LINES_SIZE; -static constexpr uint16_t PAGE_INDEX_SIZE = 500; - -static constexpr uint32_t END_OFFSET = 999999999U; - -// NOTE: -// -// In the renderer class definitions: -// -// - "Place" as a variable suffix, defines a character index in the file stream -// - "Idx" as a variable suffix, defines an index in one of the arrays maintained by this class - -using Idx = int; - -#if CONFIG_TINYFONT_TTF -// These are the different tokens so as we parse a buffer, we more easily know what to do with the -// text. Bits are assigned to each such that they can be combined in a mask for comparisons. -enum class TokenId : uint16_t { - TEXT = 1 << 0, // Usually a word. Can contain &NBSP; and spaces characters for formatting - ITEXT = 1 << 1, // Indent *next* lines using this token width - HTEXT = 1 << 2, // Start of a

      ..

      header tag - NTEXT = 1 << 3, // Text that is *NOT* in the pool - SUPTEXT = 1 << 4, // ... support - SUBTEXT = 1 << 5, // .. support - NEWLINE = 1 << 6, // Full line vertical space. Break lines - ENDPARAGRAPH = 1 << 7, // After paragraph vertical space - HYPHEN = 1 << 8, // ASCII hyphen "-" - ENDASH = 1 << 9, // Unicode En Dash (U+2013) "–" - EMDASH = 1 << 10, // Unicode Em Dash (U+2014) "—" - PICTURE = 1 << 11, // Whole screen picture - EPICTURE = - 1 << 12, // Embedded in page picture (between paragraphs) More than one can be present - HRULE = 1 << 13, // Horizontal line - BTABLE = 1 << 14, // Beginning of a table - ETABLE = 1 << 15 // End of table -}; - -#if REND_DEBUGGING -static const char *TokenIdStr[] = { - "TEXT", "ITEXT", "HTEXT", "NTEXT", "SUPTEXT", "SUBTEXT", "NEWLINE", "ENDPARAGRAPH", - "HYPHEN", "ENDASH", "EMDASH", "PICTURE", "EPICTURE", "HRULE", "BTABLE", "ËTABLE"}; -#endif -#else -// These are the different tokens so as we parse a buffer, we more easily know what to do with the -// text. Bits are assigned to each such that they can be combined in a mask for comparisons. -enum class TokenId : uint16_t { - TEXT = 1 << 0, // Usually a word. Can contain &NBSP; and spaces characters for formatting - ITEXT = 1 << 1, // Indent *next* lines using this token width - HTEXT = 1 << 2, // Start of a

      ..

      header tag - NTEXT = 1 << 3, // Text that is *NOT* in the pool - NEWLINE = 1 << 4, // Full line vertical space. Break lines - ENDPARAGRAPH = 1 << 5, // After paragraph vertical space - HYPHEN = 1 << 6, // ASCII hyphen "-" - ENDASH = 1 << 7, // Unicode En Dash (U+2013) "–" - EMDASH = 1 << 8, // Unicode Em Dash (U+2014) "—" - PICTURE = 1 << 9, // Whole screen picture - EPICTURE = - 1 << 10, // Embedded in page picture (between paragraphs) More than one can be present - HRULE = 1 << 11, // Horizontal line - BTABLE = 1 << 12, // Beginning of a table - ETABLE = 1 << 13 // End of table -}; - -#if REND_DEBUGGING -static const char *TokenIdStr[] = {"TEXT", "ITEXT", "HTEXT", "NTEXT", "NEWLINE", - "ENDPARAGRAPH", "HYPHEN", "ENDASH", "EMDASH", "PICTURE", - "EPICTURE", "HRULE", "BTABLE", "ËTABLE"}; -#endif - -#endif - -// BitMask of all token ids that are using the pool space -static const uint16_t POOL_TOKENS = - static_cast(TokenId::TEXT) | static_cast(TokenId::ITEXT) | - static_cast(TokenId::HTEXT) -#if CONFIG_TINYFONT_TTF - | static_cast(TokenId::SUBTEXT) | static_cast(TokenId::SUPTEXT) -#endif - | static_cast(TokenId::PICTURE) | static_cast(TokenId::EPICTURE); -#pragma pack(push, 1) - -class StreamPlace { -public: - StreamPlace(uint32_t offset, Idx spineIdx) : offset_(offset), spineIdx_(spineIdx) {} - StreamPlace() : offset_(0), spineIdx_(0) {} - - inline void setOffset(uint32_t offset) { offset_ = offset; } - inline void incOffset() { offset_ += 1; } - inline void decOffset() { - if (offset_ > 0) { - offset_ -= 1; - } - } - [[nodiscard]] inline auto getOffset() const -> uint32_t { return offset_; } - [[nodiscard]] inline auto getSpineIdx() const -> SpineIdx { return spineIdx_; } - - [[nodiscard]] inline auto operator==(const StreamPlace &rhs) const -> bool { - return (offset_ == rhs.offset_) && (spineIdx_ == rhs.spineIdx_); - } - - [[nodiscard]] inline auto operator>=(const StreamPlace &rhs) const -> bool { - return (spineIdx_ > rhs.spineIdx_) || - ((spineIdx_ == rhs.spineIdx_) && (offset_ >= rhs.offset_)); - } - - [[nodiscard]] auto str() const -> std::string { - std::string s; - s = "[offset:"; - s += std::to_string(offset_); - s.append(", spineIdx:"); - s += std::to_string(spineIdx_); - s.append("]"); - return s; - } - -private: - uint32_t offset_; - Idx spineIdx_; -}; - -// A token is simply a place in the buffer and a token identifer -// that indicates what type of token it is. -// -// To determine the length of a token, we can simply look at the -// next token in the buffer and subtract the current token's -// place. -// -// Fields ordering is to optimize the space required. -struct Token { - // This basically gives each token knowledge of where the paragraph - // start is for where it's located. It's useful for looking backwards - // while rendering a back page command. - const char *cStr{}; - std::shared_ptr picture; - StreamPlace streamPlace; - uint16_t pixWidth{}; // Width in pixels. Used with pictures and text tokens - uint16_t pixHeight{}; // Height in pixels. Used with pictures tokens - uint16_t length{}; // Length in bytes - TokenId tokenId{TokenId::TEXT}; - uint8_t spacesAfter{}; -}; - -struct LineInfo { - Idx tokenIdx; - uint16_t tokenCount; - uint16_t length; // String length - uint16_t deciHeight; // Height in decilines - uint16_t leftIndent; // Line Left Indentation -}; -#pragma pack(pop) - -using TokensArray = std::array; -using CStrPool = std::array; - -using Line = std::array; -using Lines = std::array; - -using PageIndex = std::array; - -using MenuParameters = SpiramOrderedMap; - -} // namespace renderer_defs diff --git a/src/Renderers/RendererStream.hpp b/src/Renderers/RendererStream.hpp deleted file mode 100644 index 81871da45..000000000 --- a/src/Renderers/RendererStream.hpp +++ /dev/null @@ -1,158 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Models/DocType.hpp" -#include "sindarin-debug.h" - -class RendererStream { -private: - DocType docType_{DocType::NONE}; - -protected: - RendererStream(DocType docType) : docType_(docType) {} - RendererStream() = default; - -public: - virtual ~RendererStream() = default; - - [[nodiscard]] auto isFor(DocType docType) const -> bool { return docType == docType_; } - [[nodiscard]] auto getDocType() const -> DocType { return docType_; } - - static auto parseDocType(const char *path) -> DocType { - if (HasSuffix(path, ".txt") || HasSuffix(path, ".txt.gz")) { - return DocType::TXT; - } - if (HasSuffix(path, ".epub")) { - return DocType::EPUB; - } - return DocType::NONE; - } - - virtual auto getNextByte() -> uint8_t { - if (size() > position()) { - return static_cast(read()); - } else { - return 0; - } - } - - virtual auto available() -> int { return size() - position(); }; - virtual auto readBytes(uint8_t *buf, int size) -> int { - // implementation of byte-by-byte read for a block, - // only used if RendererStream subclass does not override this - // currently only compressed streams use and override this method - int ch = 0; - for (int i = 0; i < size; i++) { - ch = read(); - if (ch < 0) { - return i; - } - - buf[i] = ch; - } - - return size; - }; - virtual auto read() -> int = 0; - virtual auto seek(uint32_t pos) -> bool = 0; - virtual auto position() -> size_t = 0; - virtual auto size() -> size_t = 0; -}; - -template -class ContainerStream : public RendererStream { -public: - ContainerStream(DocType docType = DocType::TXT) : RendererStream(docType), contained_(T()) {} - ContainerStream(T contained, DocType docType = DocType::TXT) - : RendererStream(docType), contained_(std::move(contained)) {} - - auto available() -> int override { return contained_.available(); } - auto read() -> int override { return contained_.read(); }; - auto readBytes(uint8_t *buf, int size) -> int override { - return contained_.readBytes(reinterpret_cast(buf), size); - } - auto seek(uint32_t pos) -> bool override { return contained_.seek(pos); }; - auto position() -> size_t override { return contained_.position(); } - auto size() -> size_t override { return contained_.size(); } - -protected: - T contained_; -}; - -class StdStringStream : public RendererStream { -public: - StdStringStream(std::string &string, DocType docType = DocType::TXT) - : RendererStream(docType), stream_(std::make_shared(string)) { - setSize(string.length()); - } - StdStringStream(SpiramString &string, DocType docType = DocType::TXT) - : RendererStream(docType), stream_(std::make_shared(string)) { - setSize(string.length()); - } - - void setSize(size_t size) { size_ = size; } - - auto available() -> int override { return size() - position(); } - auto read() -> int override { return stream_->get(); }; - auto seek(uint32_t pos) -> bool override { - stream_->seekg(pos); - return stream_->good(); - }; - auto position() -> size_t override { return stream_->tellg(); } - auto size() -> size_t override { return size_; } - -protected: - StdStringStream(std::shared_ptr stream, DocType docType = DocType::TXT) - : RendererStream(docType), stream_(std::move(stream)) {} - - std::shared_ptr stream_; - size_t size_ = 0; -}; - -class BufferStream : public RendererStream { -public: - BufferStream(const uint8_t *data, int size, DocType docType = DocType::TXT) - : RendererStream(docType), data_(data), size_(size) {} - - auto read() -> int override { return data_[pos_++]; }; - auto readBytes(uint8_t *buf, int size) -> int override { - int readSize = size; - if (available() < readSize) { - readSize = available(); - } - - if (readSize == 0) { - return 0; - } - - memcpy(buf, &data_[pos_], readSize); - pos_ += readSize; - return readSize; - } - auto seek(uint32_t pos) -> bool override { - if (pos_ < 0 || pos >= size_) { - return false; - } - - pos_ = pos; - return true; - } - auto position() -> size_t override { return pos_; } - auto size() -> size_t override { return size_; } - -protected: - const uint8_t *data_; - int pos_ = 0; - size_t size_ = 0; -}; diff --git a/src/Renderers/Tokenizer.cpp b/src/Renderers/Tokenizer.cpp deleted file mode 100644 index 85bf662df..000000000 --- a/src/Renderers/Tokenizer.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "Tokenizer.hpp" - -#include "EPubTokenizer.hpp" -#include "NullTokenizer.hpp" -#include "Renderers/RendererStream.hpp" -#include "TxtTokenizer.hpp" - -auto Tokenizer::factory(const std::shared_ptr &stream, Font *font, - DisplayRect &rect, Tokens &tokens, Pager &pager) - -> std::shared_ptr { - std::shared_ptr tokenizer; - - if (stream->isFor(DocType::TXT)) { - tokenizer = std::make_shared(font, rect, tokens, pager); - } else if (stream->isFor(DocType::EPUB)) { - tokenizer = std::make_shared(font, rect, tokens, pager); - } else { - log_e("Stream is of wrong type (must be TXT or EPUB)!: %" PRIu8, - static_cast(stream->getDocType())); - REND_EXIT_SIM; - tokenizer = std::make_shared(font, rect, tokens, pager); - } - tokenizer->setStream(stream); - pager.setStream(stream); - - return tokenizer; -} diff --git a/src/Renderers/Tokenizer.hpp b/src/Renderers/Tokenizer.hpp deleted file mode 100644 index 32d530bfc..000000000 --- a/src/Renderers/Tokenizer.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include - -#include "Pager.hpp" -#include "RendererDefs.hpp" -#include "RendererStream.hpp" -#include "Tokens.hpp" - -using namespace renderer_defs; - -class Tokenizer { -public: - Tokenizer(Font *font, DisplayRect &rect, Tokens &tokens, Pager &pager) - : font_(font), rect_(rect), tokens_(tokens), pager_(pager) {} - virtual ~Tokenizer() = default; - Tokenizer(const Tokenizer &) = delete; - - [[nodiscard]] static auto factory(const std::shared_ptr &stream, Font *font, - DisplayRect &rect, Tokens &tokens, Pager &pager) - -> std::shared_ptr; - - virtual auto loadAndTokenizePage(StreamPlace fromPlace, bool backwards = false, - StreamPlace *toPlace = nullptr, - bool retrieveMenuParameters = false) - -> std::pair = 0; - - [[nodiscard]] virtual auto isFor(DocType docType) const -> bool = 0; - - // Get the largest token to accept to have on a single line. As this ia an approximation, the - // "G" character is a compromise between a very small width character (like the lowercase "l") - // and a very large one (like the uppercase "W"). - void computeMaxTextTokenCharSize() { - maxTextTokenCharSize_ = rect_.width / font_->getTextWidth("G"); - } - - [[nodiscard]] auto getPlaceAtBeginningOfFile(StreamPlace streamPlace) const -> StreamPlace { - return {0, streamPlace.getSpineIdx()}; - } - - [[nodiscard]] auto getPlaceAtBeginningOfDocument() const -> StreamPlace { return {0, 0}; } - - [[nodiscard]] auto atBeginningOfFile(StreamPlace streamPlace) const -> bool { - return streamPlace.getOffset() == 0; - } - - [[nodiscard]] auto addOffset(StreamPlace streamPlace, uint32_t offset) const -> StreamPlace { - return {streamPlace.getOffset() + offset, streamPlace.getSpineIdx()}; - }; - - [[nodiscard]] virtual auto goBackward(StreamPlace streamPlace, uint32_t offset) -> StreamPlace { - return {(streamPlace.getOffset() >= offset) ? streamPlace.getOffset() - offset : 0, - streamPlace.getSpineIdx()}; - } - - [[nodiscard]] virtual auto toStreamPlace(const std::shared_ptr place) const - -> StreamPlace = 0; - - [[nodiscard]] virtual auto toPlace(const StreamPlace &streamPlace) const - -> std::shared_ptr = 0; - - [[nodiscard]] virtual auto streamSize() const -> size_t = 0; - - virtual void setStream(std::shared_ptr stream) = 0; - - [[nodiscard]] virtual auto getStream() -> std::shared_ptr = 0; - - [[nodiscard]] virtual inline auto isAnArticle() const -> bool { return true; } - - [[nodiscard]] virtual inline auto isReady() const -> bool { return false; } - - [[nodiscard]] virtual inline auto getTxtOffset() const -> uint32_t { return 0; }; - - void setFont(Font *font) { font_ = font; } - - void setRect(DisplayRect rect) { rect_ = rect; } - -protected: - Font *font_{nullptr}; - DisplayRect rect_; - Tokens &tokens_; - Pager &pager_; - - // Computed TEXT Token character size that will fit on a line depending - // on the current font size and line width - uint16_t maxTextTokenCharSize_{10}; -}; diff --git a/src/Renderers/Tokens.cpp b/src/Renderers/Tokens.cpp deleted file mode 100644 index 6e7e435a1..000000000 --- a/src/Renderers/Tokens.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "Tokens.hpp" - -// Add a token to the tokens list. The null terminated string is *not* copied to the pool and is -// expected to be a literal. The token may not be added if there is no space remaining in the tokens -// array. -auto Tokens::add(Token &token, const char *str, uint8_t length) -> bool { - if (!isFull()) { - if (str != nullptr) { - token.cStr = str; - token.length = length; - } - - if ((token.pixWidth == 0) && (token.cStr != nullptr) && (token.cStr[0] != '\n')) { - token.pixWidth = (token.tokenId == TokenId::ITEXT) - ? font_->getTextWidth(token.cStr) - : font_->getTextWidthQuick(token.cStr); - // log_w("Token width of %s is %" PRIu16, token.cStr, token.pixWidth); - } - - if constexpr (REND_DEBUG) { - showToken(tokensTailIdx_); - } - - advanceTokenTail(); - - // REND_WLOG("After: tokens count: %" PRIu16 ", cStr pool count: %" PRIu16, - // getTokensCount(), - // getCStrPoolCount()); - } else { - log_e("tokens_[] too small %d!: (%s)", TOKENS_SIZE, - tokensIsFull() ? "TOKENS" : "CSTR_POOL"); - REND_EXIT_SIM; - return false; - } - return true; -} - -// Add a byte to the token string. The byte is copied to the pool. The token's length is adjusted. A -// call to endCStr() will be required to complete the null terminated string. -void Tokens::addByte(Token &token, uint8_t b) { - // REND_WLOG("Adding byte %02" PRIX8 "H to last token at %" PRIu16 ".", b, cStrPoolCount_); - if (!cStrIsFull()) { - if (token.cStr == nullptr) { - token.cStr = &cStrPool_[cStrPoolTailIdx_]; - token.length = 0; - } - cStrPool_[cStrPoolTailIdx_] = b; - token.length++; // must be done BEFORE the call to incrCStrPoolTailIdx() - incrCStrPoolTailIdx(token); - } else { - log_e("cStrPool_[] is too small!"); - REND_EXIT_SIM; - } -} - -void Tokens::addBytes(Token &token, const char *str) { - while (*str) { - addByte(token, *str++); - } -} - -void Tokens::insertBytesAtEnd(Token &token, const char *str) { - if (token.cStr != nullptr) { - cStrPoolTailIdx_ = (cStrPoolTailIdx_ == 0) ? (CSTR_POOL_SIZE - 1) : cStrPoolTailIdx_ - 1; - - while (*str) { - addByte(token, *str++); - } - endOfCStr(); - } -} - -// Associate the string to the token. The string will be copied to the pool. No other characters can -// be added to the token after it. -void Tokens::addStr(Token &token, const std::string &str) { - // REND_WLOG("Adding byte %02" PRIX8 "H to last token at %" PRIu16 ".", b, cStrPoolCount_); - - uint16_t len = str.length(); - if ((getCStrPoolCount() + len) < (CSTR_POOL_SIZE - 2)) { - const char *s = str.c_str(); - if (token.cStr == nullptr) { - token.cStr = &cStrPool_[cStrPoolTailIdx_]; - } - while (len--) { - cStrPool_[cStrPoolTailIdx_] = *s++; - token.length++; // must be done BEFORE the call to incrCStrPoolTailIdx(). DO NOT - // OPTIMIZE! - incrCStrPoolTailIdx(token); - } - endOfCStr(); - - if constexpr (REND_DEBUG) { - if (strlen(token.cStr) != token.length) { - REND_ELOG("Hum... token length wrong: %zu vs %" PRIu16, strlen(token.cStr), - token.length); - } - // log_d("After: tokensCount_: %" PRIu16 ", token length: %" PRIu16 - // ", cStrPoolCount_: %" PRIu16, - // tokensCount_, token.length, cStrPoolCount_); - } - } else { - log_e("cStrPool_[] is too small!"); - REND_EXIT_SIM; - } -} - -// This will free space taken by the tokens that are part of the Head Line. It is expected the -// fromTokenIdx is equal to the tokensHeadIdx_. -void Tokens::freeTokens(Idx fromTokenIdx, uint16_t count) { - - if constexpr (REND_DEBUG) { - // log_w("fromTokenIdx: %" PRIu16 " , count: %" PRIu16, fromTokenIdx, count); - if (fromTokenIdx != tokensHeadIdx_) { - REND_ELOG("freeTokens received a bad fromTokenIdx (%" PRIu16 "), expected %" PRIu16, - fromTokenIdx, tokensHeadIdx_); - } - } - if (count > 0) { - // if constexpr (REND_DEBUG) { - // Idx lastTokenIdx = addToTokenIdx(fromTokenIdx, count); - // Idx tokenIdx = fromTokenIdx; - // while (tokenIdx != lastTokenIdx) { - // if ((tokens_[tokenIdx].tokenId == TokenId::TEXT) || - // (tokens_[tokenIdx].tokenId == TokenId::PICTURE)) { - // Idx poolIdx = (Idx)(tokens_[tokenIdx].cStr - cStrPool_.data()); - // Idx poolHead = cStrPoolHeadIdx_; - // if ((cStrPoolHeadIdx_ + tokens_[tokenIdx].length + 1) >= CSTR_POOL_SIZE) - // { - // cStrPoolHeadIdx_ = 0; - // } - // if (poolIdx != cStrPoolHeadIdx_) { - // log_e("cStrPool pointers discrepancies: %" PRIu16 " vs %" PRIu16, - // poolIdx, - // cStrPoolHeadIdx_); - // REND_EXIT_SIM; - // } - // break; - // } - // nextTokenIdx(tokenIdx); - // } - // } - - // Release pointers on pictures if exists, save the last entry where there is a pool string - // pointer - Idx idx = fromTokenIdx; - Idx lastTokenPoolIdx = fromTokenIdx; - TokenId id = tokens_[idx].tokenId; - - for (int i = 0; i < count; i++, nextTokenIdx(idx)) { - if (tokens_[idx].picture != nullptr) { - tokens_[idx].picture.reset(); - } - // Not all tokens that are using the pool - if ((static_cast(id) & POOL_TOKENS) != 0) { - lastTokenPoolIdx = idx; - id = tokens_[idx].tokenId; - } - } - - // Search the last token that contains a pointer inside the pool - int32_t poolCount = getCStrPoolCount(); - // Idx tokenIdx = addToTokenIdx(fromTokenIdx, count - 1); - // TokenId id = tokens_[tokenIdx].tokenId; - // while ((tokenIdx != fromTokenIdx) && (id != TokenId::TEXT) && (id != TokenId::PICTURE) && - // (id != TokenId::EPICTURE)) { - // prevTokenIdx(tokenIdx); - // id = tokens_[tokenIdx].tokenId; - // } - // Not all tokens are using the pool - if ((static_cast(id) & POOL_TOKENS) != 0) { - // Free space in string pool - Idx poolIdx = tokens_[lastTokenPoolIdx].cStr - cStrPool_.data(); - - if constexpr (REND_DEBUG) { - if (poolIdx >= CSTR_POOL_SIZE) { - REND_ELOG("poolIdx too large: %" PRIu16 ", must be smaller than %" PRIu16, - poolIdx, CSTR_POOL_SIZE); - } - } - - cStrPoolHeadIdx_ = addToCStrPoolIdx(poolIdx, tokens_[lastTokenPoolIdx].length + 1); - } - - if (poolCount < getCStrPoolCount()) { - log_e("cStrPool is larger than before freeing space! old size: %" PRIi32 - ", new: %" PRIu16, - poolCount, getCStrPoolCount()); - REND_EXIT_SIM; - } - - // REND_WLOG("cStrPool reduced from %" PRIi32 " to %" PRIi32, poolCount, - // getCStrPoolCount()); - - tokensHeadIdx_ = addToTokenIdx(fromTokenIdx, count); - - if constexpr (REND_DEBUG) { - if (getTokenCount() == 0) { - REND_ELOG("Hum... Tokens is empty!!"); - } - } - } -} - -// debugging methods - -void Tokens::showToken(Idx tokenIdx) const { - if constexpr (REND_DEBUG) { - const Token &token = tokens_[tokenIdx]; - log_d("TokenIdx: %d, TokenId: %" PRIu8 ", streamPlace: %s, width: %" PRIu16 - //", height: %" PRIu16 - ", Length: %" PRIu16, - tokenIdx, (uint8_t)token.tokenId, token.streamPlace.str().c_str(), token.pixWidth, - // token.height, - token.length); - if (static_cast(token.tokenId) & - (static_cast(TokenId::TEXT) | static_cast(TokenId::ITEXT) | - static_cast(TokenId::HTEXT) | static_cast(TokenId::NTEXT))) { - if (token.cStr != nullptr) { - log_d("Text Content: \"%s\"", token.cStr); - } else { - log_e("Text content is null!!"); - } - } - } -} - -void Tokens::show() const { - if constexpr (REND_DEBUG) { - for (Idx tokenIdx = tokensHeadIdx_; tokenIdx != tokensTailIdx_; nextTokenIdx(tokenIdx)) { - showToken(tokenIdx); - } - } -} diff --git a/src/Renderers/Tokens.hpp b/src/Renderers/Tokens.hpp deleted file mode 100644 index d84133201..000000000 --- a/src/Renderers/Tokens.hpp +++ /dev/null @@ -1,198 +0,0 @@ -#pragma once - -#include - -#include "RendererDefs.hpp" - -using namespace renderer_defs; - -class Tokens { -private: - Font *font_; - - TokensArray tokens_{}; - Idx tokensTailIdx_{0}; - Idx tokensHeadIdx_{0}; - - CStrPool cStrPool_{}; - Idx cStrPoolTailIdx_{0}; - Idx cStrPoolHeadIdx_{0}; - - uint16_t maxCStrPoolCount_{}; - uint16_t maxTokensCount_{}; - - // Increment the cStrPoolTail_ to the next location in the pool. As a null terminated string - // must remain contiguous, it cannot be split between the end and the start of the pool. If so, - // it is relocated to the beginning of the pool. - inline void incrCStrPoolTailIdx(Token &token) { - cStrPoolTailIdx_ = (cStrPoolTailIdx_ + 1) % CSTR_POOL_SIZE; - - if (cStrPoolTailIdx_ == 0) { - if ((getCStrPoolCount() + token.length) < (CSTR_POOL_SIZE - 2)) { - memcpy(cStrPool_.data(), token.cStr, token.length); - token.cStr = cStrPool_.data(); - cStrPoolTailIdx_ = token.length; - } else { - log_e("cStrPool_[] is too small!"); - REND_EXIT_SIM; - } - } - if constexpr (REND_STATS) { - uint16_t count = getCStrPoolCount(); - if (count > maxCStrPoolCount_) { - maxCStrPoolCount_ = count; - } - } - } - -public: - Tokens(Font *font) : font_(font) {} - [[nodiscard]] inline auto operator[](renderer_defs::Idx idx) const -> const Token & { - return tokens_[idx]; - } - - inline auto setFont(Font *font) -> void { font_ = font; } - - [[nodiscard]] inline auto tokenAt(renderer_defs::Idx idx) -> Token & { return tokens_[idx]; } - - [[nodiscard]] inline auto getMaxTokensCount() const -> uint16_t { return maxTokensCount_; } - [[nodiscard]] inline auto getMaxCStrPoolCount() const -> uint16_t { return maxCStrPoolCount_; } - - [[nodiscard]] inline auto getLastIdx() const -> Idx { - if constexpr (REND_DEBUG) { - if (isEmpty()) { - REND_ELOG("forgetLast(): tokens is empty!"); - } - } - - return (tokensTailIdx_ == 0) ? (TOKENS_SIZE - 1) : (tokensTailIdx_ - 1); - } - - [[nodiscard]] inline auto getLast() -> Token & { - - if constexpr (REND_DEBUG) { - if (isEmpty()) { - REND_ELOG("getLast(): tokens is empty!"); - } - } - return tokens_[getLastIdx()]; - } - - [[nodiscard]] inline auto getTokenCount() const -> uint16_t { - return (tokensHeadIdx_ <= tokensTailIdx_) - ? (tokensTailIdx_ - tokensHeadIdx_) - : (tokensTailIdx_ + (TOKENS_SIZE - tokensHeadIdx_)); - } - - // The caller must have checked that there is at least 2 entries in the tokens ring before using - // this method!! - [[nodiscard]] inline auto getBeforeLast() -> Token & { - if constexpr (REND_DEBUG) { - if (getTokenCount() < 2) { - REND_ELOG("getBeforeLast(): tokens not large enough!"); - } - } - return tokens_[(tokensTailIdx_ > 1) ? (tokensTailIdx_ - 2) - : (TOKENS_SIZE + tokensTailIdx_ - 2)]; - } - - inline void forgetLast() { - if constexpr (REND_DEBUG) { - if (tokensHeadIdx_ == tokensTailIdx_) { - REND_ELOG("forgetLast(): tokens is empty!"); - } - } - - if (tokensHeadIdx_ != tokensTailIdx_) { - prevTokenIdx(tokensTailIdx_); - } - } - - [[nodiscard]] inline auto isEmpty() const -> bool { return tokensHeadIdx_ == tokensTailIdx_; } - - [[nodiscard]] inline auto getCStrPoolCount() const -> uint16_t { - return (cStrPoolHeadIdx_ <= cStrPoolTailIdx_) - ? (cStrPoolTailIdx_ - cStrPoolHeadIdx_) - : (cStrPoolTailIdx_ + (CSTR_POOL_SIZE - cStrPoolHeadIdx_)); - } - - [[nodiscard]] inline auto cStrIsFull() const -> bool { - return (getCStrPoolCount() >= (CSTR_POOL_SIZE - 2)); - } - - [[nodiscard]] inline auto tokensIsFull() const -> bool { - return ((tokensTailIdx_ + 1) % TOKENS_SIZE) == tokensHeadIdx_; - } - - [[nodiscard]] inline auto isFull() const -> bool { return (cStrIsFull() || tokensIsFull()); } - - [[nodiscard]] auto add(Token &token, const char *str = nullptr, uint8_t length = 0) -> bool; - - void addByte(Token &token, uint8_t b); - void addBytes(Token &token, const char *str); - void addStr(Token &token, const std::string &str); - void insertBytesAtEnd(Token &token, const char *str); - - // Adds a null character at the end of a cStr that is in the cStr pool - inline void endOfCStr() { - cStrPool_[cStrPoolTailIdx_] = 0; - cStrPoolTailIdx_ = (cStrPoolTailIdx_ + 1) % CSTR_POOL_SIZE; - - if constexpr (REND_STATS) { - uint16_t count = getCStrPoolCount(); - if (count > maxCStrPoolCount_) { - maxCStrPoolCount_ = count; - } - } - } - - inline void clear() { - tokensTailIdx_ = cStrPoolTailIdx_ = 0; - tokensHeadIdx_ = cStrPoolHeadIdx_ = 0; - if constexpr (REND_STATS) { - maxTokensCount_ = maxCStrPoolCount_ = 0; - } - } - - void show() const; - void showToken(Idx tokenIdx) const; - - [[nodiscard]] inline auto getNextAvailable() -> Token & { - if constexpr (REND_DEBUG) { - if (isFull()) { - REND_ELOG("forgetLast(): tokens[] is full!"); - } - } - memset(reinterpret_cast(&tokens_[tokensTailIdx_]), 0, sizeof(Token)); - return tokens_[tokensTailIdx_]; - } - - inline void advanceTokenTail() { - - if constexpr (REND_DEBUG) { - if (isFull()) { - REND_ELOG("forgetLast(): tokens[] is full!"); - } - } - tokensTailIdx_ = (tokensTailIdx_ + 1) % TOKENS_SIZE; - - if constexpr (REND_STATS) { - uint16_t count = getTokenCount(); - if (count > maxTokensCount_) { - maxTokensCount_ = count; - } - } - } - - void freeTokens(Idx fromTokenIdx, uint16_t count); - - [[nodiscard]] inline auto addToTokenIdx(Idx idx, uint16_t val) const -> Idx { - return (idx + val) % TOKENS_SIZE; - } - [[nodiscard]] inline auto addToCStrPoolIdx(Idx idx, uint16_t val) const -> Idx { - return (idx + val) % CSTR_POOL_SIZE; - } - - inline void nextTokenIdx(Idx &i) const { i = addToTokenIdx(i, 1); } - inline void prevTokenIdx(Idx &i) const { i = (i == 0) ? (TOKENS_SIZE - 1) : (i - 1); } -}; diff --git a/src/Renderers/TxtTokenizer.cpp b/src/Renderers/TxtTokenizer.cpp deleted file mode 100644 index 302f77520..000000000 --- a/src/Renderers/TxtTokenizer.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "TxtTokenizer.hpp" - -// -// This loads from the stream character by character and populates -// the token buffer based on the TOKEN enum. -// After this is done, then we can do the heavy lifting of walking -// through the tokens, checking size, and rendering the text on -// a line by line basis. -// -// We track backwards because we need to double the amount read in a backwards -// read (2 pages instead of 1) -// -// Computes the number of word in the page. -// -// Returns the computed streamPlace where the page start really. -// -// IMPORTANT: The getFileLocation() method must be called only through this method. -auto TxtTokenizer::loadAndTokenizePage(StreamPlace fromPlace, bool backwards, StreamPlace *toPlace, - bool retrieveMenuParameters) - -> std::pair { - - uint32_t streamRelativeOffset = 0; - - lastToken_ = nullptr; - - fromPlace = getFileLocation(fromPlace); - - if (fromPlace.getOffset() == LOCATION_ERROR) { - return {getPlaceAtBeginningOfDocument(), {0, 0}}; - } - - if constexpr (REND_DEBUG) { - log_w("Starting tokenizer at streamPlace %s", fromPlace.str().c_str()); - if (backwards) { - log_w("Going up to place %s", toPlace->str().c_str()); - } - } - - bool pageCompleted = false; - - // Keep it DRY - - uint8_t curChar = stream_->getNextByte(); - - auto nextByte = [this, &curChar, &streamRelativeOffset]() -> void { - curChar = stream_->getNextByte(); - streamRelativeOffset++; - }; - - // If we go backwards, get rid of the first bytes that could be the end of a line/paragraph - if (backwards && (fromPlace.getOffset() != 0)) { - while ((curChar != 0) && (curChar != '\n')) { - nextByte(); - } - if (curChar == '\n') { - nextByte(); - } - } - - // REND_WLOG("First Character: %c (%02" PRIX8 "H)", - // (((curChar >= ' ') && (curChar < 127)) ? curChar : ' '), curChar); - - while ((!pageCompleted) && (curChar != 0) && (!tokens_.isFull())) { - - Token &newToken = tokens_.getNextAvailable(); - - newToken = {.cStr = nullptr, - .picture = nullptr, - .streamPlace = addOffset(fromPlace, streamRelativeOffset), - .pixWidth = 0, - .pixHeight = 0, - .length = 0, - .tokenId = TokenId::TEXT, - .spacesAfter = 0}; - - if (backwards && !tokens_.isEmpty() && - (newToken.streamPlace.getOffset() >= toPlace->getOffset())) { - pager_.completeLastLine(); - break; - } - - // REND_WLOG("Set the stream place to %s", - // addOffset(fromPlace, streamRelativeOffset).str().c_str()); - // REND_WLOG("Current Character: %c (%02" PRIX8 "H)", - // (((curChar >= ' ') && (curChar < 127)) ? curChar : ' '), curChar); - - if (curChar == '-') { - nextByte(); - if (curChar != '-') { - newToken.tokenId = TokenId::HYPHEN; - pageCompleted = addTokenToPage(newToken, backwards, "-", 1); - } else { - nextByte(); - if (curChar != '-') { - newToken.tokenId = TokenId::ENDASH; - pageCompleted = addTokenToPage(newToken, backwards, "--", 2); - } else { - nextByte(); - newToken.tokenId = TokenId::EMDASH; - pageCompleted = addTokenToPage(newToken, backwards, "---", 3); - } - } - lastToken_ = &newToken; - } else if (curChar > ' ') { - // newToken.tokenId = TokenId::TEXT; // already set at beginning of loop - lastToken_ = &newToken; - uint16_t textCharCount = 0; - - do { - if ((curChar <= 0x7FU) || (curChar >= 0xC0U)) { - textCharCount += 1; - } - if (!tokens_.cStrIsFull()) { - tokens_.addByte(newToken, curChar); - } else { - break; - } - if (curChar == '-') { - nextByte(); - break; - } - nextByte(); - } while ((curChar > ' ') && - ((textCharCount < maxTextTokenCharSize_) || (curChar >= 0x80U))); - - tokens_.endOfCStr(); - - // The last test of the while above is to get a complete UTF8 multi-bytes character - // if the maximum word size is reached - - pageCompleted = addTokenToPage(newToken, backwards); - } else if ((curChar == ' ') || (curChar == '\t')) { - - if (lastToken_ != nullptr) { - lastToken_->spacesAfter++; - pager_.addSpaceWidth(); - } - // newToken.tokenId = TokenId::SPACE; - // tokens_.setSpaceSize(newToken); - // pageCompleted = addTokenToPage(newToken, backwards, " ", 1); - nextByte(); - } else if (curChar == '\n') { - if (tokens_.getTokenCount() == 0) { - // of a newline character appears at the beginning of the parsing process, it must - // be an end of paragraph, as the preceding newline was absorbed by the preceding - // page - newToken = {.cStr = nullptr, - .picture = nullptr, - .streamPlace = addOffset(fromPlace, streamRelativeOffset), - .pixWidth = 0, - .pixHeight = 0, - .length = 0, - .tokenId = TokenId::ENDPARAGRAPH, - .spacesAfter = 0}; - pageCompleted = addTokenToPage(newToken, backwards, "\n", 1); - nextByte(); - // No two or more lines with only a \n are allowed. - while (curChar == '\n') { - nextByte(); - } - } else { - newToken.tokenId = TokenId::NEWLINE; - lastToken_ = nullptr; - pageCompleted = addTokenToPage(newToken, backwards, "\n", 1); - if (!pageCompleted) { - do { - nextByte(); - } while ((curChar != '\0') && (curChar < ' ') && (curChar != '\n') && - (curChar != '\t')); - if (curChar == '\n') { - // log_w("Stream relative offset:%" PRIu32, streamRelativeOffset); - if (backwards && !tokens_.isEmpty() && - ((fromPlace.getOffset() + streamRelativeOffset) >= - toPlace->getOffset())) { - pageCompleted = true; - break; - } - - Token &newToken = tokens_.getNextAvailable(); - - newToken = {.cStr = nullptr, - .picture = nullptr, - .streamPlace = addOffset(fromPlace, streamRelativeOffset), - .pixWidth = 0, - .pixHeight = 0, - .length = 0, - .tokenId = TokenId::ENDPARAGRAPH, - .spacesAfter = 0}; - pageCompleted = addTokenToPage(newToken, backwards, "\n", 1); - nextByte(); - // No two or more lines with only a \n are allowed. - while (curChar == '\n') { - nextByte(); - } - } - } - } - } else { - nextByte(); - } - } - - if (!pageCompleted && tokens_.cStrIsFull()) { - // There is a potential that the last token is not complete (e.g. a TEXT or ENDASH or - // EMDASH), so forget it - tokens_.forgetLast(); - } - - if (curChar == 0) { - pager_.setEndOfFile(); - } - - REND_WLOG("Computed Word Count: %" PRIu16, pager_.getWordCount()); - REND_WLOG("Tokens Count: %" PRIu16, tokens_.getTokenCount()); - - if (backwards) { - pager_.setFirstLineIdxBackward(); - pager_.setNextPagePlace(*toPlace); - } - - return {pager_.getStreamPlaceOfFirstLine(), pager_.getNextPageStreamPlace()}; -} - -// Must be called only by the loadAndTokenizePage(). -// From the fromPlace variable, seek into the file and synchronize -// the value at a valid UTF8 location. An UTF8 character start with -// a first byte MSBit == 0 or the two MSBits == 11 -// -// returns an updated fromPlace location or the LOCATION_ERROR value. -auto TxtTokenizer::getFileLocation(StreamPlace fromPlace) -> StreamPlace { - - // if received streamPlace is larger than the stream size, reset it to - // the beginning of file - if (fromPlace.getOffset() >= stream_->size()) { - fromPlace = getPlaceAtBeginningOfDocument(); - } - - // When the offset is 0, we are at a the beginning of a file, so it’s a word boundary. - bool atWordBoundary = (fromPlace.getOffset() == 0); - if (!atWordBoundary) { - // We need to start at the previous byte to check for a word boundary. - fromPlace.decOffset(); - } - - if (!stream_->seek(fromPlace.getOffset())) { - log_e("failed to seek at streamPlace %s in file!", fromPlace.str().c_str()); - REND_EXIT_SIM; - } else { - uint8_t ch = 0; - uint16_t count = 0; - - // If not at word boundary, go forward and stop once we found one. - // Word boundaries are: - // - any byte with value that is lower or equal to a space (0x20). This cover any - // of space, tab, newline or any other special characters. - // - any byte with value '-': this is a partial one as many hyphen can be present. - // (see below the while loop) - while ((!atWordBoundary) && stream_->available()) { - ch = stream_->read(); - atWordBoundary = ((ch <= ' ') || (ch == '-')); - fromPlace.incOffset(); - count += 1; - } - - if (ch == '-') { - // Pass hyphens only if found in the first loop above, else it's going to be - // the first shown glyph on the page (hyphen, en-dash, em-dash). - // Single hyphen are normally shown glued to the preceeding word, but here there is - // no simple way to manage it. - if (count == 1) { - // Ensure that we skip all other hyphens that are present at word boundary - while (stream_->available()) { - ch = stream_->read(); - if (ch != '-') { - break; - } - fromPlace.incOffset(); - } - } else { - fromPlace.decOffset(); - } - stream_->seek(fromPlace.getOffset()); - } - - while (stream_->available()) { - ch = stream_->read(); - // A valid UTF8 character is ASCII or got it's first byte - // larger or equal to 0xC0 - if ((ch < 0x7FU) || (ch >= 0xC0U)) { - stream_->seek(fromPlace.getOffset()); - return fromPlace; - } - fromPlace.incOffset(); - if constexpr (REND_DEBUG) { - log_d("Synching to start of UTF8 character at streamPlace %s", - fromPlace.str().c_str()); - } - } - } - - fromPlace.setOffset(LOCATION_ERROR); - return fromPlace; -} diff --git a/src/Renderers/TxtTokenizer.hpp b/src/Renderers/TxtTokenizer.hpp deleted file mode 100644 index 6eb93587e..000000000 --- a/src/Renderers/TxtTokenizer.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "Tokenizer.hpp" - -class TxtTokenizer : public Tokenizer { - -public: - TxtTokenizer(Font *font, DisplayRect &rect, Tokens &tokens, Pager &pager) - : Tokenizer(font, rect, tokens, pager) {} - ~TxtTokenizer() override = default; - - [[nodiscard]] auto loadAndTokenizePage(StreamPlace fromPlace, bool backwards = false, - StreamPlace *toPlace = nullptr, - bool retrieveMenuParameters = false) - -> std::pair override; - - // ToDo: May have to be adjusted related to the upcoming Place definition - [[nodiscard]] auto toStreamPlace(const std::shared_ptr place) const - -> StreamPlace override { - if (place->isFor(DocType::TXT)) { - return {place->getOffset(), 0}; - } else { - log_e("Wrong Place Type: %" PRIu8, static_cast(place->getDocType())); - REND_EXIT_SIM; - } - - return {}; - } - - [[nodiscard]] inline auto toPlace(const StreamPlace &streamPlace) const - -> std::shared_ptr override { - return std::make_shared(streamPlace.getOffset()); - } - - [[nodiscard]] auto streamSize() const -> size_t override { - return stream_ != nullptr ? stream_->size() : 0; - } - - [[nodiscard]] auto isFor(DocType docType) const -> bool override { - return stream_->isFor(docType); - } - - void setStream(std::shared_ptr stream) override { stream_ = stream; } - - [[nodiscard]] auto getStream() -> std::shared_ptr override { return stream_; } - - [[nodiscard]] inline auto isReady() const -> bool override { return stream_ != nullptr; } - - [[nodiscard]] inline auto getTxtOffset() const -> uint32_t override { - return stream_->position(); - } - -private: - std::shared_ptr stream_; - - // Used to add space qty that follow a token - Token *lastToken_{}; - - [[nodiscard]] auto getFileLocation(StreamPlace fromPlace) -> StreamPlace; - - // Integrates the token to the tokens_[] array and to the page. - // Returns true if the page is ready. - inline auto addTokenToPage(Token &token, bool backwards = false, const char *str = nullptr, - uint8_t strSize = 0) -> bool { - if (tokens_.add(token, str, strSize)) { - // log_w("Token Id: %" PRIu8 " at idx: %" PRIu16, (uint8_t)token.tokenId, - // tokens_.getLastIdx()); - return !pager_.addLastTokenToPage(backwards) && !backwards; - } else { - return false; - } - } -}; diff --git a/src/Renderers/doc/EPub Formatting Heuristics.md b/src/Renderers/doc/EPub Formatting Heuristics.md deleted file mode 100644 index 6a8b586fa..000000000 --- a/src/Renderers/doc/EPub Formatting Heuristics.md +++ /dev/null @@ -1,187 +0,0 @@ -## EPub Formatting Heuristics: - -Here is the list of all supported features that are part of EPub's XHTML files, and how the Renderer is displaying those features on screen. - -**`

      ..

      `** : Those constitute normal paragraphs. Words are tokenized and presented as a single paragraph, terminated with a small vertical gap to separate paragraphs. Empty paragraphs are bypassed. An empty paragraph contains only spaces or **``** tags with or without spaces. They are often used to generate easy vertical space in title pages instead of using CSS. When part of a list or blockquote, paragraphs are indented at the appropriate level, supporting nested lists and blockquotes. - -Partially working: For header tags (**`

      ..

      `**), the first line of the following paragraph will be kept on the same page: if there is not enough space at the end of the current page, the header will appears on next page. - -**``** : Hyperlink. The text that is part of this tag is put in square brackets unless it is located inside a superscript, or when there is no text, or if the text starts with **`(`**, **`[`**, **`<`**, or **`{`**. - -**``** : Pictures are normally loaded in memory by the Pager class when it is ready to generate the content to be displayed. The server generated EPub documents have the width and height of pictures inserted as attributes to the tag, this permits to optimize the moment the picture will be loaded when it is required (the width and height are sufficient to compute the page required space) as these pieces of information are part of the generated token. The pictures are loaded alone on a page when they are large enough to make it impossible to have paragraphs above or below them. Pictures are displayed using their native width and height. Only dithered pictures are selected for display. TBC - -**`
      1. `** : Ordered lists use a default number sequence starting at 1 for each item in the list. For **`
          `** tag, a "start=" attribute would set the sequence number to start the numbering. For **`
        1. `** tags, a "value=" attribute would set the number of that item, and subsequent list entries will increment from this number. When an item is taking more than one line, the following lines are indented to be aligned with the text of the first line located after the sequence number. - -**`
          • `** : Unordered lists are using bullets to mark the beginning of an item. The characters used as bullets are the **hyphen** for odd nested levels and the **lowercase o** for even nested levels (TBD: use nicer characters). When an item is taking more than one line, the following lines are indented to be aligned with the text of the first line located after the bullet. - -Nested lists are allowed up to 5 levels. Both Ordered and Unordered lists can be used in nested lists. Each level of a nested list is indented from the preceding level. Items can be composed of paragraphs and blockquotes and are indented as well. - -**``** : Table formatting is partial: a table will start with "{TABLE}" shown at the beginning, and "{END TABLE}" shown at the end. Every row is separated with an horizontal rule. Rows are usually having one or more "cells" into them that are normally being shown in different columns. The renderer keeps the cells in separate lines: The screen width, the complexity of showing properly columns render the task of displaying them properly beyond the current capability: often the same cell must be aligned inside many columns or many rows, CSS is often used to control various aspect of cell formatting. Could be revisited in a subsequent version if better rendering solutions are identified. - -**`
            `** : Left and Right indent are added to paragraphs that are part of a blockquote. The left and right indent are set to 12 and 8 pixels respectively for the first level. Blockquotes can be nested into each other. 8 pixels are added at additional level of indentation. - -**`Space`** : Multiple space characters in a row are replaced with a single space. Paragraphs that contain only space characters are eliminated from the output (Often used to do easy placement in pages instead of using CSS). - -**`U+2000..U+200F`** : These code points represent various space characters. They are all replaced with a usual space glyph. - -**`Hyphen`** : It is managed as a single-character word token. As lines are cut at word boundaries, this automatically offers the capability of splitting hyphenated compound words at the hyphens location. Endash and Emdash are expected to be entered as UTF8, or HTML character entities (**`–`**, **`—`**). - -**`
            `** : Used to generate a break in paragraphs. Can be used in many different contexts, one of which is to do easy formatting instead of using CSS. Bad formatting occurs on the Sol as the available screen space is smaller than usually available with electronic readers. It is then replaced with a space character. - -**`Tab`** : That character is managed as space characters (see the definition above). Could eventually be replaced with a displacement toward dedicated horizontal tabulator locations or other means. Would require many changes to the Pager class. - -**``** : Superscript / Subscript processing is limited to the decimal digits 0 to 9, lower / upper case ASCII characters and the following: **`* - + / = [ ] ( ) . ,`**. Other characters are ignored. TBD: Superscript text needs to be glued (i.e. no newline before it) to the preceding word, or the following word (i.e. no newline after it) if located at the beginning of a paragraph as usually used for notes at the end of a chapter or section. - -**`
            `** : Generates a horizontal rule. - -**`&...;`** : HTML character entities are translated to their UTF8 counterpart. Both numerical (e.g. **`Ӓ`** and textual (e.g. **` `**) descriptors are supported. All the supported descriptors as defined for HTML are described below. If a UTF8 character is present in the document, but not available in the font, it will be displayed as a question mark inside a rounded rectangle. - -**` `** : Stand for "Not Breakable Space". Considered as part of a word, as for the other HTML character entities. - -**`U+FFF0..U+FFFF`** : These code points are ignored. All other code points that are not available in the Sol font are shown as a question mark inside a rounded rectangle. - -### Some notes: - -- As we are not using CSS to format the content, there is many cases for which the pages could be showing things that are not expected. For example, items could be made hidden or displayed with specific characteristics depending on some information in CSS, like @media type that we don't interpret. Pictures could be shown in different flavors, sometimes being show two or three times in a row, the CSS-based selection not being interpreted. - -- In Books, some formatting is made using HTML tags, doing a nice job for larger displays, but not that good for small screens like the Sol glasses. - -- The Sol's font doesn't contain all possible characters. When a character is not available in the font, the driver replace it with a question mark inside a rounded rectangle. - -- Some books are using specialized embedded fonts to display nice drawing effects in paragraphs or between them. If those drawings are encoded using the same byte code used for normal characters, those normal characters will then appear instead. - -### Supported HTML Character Entities - -| Name | Description | -|:-------:|------------------------------------------ -| nbsp | no-break space = non-breaking space, U+00A0 ISOnum -| iexcl | inverted exclamation mark, U+00A1 ISOnum -| cent | cent sign, U+00A2 ISOnum -| pound | pound sign, U+00A3 ISOnum -| curren | currency sign, U+00A4 ISOnum -| yen | yen sign = yuan sign, U+00A5 ISOnum -| brvbar | broken bar = broken vertical bar, U+00A6 ISOnum -| sect | section sign, U+00A7 ISOnum -| uml | diaeresis = spacing diaeresis, U+00A8 ISOdia -| copy | copyright sign, U+00A9 ISOnum -| ordf | feminine ordinal indicator, U+00AA ISOnum -| laquo | left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum -| not | not sign = angled dash, U+00AC ISOnum -| shy | soft hyphen = discretionary hyphen, U+00AD ISOnum -| reg | registered sign = registered trade mark sign, U+00AE ISOnum -| macr | macron = spacing macron = overline = APL overbar, U+00AF ISOdia -| deg | degree sign, U+00B0 ISOnum -| plusmn | plus-minus sign = plus-or-minus sign, U+00B1 ISOnum -| sup2 | superscript two = superscript digit two = squared, U+00B2 ISOnum -| sup3 | superscript three = superscript digit three = cubed, U+00B3 ISOnum -| acute | acute accent = spacing acute, U+00B4 ISOdia -| micro | micro sign, U+00B5 ISOnum -| para | pilcrow sign = paragraph sign, U+00B6 ISOnum -| middot | middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum -| cedil | cedilla = spacing cedilla, U+00B8 ISOdia -| sup1 | superscript one = superscript digit one, U+00B9 ISOnum -| ordm | masculine ordinal indicator, U+00BA ISOnum -| raquo | right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum -| frac14 | vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum -| frac12 | vulgar fraction one half = fraction one half, U+00BD ISOnum -| frac34 | vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum -| iquest | inverted question mark = turned question mark, U+00BF ISOnum -| Agrave | latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 -| Aacute | latin capital letter A with acute, U+00C1 ISOlat1 -| Acirc | latin capital letter A with circumflex, U+00C2 ISOlat1 -| Atilde | latin capital letter A with tilde, U+00C3 ISOlat1 -| Auml | latin capital letter A with diaeresis, U+00C4 ISOlat1 -| Aring | latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 -| AElig | latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 -| Ccedil | latin capital letter C with cedilla, U+00C7 ISOlat1 -| Egrave | latin capital letter E with grave, U+00C8 ISOlat1 -| Eacute | latin capital letter E with acute, U+00C9 ISOlat1 -| Ecirc | latin capital letter E with circumflex, U+00CA ISOlat1 -| Euml | latin capital letter E with diaeresis, U+00CB ISOlat1 -| Igrave | latin capital letter I with grave, U+00CC ISOlat1 -| Iacute | latin capital letter I with acute, U+00CD ISOlat1 -| Icirc | latin capital letter I with circumflex, U+00CE ISOlat1 -| Iuml | latin capital letter I with diaeresis, U+00CF ISOlat1 -| ETH | latin capital letter ETH, U+00D0 ISOlat1 -| Ntilde | latin capital letter N with tilde, U+00D1 ISOlat1 -| Ograve | latin capital letter O with grave, U+00D2 ISOlat1 -| Oacute | latin capital letter O with acute, U+00D3 ISOlat1 -| Ocirc | latin capital letter O with circumflex, U+00D4 ISOlat1 -| Otilde | latin capital letter O with tilde, U+00D5 ISOlat1 -| Ouml | latin capital letter O with diaeresis, U+00D6 ISOlat1 -| times | multiplication sign, U+00D7 ISOnum -| Oslash | latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 -| Ugrave | latin capital letter U with grave, U+00D9 ISOlat1 -| Uacute | latin capital letter U with acute, U+00DA ISOlat1 -| Ucirc | latin capital letter U with circumflex, U+00DB ISOlat1 -| Uuml | latin capital letter U with diaeresis, U+00DC ISOlat1 -| Yacute | latin capital letter Y with acute, U+00DD ISOlat1 -| THORN | latin capital letter THORN, U+00DE ISOlat1 -| szlig | latin small letter sharp s = ess-zed, U+00DF ISOlat1 -| agrave | latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 -| aacute | latin small letter a with acute, U+00E1 ISOlat1 -| acirc | latin small letter a with circumflex, U+00E2 ISOlat1 -| atilde | latin small letter a with tilde, U+00E3 ISOlat1 -| auml | latin small letter a with diaeresis, U+00E4 ISOlat1 -| aring | latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 -| aelig | latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 -| ccedil | latin small letter c with cedilla, U+00E7 ISOlat1 -| egrave | latin small letter e with grave, U+00E8 ISOlat1 -| eacute | latin small letter e with acute, U+00E9 ISOlat1 -| ecirc | latin small letter e with circumflex, U+00EA ISOlat1 -| euml | latin small letter e with diaeresis, U+00EB ISOlat1 -| igrave | latin small letter i with grave, U+00EC ISOlat1 -| iacute | latin small letter i with acute, U+00ED ISOlat1 -| icirc | latin small letter i with circumflex, U+00EE ISOlat1 -| iuml | latin small letter i with diaeresis, U+00EF ISOlat1 -| eth | latin small letter eth, U+00F0 ISOlat1 -| ntilde | latin small letter n with tilde, U+00F1 ISOlat1 -| ograve | latin small letter o with grave, U+00F2 ISOlat1 -| oacute | latin small letter o with acute, U+00F3 ISOlat1 -| ocirc | latin small letter o with circumflex, U+00F4 ISOlat1 -| otilde | latin small letter o with tilde, U+00F5 ISOlat1 -| ouml | latin small letter o with diaeresis, U+00F6 ISOlat1 -| divide | division sign, U+00F7 ISOnum -| oslash | latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 -| ugrave | latin small letter u with grave, U+00F9 ISOlat1 -| uacute | latin small letter u with acute, U+00FA ISOlat1 -| ucirc | latin small letter u with circumflex, U+00FB ISOlat1 -| uuml | latin small letter u with diaeresis, U+00FC ISOlat1 -| yacute | latin small letter y with acute, U+00FD ISOlat1 -| thorn | latin small letter thorn, U+00FE ISOlat1 -| yuml | latin small letter y with diaeresis, U+00FF ISOlat1 -| quot | quotation mark, U+0022 ISOnum -| amp | ampersand, U+0026 ISOnum -| lt | less-than sign, U+003C ISOnum -| gt | greater-than sign, U+003E ISOnum -| apos | apostrophe = APL quote, U+0027 ISOnum -| OElig | latin capital ligature OE, U+0152 ISOlat2 -| oelig | latin small ligature oe, U+0153 ISOlat2 -| Scaron | latin capital letter S with caron, U+0160 ISOlat2 -| scaron | latin small letter s with caron, U+0161 ISOlat2 -| Yuml | latin capital letter Y with diaeresis, U+0178 ISOlat2 -| circ | modifier letter circumflex accent, U+02C6 ISOpub -| tilde | small tilde, U+02DC ISOdia -| ensp | en space, U+2002 ISOpub -| emsp | em space, U+2003 ISOpub -| thinsp | thin space, U+2009 ISOpub -| zwnj | zero width non-joiner, U+200C NEW RFC 2070 -| zwj | zero width joiner, U+200D NEW RFC 2070 -| lrm | left-to-right mark, U+200E NEW RFC 2070 -| rlm | right-to-left mark, U+200F NEW RFC 2070 -| ndash | en dash, U+2013 ISOpub -| mdash | em dash, U+2014 ISOpub -| lsquo | left single quotation mark, U+2018 ISOnum -| rsquo | right single quotation mark, U+2019 ISOnum -| sbquo | single low-9 quotation mark, U+201A NEW -| ldquo | left double quotation mark, U+201C ISOnum -| rdquo | right double quotation mark, U+201D ISOnum -| bdquo | double low-9 quotation mark, U+201E NEW -| dagger | dagger, U+2020 ISOpub -| Dagger | double dagger, U+2021 ISOpub -| permil | per mille sign, U+2030 ISOtech -| lsaquo | single left-pointing angle quotation mark, U+2039 ISO proposed -| rsaquo | single right-pointing angle quotation mark, U+203A ISO proposed -| euro | euro sign, U+20AC NEW - -[The End] diff --git a/src/Renderers/doc/Menu Parameters Retrieval.md b/src/Renderers/doc/Menu Parameters Retrieval.md deleted file mode 100644 index 8122aa9a3..000000000 --- a/src/Renderers/doc/Menu Parameters Retrieval.md +++ /dev/null @@ -1,43 +0,0 @@ -## Menu Parameters Definition and Retrieval - -The Renderer now supplies the capability of inserting menu parameters' lists in an EPub document. This feature permits the main sol application to interrupt the presentation of pages to the user, display a menu to him, and continue to present the pages where the sequence of pages was interrupted. - -To do so, a menu parameters' list definition must be inserted at least once anywhere inside the ` .. ` of one of the spine's XHTML file that is part of the EPub document. The definition takes the following form: - -```xml -
            • Value 1
            • ...
            -``` - -The `Renderer::renderPage()` method takes a new fourth parameter named `bool retrieveMenuParameters`. When true, the Renderer will take action to retrieve the parameters in a `MenuParameters` type variable (that is defined as `SpiramOrderedMap`), if the form is detected in the current page being parsed to be shown. - -If a valid MenuParameters has been retrieved, the renderPage() method will return the same page location (e.g. the first parameter of the `Renderer::renderPage()` call) that was requested. The page will not be shown on screen. The application can then retrieve the menu parameters using the `auto Renderer::getMenuParameters() -> const MenuParameters &` method. The ordered map will not be empty if a valid parameters list was retrieved. - -Once the Sol application completes the processing of the menu, it can then request the interrupted page rendering to be done again. It is important to pass a `retrieveMenuParameters` parameter set to false. If not, the renderer will loop continuously on the menu parameters' list retrieval as described above. - -In the `guy/menu-parameters-retrieval` branch, the `ReadableScreen` class has been modified to demonstrate the capability. When a menu parameters' list is retrieved, it is shown in the log, the interrupted page is then requested again as described above (here is the modification done at line 281): - -```c++ - // Regular Reading - bool retrieveMenuParameters = true; - do { - std::tie(newPlace_, darkPicturePercent) = - renderer_.renderPage(readable_->place, 0, false, retrieveMenuParameters); - if (retrieveMenuParameters) { - const MenuParameters &menuParameters = renderer_.getMenuParameters(); - if (!menuParameters.empty()) { - log_d("Menu Parameters:"); - for (auto ¶m : menuParameters) { - log_d(" %s: %s", param.first.c_str(), param.second.c_str()); - } - retrieveMenuParameters = false; - continue; - } - } - - break; - } while (true); -``` - -If more than one menu parameters' list is required in a same document, both lists must be far enough to be on different displayed pages in the document. If not, the second menu parameters' list will not be retrievable. - -[the End] diff --git a/src/Renderers/doc/Sol Font content.md b/src/Renderers/doc/Sol Font content.md deleted file mode 100644 index eb0e77eb7..000000000 --- a/src/Renderers/doc/Sol Font content.md +++ /dev/null @@ -1,40 +0,0 @@ -## Sol Font Content - -The **Sol Font** is based on the **NotoSans Lite** True Type Font (TTF). The TTF font file is kept with the Sol Font in the src/UI/Fonts/IBMFFont folder. - -Using the **IBMFFontEditor**, parts of the NotoSans font are extracted to create the Sol Font. The following portions are currently part of the Sol Font: - -| Name | Range -|-----------------------------|:-----------------------------------------:| -| Basic Latin | (U+0020 .. U+007F) -| Latin-1 Supplement | (U+0080 .. U+00FF) -| Latin Extended-A | (U+0100 .. U+017F) -| Latin Extended-B | (U+0180 .. U+024F) -| Spacing Modifier Letters | (U+02B0 .. U+02FF) -| Greek and Coptic | (U+0370 .. U+03FF) -| Cyrillic | (U+0400 .. U+04FF) -| General Punctuation | (U+2000 .. U+206F) -| Superscripts and Subscripts | (U+2070 .. U+209F) -| Currency Symbols | (U+20A0 .. U+20CF) -| User Defined | (U+E000 .. U+E07C) -| Alphabetic Presentation Forms | (U+FB00 .. U+FB4F) - - -### User Defined characters - -The following table lists the characters present in the user defined portion of the font: - -| Range | Letters | -|:---------:|--------------| -| (U+E000 .. U+E004) | Sol Icon, < > ^ v -| (U+E005 .. U+E01E) | Subscript A..Z -| (U+E01F .. U+E021) | Subscript , . / -| (U+E023 .. U+E03C) | Subscript a..z -| (U+E03D .. U+E040) | Subscript * cross [ ] -| (U+E041 .. U+E05A) | Superscript A..Z -| (U+E05B .. U+E05D) | Superscript , . / -| (U+E05E .. U+E05E) | Unknown character (question mark in rounded rectangle) -| (U+E05F .. U+E078) | Superscript a..z -| (U+E079 .. U+E07C) | Superscript * cross [ ] - -[The End] \ No newline at end of file diff --git a/src/Storage/EPubAdditions.cpp b/src/Storage/EPubAdditions.cpp new file mode 100644 index 000000000..8efc3d3fa --- /dev/null +++ b/src/Storage/EPubAdditions.cpp @@ -0,0 +1,228 @@ +#include "EPubAdditions.hpp" + +auto HasSummary(const std::shared_ptr &epubFile, const std::string_view &spineHref) + -> bool { + return epubFile->hasEpubAddition(spineHref, "summary"); +} + +auto GetSummaryContent( + const std::shared_ptr &epubFile, const std::shared_ptr &placeInReadable, + std::shared_ptr &placeInSummary, + const std::shared_ptr &partialChapterSummary, + bool includeUnread) -> SpiramString { + + auto placeHref = placeInReadable->getHRef(); + if (!HasSummary(epubFile, placeHref) && partialChapterSummary == nullptr) { + log_e("This chapter does not have a summary."); + return "This chapter does not have a summary."; + } + + // Load the summary xhtml content + auto chapterHref = epubFile->getChapterFirstHref(placeHref); + auto chapterIdx = epubFile->getChapterIdx(chapterHref); + if (chapterIdx == -1) { + log_e("Unable to find chapter index for %s", chapterHref.c_str()); + return "This chapter does not have a summary."; + } + log_w("Chapter href: %s, chapter idx: %d, toc href: %s", chapterHref.c_str(), chapterIdx, + std::string(epubFile->getToc()[chapterIdx].href).c_str()); + if (std::string(epubFile->getToc()[chapterIdx].href) != chapterHref) { + chapterHref = std::string(epubFile->getToc()[chapterIdx].href); + } + + std::string partialSummaryChapterHRef; + if (partialChapterSummary != nullptr && !partialChapterSummary->text.empty() && + partialChapterSummary->text != "null") { + auto summarySpineHref = partialChapterSummary->ePubPlace->getHRef(); + partialSummaryChapterHRef = epubFile->getChapterFirstHref(summarySpineHref); + } + + log_d("Loading summary for chapter href: %s, chapter idx: %d", chapterHref.c_str(), chapterIdx); + SpiramString summaryText; + bool foundAnySummaries = false; + for (const auto &chapter : epubFile->getToc()) { + auto sameChapter = chapter.href == chapterHref; + auto tocChapterIdx = epubFile->getChapterIdx(std::string(chapter.href)); + if (tocChapterIdx == -1) { + log_e("Unable to find chapter index for %s", chapter.href.c_str()); + continue; + } + auto futureChapter = tocChapterIdx > chapterIdx; + auto pastOrSameChapter = tocChapterIdx <= chapterIdx; + + if (pastOrSameChapter) { + placeInSummary->setOffset(summaryText.length()); + } + + if (futureChapter && !includeUnread) { + break; + } + + if (sameChapter && !includeUnread && partialSummaryChapterHRef == chapter.href) { + summaryText += "Summary: " + chapter.title; + summaryText += StringFormat(" (%d%% read)\n\n", partialChapterSummary->readPct); + summaryText += partialChapterSummary->text; + summaryText += "\n\n"; + foundAnySummaries = true; + } else { + std::string currentChapterId = + epubFile->getManifestIdByHref(static_cast(chapter.href)); + auto currentSummaryPath = epubFile->getHrefByManifestId("summary-" + currentChapterId); + if (currentSummaryPath.empty()) { + log_w("No summary for %s", chapter.href.c_str()); + continue; + } + foundAnySummaries = true; + pugi::xml_document ¤tDoc = epubFile->getXHTMLFile(currentSummaryPath); + + for (auto node : currentDoc.child("html").child("body").children()) { + // If the node is p or other nodes with child nodes + if (std::string(node.name()) == "p") { + for (auto child : node.children()) { + if (child.type() == pugi::node_pcdata) { // If child is plain character data + summaryText += child.value(); + } + if (std::string(child.name()) == "br") { + summaryText += "\n"; + } + } + summaryText += "\n\n"; + } else if (std::string(node.name()) == "h1") { + summaryText += node.text().get(); + summaryText += "\n\n"; + } else if (std::string(node.name()) == "br") { + summaryText += "\n"; + } else { + summaryText += node.text().get(); + } + } + } + } + + if (!foundAnySummaries) { + return "This chapter does not have a summary."; + } + + return summaryText; +} + +auto HasDictionary(const std::shared_ptr &epubFile, const std::string_view &spineHref) + -> bool { + return epubFile->hasEpubAddition(spineHref, "dictionary"); +} + +auto GetDictionaryContent(const std::shared_ptr &epubFile, + const std::shared_ptr &placeInReadable, + std::shared_ptr &placeInDictionary) -> SpiramString { + + auto spineHref = placeInReadable->getHRef(); + if (!HasDictionary(epubFile, spineHref)) { + log_e("This chapter does not have any dictionary definitions."); + return "This chapter does not have any dictionary definitions."; + } + + // Load the dictionary xhtml content + int fileOffset = placeInReadable->getOffset(); + auto chapterIdx = epubFile->getChapterIdx(spineHref); + auto spineIdx = epubFile->getSpineIdx(spineHref); + auto spineId = epubFile->getManifestIdByHref(spineHref); + + if (chapterIdx == -1 || spineIdx == -1) { + log_e("Unable to find chapter index for %s", spineHref.c_str()); + return "This chapter does not have any dictionary definitions."; + } + + auto previousSpineOffset = 0; + if (epubFile->getToc()[chapterIdx].href != spineHref) { + spineHref = epubFile->getToc()[chapterIdx].href; + spineId = epubFile->getManifestIdByHref(spineHref); + // If we're looking at a spine item that doesn't match the chapter, we need to calculate the + // offset to match how the server is calculating word locations, which is cumulatively + // adding the size of each spine item + previousSpineOffset = epubFile->getOffsetPreviousSpineItemsInChapter(spineIdx); + log_d("Calculated previousSpineOffset: %d", previousSpineOffset); + } + + // Convert chapterId to dictionary file path + std::string dictionaryId = "dictionary-" + spineId; + auto dictionaryPath = epubFile->getHrefByManifestId(dictionaryId); + // int currentOffset = findCharOffsetAtFileOffset(spineHref, fileOffset); + int currentOffset = fileOffset + previousSpineOffset; + pugi::xml_document &doc = epubFile->getXHTMLFile(dictionaryPath); + pugi::xml_node closestNode; + pugi::xml_node prevNode; + int closestLocation = -1; + + log_d("Loading dictionary for chapter: %s. (href: %s, chapter idx: %d) Current offset: %d " + "(file:%d+prev:%d)", + spineId.c_str(), spineHref.c_str(), chapterIdx, currentOffset, fileOffset, + previousSpineOffset); + // Search for the closest data-locations attribute closest to currentOffset without going over + for (auto p : doc.child("html").child("body").children("p")) { + int location = p.attribute("data-locations").as_int(); + log_d("Found dictionary location: %d", location); + + // If currentOffset is 0, set the closestNode as the first node + if (currentOffset == 0 || closestNode == nullptr) { + closestNode = p; + closestLocation = location; + } + + if (location <= currentOffset) { + // Still finding nodes behind the user's placemark + closestLocation = location; + prevNode = closestNode; + closestNode = p; + } else { + // If we've gone over and the location is greater than the offset, we're good with + // what's left. Clearing prevNode means we haven't exhausted all definitions and will + // still have some to show. + prevNode = pugi::xml_node(); + break; + } + } + + // If closestNode refers to the last entry and there was no break, backtrack one entry so we + // always show one dictionary definition + if (prevNode) { + closestNode = prevNode; + closestLocation = closestNode.attribute("data-locations").as_int(); + } + + SpiramString dictionaryText; + SpiramString beforeClosestNodeText; + if (closestLocation != -1) { + // Construct the text with the XML before the closest node + for (auto p : doc.child("html").child("body").children("p")) { + if (p == closestNode) { + if (closestLocation < currentOffset) { + beforeClosestNodeText += StringFormat("%s\n\n", p.child_value()); + } else { + dictionaryText += StringFormat("%s\n\n", p.child_value()); + } + break; + } + beforeClosestNodeText += StringFormat("%s\n\n", p.child_value()); + } + + // Construct the text with the XML after the closest node + for (auto p = closestNode.next_sibling(); p; p = p.next_sibling()) { + dictionaryText += StringFormat("%s\n\n", p.child_value()); + } + + // Set the place in the dictionary based on the length of the text before the closest node + uint32_t charOffset = beforeClosestNodeText.length(); + placeInDictionary = std::dynamic_pointer_cast(Place::factory(charOffset)); + + // Concatenate to form the full dictionaryText + dictionaryText = beforeClosestNodeText + dictionaryText; + + log_i("Found closest node at char offset: %" PRIu32 " for location: %d/%d", charOffset, + closestLocation, currentOffset); + } else { + log_e("Location not found in dictionaryString chapter: %s : %d", spineId.c_str(), + currentOffset); + } + + return dictionaryText; +} \ No newline at end of file diff --git a/src/Storage/EPubAdditions.hpp b/src/Storage/EPubAdditions.hpp new file mode 100644 index 000000000..7c3959ae4 --- /dev/null +++ b/src/Storage/EPubAdditions.hpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +#include "Models/UserReadablePartialChapterSummary.hpp" + +auto HasSummary(const std::shared_ptr &epubFile, const std::string_view &spineHref) + -> bool; + +auto GetSummaryContent( + const std::shared_ptr &epubFile, const std::shared_ptr &placeInReadable, + std::shared_ptr &placeInSummary, + const std::shared_ptr &partialChapterSummary, + bool includeUnread) -> SpiramString; + +auto HasDictionary(const std::shared_ptr &epubFile, const std::string_view &spineHref) + -> bool; + +auto GetDictionaryContent(const std::shared_ptr &epubFile, + const std::shared_ptr &placeInReadable, + std::shared_ptr &placeInDictionary) -> SpiramString; diff --git a/src/Storage/FileSystem.cpp b/src/Storage/FileSystem.cpp index cc758e1ba..49508c5e9 100644 --- a/src/Storage/FileSystem.cpp +++ b/src/Storage/FileSystem.cpp @@ -1,8 +1,11 @@ #include "FileSystem.hpp" -#include "EPub/EPubFile.hpp" +#include +#include + #include "Misc/GUnzipStream.hpp" #include "Misc/LookBackStream.hpp" +#include "Storage/SFileUnzipperStream.hpp" #include "sindarin-debug.h" auto FileSystem::newRendererStream(const char *path) const -> std::shared_ptr { @@ -15,28 +18,31 @@ auto FileSystem::newRendererStream(const char *path) const -> std::shared_ptr>(file, RendererStream::parseDocType(path)); } -auto FileSystem::newEPubRendererStream(const char *path, const std::shared_ptr &readable, - SolDrm &solDrm) const -> std::shared_ptr { +auto FileSystem::newEPubRendererStream(const char *path, + const std::shared_ptr &solDrm) const + -> std::shared_ptr { log_d("Attempting to open: %s", path); auto file = open(path); if (!file) { - log_e("Failed to open file: %s is the filename", path); + log_e("Failed to open file: %s", path); return nullptr; } - auto stream = std::make_shared(file, readable, solDrm); - if (!stream->isOpen()) { - log_e("Failed to open stream for: %s is the filename", path); + auto unzipperStream = std::make_shared(file); + auto epubFile = std::make_shared(unzipperStream, solDrm); + + if (!epubFile->isOpen()) { + log_e("Failed to open stream for: %s", path); return nullptr; } - return stream; + return std::make_shared(epubFile); } auto FileSystem::newGzipRendererStream(const char *path) const -> std::shared_ptr { log_d("Attempting to open: %s", path); auto file = open(path); if (!file) { - log_e("Failed to open file: %s is the filename", path); + log_e("Failed to open file: %s", path); return nullptr; } diff --git a/src/Storage/FileSystem.hpp b/src/Storage/FileSystem.hpp index 769bd0de1..af783a880 100644 --- a/src/Storage/FileSystem.hpp +++ b/src/Storage/FileSystem.hpp @@ -4,10 +4,10 @@ class EPubFile; +#include #include #include "Models/Readable.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/SFile.hpp" #include "Storage/SolDrm.hpp" @@ -24,8 +24,8 @@ class FileSystem { // Note: Allocates the ContainerStream on the heap, make sure to `delete` it somewhere. auto newRendererStream(const char *path) const -> std::shared_ptr; - auto newEPubRendererStream(const char *path, const std::shared_ptr &readable, - SolDrm &solDrm) const -> std::shared_ptr; + auto newEPubRendererStream(const char *path, const std::shared_ptr &solDrm) const + -> std::shared_ptr; auto newGzipRendererStream(const char *path) const -> std::shared_ptr; // Underlying methods to init and read data diff --git a/src/Storage/LocalStore.cpp b/src/Storage/LocalStore.cpp index a5a5c6c7d..adbc3b404 100644 --- a/src/Storage/LocalStore.cpp +++ b/src/Storage/LocalStore.cpp @@ -1,6 +1,8 @@ #include "LocalStore.hpp" #include +#include +#include #include #include #include @@ -27,9 +29,7 @@ #include "Misc/SemVer.hpp" #include "Models/BookQuestion.hpp" #include "Models/Json.hpp" -#include "Models/Place.hpp" #include "Models/ReadableBookmark.hpp" -#include "Renderers/RendererStream.hpp" #include "Version.hpp" #include "sindarin-debug.h" @@ -890,7 +890,7 @@ auto LocalStore::syncFromRemote(bool wifiConnected, bool bleConnected, SyncReaso log_d("Syncing books and articles"); progressCallback(ContentSyncState{.state = ContentSyncState::State::CheckingSync}); - std::string publicKey = solDrm_.publicKeyPem(); + std::string publicKey = solDrm_->publicKeyPem(); // Collect Kindle book UUIDs for sync StoredUuidsVector kindleUuids; diff --git a/src/Storage/LocalStore.hpp b/src/Storage/LocalStore.hpp index 08f7e799d..b134d346e 100644 --- a/src/Storage/LocalStore.hpp +++ b/src/Storage/LocalStore.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -8,7 +9,6 @@ #include "Misc/Telemetry.hpp" #include "Models/CheckSync.hpp" #include "Models/KindleBook.hpp" -#include "Models/Place.hpp" #include "Models/Readable.hpp" #include "Models/UserReadable.hpp" #include "Models/UserReadablePartialChapterSummary.hpp" @@ -84,7 +84,7 @@ using UserReadablesVector = SpiramVector; class LocalStore { public: LocalStore(FileSystem &fileSystem, PreferencesStore &prefs, Battery &battery, - Telemetry &telemetry, SolDrm &solDrm) + Telemetry &telemetry, const std::shared_ptr &solDrm) : fileSystem(fileSystem), prefs_(prefs), battery_(battery), telemetry_(telemetry), solDrm_(solDrm){}; LocalStore(LocalStore const &) = delete; @@ -193,7 +193,7 @@ class LocalStore { PreferencesStore &prefs_; Battery &battery_; Telemetry &telemetry_; - SolDrm &solDrm_; + const std::shared_ptr &solDrm_; ReadablesVector books_; ReadablesVector articles_; diff --git a/src/Storage/SFileUnzipperStream.hpp b/src/Storage/SFileUnzipperStream.hpp new file mode 100644 index 000000000..16ab9d108 --- /dev/null +++ b/src/Storage/SFileUnzipperStream.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "Storage/SFile.hpp" + +// Compatibility layer to hook up a SFile to an UnzipperFileStream +class SFileUnzipperStream : public UnzipperFileStream { +public: + explicit SFileUnzipperStream(const SFile &file) : file_(file) {} + ~SFileUnzipperStream() override = default; + + explicit operator bool() const override { return static_cast(file_); } + + void close() override { file_.close(); } + + auto read(uint8_t *buf, size_t size) -> size_t override { return file_.read(buf, size); } + + auto seek(off_t offset, EPFSSeekWhence whence) -> bool override { + SSeekMode mode = SSeekSet; + switch (whence) { + case EPFSSeekSet: + mode = SSeekSet; + break; + case EPFSSeekCur: + mode = SSeekCur; + break; + case EPFSSeekEnd: + mode = SSeekEnd; + break; + } + return file_.seek(static_cast(offset), mode); + } + + auto position() -> size_t override { return file_.position(); } + +private: + SFile file_; +}; diff --git a/src/Storage/SolDrm.hpp b/src/Storage/SolDrm.hpp index 55470f10c..124551611 100644 --- a/src/Storage/SolDrm.hpp +++ b/src/Storage/SolDrm.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -11,16 +12,17 @@ #include "Storage/PreferencesStore.hpp" -class SolDrm { +class SolDrm : public EPubDrm { public: SolDrm(PreferencesStore &prefs) : prefs_(prefs){}; - ~SolDrm(); + ~SolDrm() override; auto publicKeyPem() -> std::string; - auto parseKeyfile(const uint8_t *data, size_t length) -> std::optional>; + auto parseKeyfile(const uint8_t *data, size_t length) + -> std::optional> override; auto decryptInPlaceIfNecessary(const std::vector &aesKey, const std::shared_ptr &data, size_t length) - -> std::pair, size_t>; + -> std::pair, size_t> override; private: class FileHeader { diff --git a/src/UI/AboutScreen.hpp b/src/UI/AboutScreen.hpp index 677e9a15c..eb06ce0a1 100644 --- a/src/UI/AboutScreen.hpp +++ b/src/UI/AboutScreen.hpp @@ -1,9 +1,10 @@ #pragma once +#include + #include "Misc/Platform.hpp" #include "Misc/PowerManager.hpp" #include "Misc/SecretCode.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Bitmaps/sol-logo.hpp" #include "UI/Components/EinkView.hpp" diff --git a/src/UI/AlignmentScreen.hpp b/src/UI/AlignmentScreen.hpp index 1f3a8b9ba..6b798d48d 100644 --- a/src/UI/AlignmentScreen.hpp +++ b/src/UI/AlignmentScreen.hpp @@ -1,8 +1,9 @@ #ifndef ALIGNMENT_SCREEN_HPP #define ALIGNMENT_SCREEN_HPP +#include + #include "Misc/Battery.hpp" -#include "Renderers/Renderer.hpp" #include "Sensors/LightingController.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/BatteryScreen.hpp b/src/UI/BatteryScreen.hpp index 9ffa9d44e..3c909f93a 100644 --- a/src/UI/BatteryScreen.hpp +++ b/src/UI/BatteryScreen.hpp @@ -1,8 +1,9 @@ #ifndef BATTERY_SCREEN_HPP #define BATTERY_SCREEN_HPP +#include + #include "Misc/Battery.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/BookQuestionsScreen.cpp b/src/UI/BookQuestionsScreen.cpp index b69dc52fa..cc8f1298c 100644 --- a/src/UI/BookQuestionsScreen.cpp +++ b/src/UI/BookQuestionsScreen.cpp @@ -9,6 +9,7 @@ #endif #include "Communications/WiFi.hpp" +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" diff --git a/src/UI/BookQuestionsScreen.hpp b/src/UI/BookQuestionsScreen.hpp index 6300bcc09..9ab7f1707 100644 --- a/src/UI/BookQuestionsScreen.hpp +++ b/src/UI/BookQuestionsScreen.hpp @@ -1,12 +1,13 @@ #pragma once +#include +#include +#include + #include "Communications/SindarinAPI.hpp" -#include "EPub/EPubFile.hpp" #include "Misc/CancelFlag.hpp" #include "Misc/Telemetry.hpp" #include "Models/BookQuestion.hpp" -#include "Renderers/Renderer.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/FileSystem.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/ArrowLabelView.hpp" diff --git a/src/UI/Components/RendererView.hpp b/src/UI/Components/RendererView.hpp index da6720317..8be3c317e 100644 --- a/src/UI/Components/RendererView.hpp +++ b/src/UI/Components/RendererView.hpp @@ -1,6 +1,7 @@ #pragma once -#include "Renderers/Renderer.hpp" +#include + #include "UI/Components/BoxView.hpp" #include "UI/Components/EinkView.hpp" diff --git a/src/UI/ContentSyncScreen.cpp b/src/UI/ContentSyncScreen.cpp index ffa128043..dadac1520 100644 --- a/src/UI/ContentSyncScreen.cpp +++ b/src/UI/ContentSyncScreen.cpp @@ -4,10 +4,11 @@ #if !SIMULATOR #include "Communications/BLEManager.hpp" #endif +#include + #include "Communications/WiFi.hpp" #include "Misc/Platform.hpp" #include "Misc/RateLimiter.hpp" -#include "Renderers/RendererStream.hpp" ContentSyncScreen::ContentSyncScreen(DisplaySystem &display, LocalStore &localStore, PreferencesStore &prefs, Telemetry &telemetry, diff --git a/src/UI/ContentSyncScreen.hpp b/src/UI/ContentSyncScreen.hpp index 52c7e0403..1d6a2238e 100644 --- a/src/UI/ContentSyncScreen.hpp +++ b/src/UI/ContentSyncScreen.hpp @@ -1,9 +1,10 @@ #pragma once +#include + #include "Misc/CancelFlag.hpp" #include "Misc/PowerManager.hpp" #include "Misc/Telemetry.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/LocalStore.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Bitmaps/CheckMark.xbm" diff --git a/src/UI/DebugAboutScreen.hpp b/src/UI/DebugAboutScreen.hpp index 1c5f5ee83..7a1a8f341 100644 --- a/src/UI/DebugAboutScreen.hpp +++ b/src/UI/DebugAboutScreen.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include "Misc/Platform.hpp" -#include "Renderers/Renderer.hpp" #include "UI/Components/EinkView.hpp" #include "UI/Components/SingleLineTextView.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/DemoScreen.cpp b/src/UI/DemoScreen.cpp index 82b539aef..b6160bdd9 100644 --- a/src/UI/DemoScreen.cpp +++ b/src/UI/DemoScreen.cpp @@ -4,6 +4,7 @@ #include #include "Communications/WiFi.hpp" +#include "Displays/TextInsets.hpp" #include "Misc/Dispatch.hpp" #include "Misc/Platform.hpp" #include "UI/Bitmaps/mani-astronaut.hpp" @@ -42,7 +43,7 @@ DemoScreen::DemoScreen(DisplaySystem &display, PreferencesStore &prefs, bool fullManifesto) : display_(display), - renderer_(display, GetTextRectFromPrefs(display, prefs), static_cast(&fontFace0)), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{&fontFace0}), prefs_(prefs), originalInversion_(display_.getInversion()), fullManifesto_(fullManifesto) { // We want to control exactly when full and partial will happen. // This allows us to cross ownership boundaries and to force a partial update diff --git a/src/UI/DemoScreen.hpp b/src/UI/DemoScreen.hpp index 89cde7767..f02f60b2b 100644 --- a/src/UI/DemoScreen.hpp +++ b/src/UI/DemoScreen.hpp @@ -1,8 +1,9 @@ #ifndef DEMO_SCREEN_HPP #define DEMO_SCREEN_HPP +#include + #include "Misc/Battery.hpp" -#include "Renderers/Renderer.hpp" #include "Sensors/LightingController.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/FirstHookTestScreen.cpp b/src/UI/FirstHookTestScreen.cpp index 12d208bbf..e03c9d536 100644 --- a/src/UI/FirstHookTestScreen.cpp +++ b/src/UI/FirstHookTestScreen.cpp @@ -2,6 +2,7 @@ #include +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" @@ -10,7 +11,7 @@ FirstHookTestScreen::FirstHookTestScreen(DisplaySystem &display, PreferencesStore &prefs, Telemetry &telemetry) : display_(display), - renderer_(display, GetTextRectFromPrefs(display, prefs), static_cast(&fontFace0)), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{&fontFace0}), prefs_(prefs), telemetry_(telemetry) { std::string message; diff --git a/src/UI/FirstHookTestScreen.hpp b/src/UI/FirstHookTestScreen.hpp index 77bd827e6..01f73b440 100644 --- a/src/UI/FirstHookTestScreen.hpp +++ b/src/UI/FirstHookTestScreen.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "Misc/Battery.hpp" #include "Misc/Telemetry.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/KindleReadableScreen.cpp b/src/UI/KindleReadableScreen.cpp index c2162a746..c99acbc7c 100644 --- a/src/UI/KindleReadableScreen.cpp +++ b/src/UI/KindleReadableScreen.cpp @@ -7,6 +7,7 @@ #endif #include "Communications/SindarinAPI.hpp" #include "Communications/WiFi.hpp" +#include "Displays/TextInsets.hpp" #include "Menu/ReadingSettingsMenuScreen.hpp" #include "Models/Readable.hpp" #include "UI/Fonts.hpp" @@ -20,7 +21,7 @@ KindleReadableScreen::KindleReadableScreen(DisplaySystem &display, FileSystem &fileSystem, LightingController &lightingController, LocalStore &localStore, Font *font) - : EinkView(display), renderer_(display, GetTextRectFromPrefs(display, prefs), font), + : EinkView(display), renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{font}), prefs_(prefs), telemetry_(telemetry), fileSystem_(fileSystem), lightingController_(lightingController), localStore_(localStore), kindleBook_(kindleBook), cancelFlag_(std::make_shared()), @@ -137,7 +138,7 @@ KindleReadableScreen::KindleReadableScreen(DisplaySystem &display, fontMenu->setBackCallback([this]() { Font *font = FontFromPrefs(prefs_.font.get().value_or(PREFS_SOL_SMALL)); if (font) { - renderer_.setFont(font); + renderer_.setFont(FontHandle{font}); } setCurrentScreen(nullptr); }); diff --git a/src/UI/KindleReadableScreen.hpp b/src/UI/KindleReadableScreen.hpp index f5013aea4..96bffb97c 100644 --- a/src/UI/KindleReadableScreen.hpp +++ b/src/UI/KindleReadableScreen.hpp @@ -3,6 +3,9 @@ // Uncomment to enable simulated connection loss for debugging reconnection issues // #define DEBUG_KINDLE_RECONNECTION +#include +#include +#include #include #include #include @@ -13,9 +16,6 @@ #include "Models/KindleBook.hpp" #include "Models/KindlePageFlipper.hpp" #include "Models/KindlePageResponse.hpp" -#include "Models/Place.hpp" -#include "Renderers/Renderer.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/LocalStore.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/MultiLineTextView.hpp" diff --git a/src/UI/LegalScreen.hpp b/src/UI/LegalScreen.hpp index 0bf93a5f8..7f9ca56d1 100644 --- a/src/UI/LegalScreen.hpp +++ b/src/UI/LegalScreen.hpp @@ -1,9 +1,10 @@ #ifndef LEGAL_SCREEN_HPP #define LEGAL_SCREEN_HPP +#include + #include "Misc/Platform.hpp" #include "Misc/PowerManager.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Bitmaps/bacl.hpp" #include "UI/Bitmaps/ce.hpp" diff --git a/src/UI/LineSpacingScreen.cpp b/src/UI/LineSpacingScreen.cpp index 8a0701793..9fa5423bf 100644 --- a/src/UI/LineSpacingScreen.cpp +++ b/src/UI/LineSpacingScreen.cpp @@ -1,5 +1,6 @@ #include "UI/LineSpacingScreen.hpp" +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" @@ -10,7 +11,7 @@ LineSpacingScreen::LineSpacingScreen(DisplaySystem &display, PreferencesStore &p Telemetry &telemetry) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), prefs_(prefs), telemetry_(telemetry), lineSpacing_(prefs.lineSpacing.get().value_or(0)), rendererRef_(renderer_), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 3) { @@ -60,7 +61,7 @@ LineSpacingScreen::LineSpacingScreen(DisplaySystem &display, PreferencesStore &p std::shared_ptr place) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), place_(std::move(place)), prefs_(prefs), telemetry_(telemetry), lineSpacing_(prefs.lineSpacing.get().value_or(0)), rendererRef_(renderer), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 5) { diff --git a/src/UI/LineSpacingScreen.hpp b/src/UI/LineSpacingScreen.hpp index 815fa12e6..2e5eca43e 100644 --- a/src/UI/LineSpacingScreen.hpp +++ b/src/UI/LineSpacingScreen.hpp @@ -1,9 +1,10 @@ #ifndef LINE_SPACING_SCREEN_HPP #define LINE_SPACING_SCREEN_HPP +#include + #include "Misc/Battery.hpp" #include "Misc/Telemetry.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/BoxView.hpp" #include "UI/Components/EinkView.hpp" diff --git a/src/UI/Menu/DRMFreeWelcomeMenuScreen.cpp b/src/UI/Menu/DRMFreeWelcomeMenuScreen.cpp index dd87c7c5c..292b8ca3a 100644 --- a/src/UI/Menu/DRMFreeWelcomeMenuScreen.cpp +++ b/src/UI/Menu/DRMFreeWelcomeMenuScreen.cpp @@ -2,7 +2,7 @@ DRMFreeWelcomeMenuScreen::DRMFreeWelcomeMenuScreen(DisplaySystem &display) : EinkView(display), menuView_(display), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), rendererView_(display, renderer_) { menuView_.padding = 5; menuView_.itemsPerPage = 4; diff --git a/src/UI/Menu/DRMFreeWelcomeMenuScreen.hpp b/src/UI/Menu/DRMFreeWelcomeMenuScreen.hpp index fdc64cdf9..5bc286544 100644 --- a/src/UI/Menu/DRMFreeWelcomeMenuScreen.hpp +++ b/src/UI/Menu/DRMFreeWelcomeMenuScreen.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "Displays/DisplaySystem.hpp" #include "Inputs/RemoteInput.hpp" -#include "Renderers/Renderer.hpp" #include "UI/Components/EinkView.hpp" #include "UI/Components/RendererView.hpp" #include "UI/Menu/MenuView.hpp" diff --git a/src/UI/Menu/ExperimentalMenuScreen.cpp b/src/UI/Menu/ExperimentalMenuScreen.cpp index 6f1bf2207..f13fa95bc 100644 --- a/src/UI/Menu/ExperimentalMenuScreen.cpp +++ b/src/UI/Menu/ExperimentalMenuScreen.cpp @@ -7,7 +7,7 @@ ExperimentalMenuScreen::ExperimentalMenuScreen(DisplaySystem &display, const std PreferencesStore &prefs, Telemetry &telemetry, PushScreenFunc pushScreen) : EinkView(display), menuView_(display), backLabelView_(display, backTitle), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), prefs_(prefs), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), prefs_(prefs), telemetry_(telemetry), pushScreen_(std::move(pushScreen)) { menuView_.padding = 1; menuView_.itemsPerPage = 8; @@ -21,7 +21,7 @@ void ExperimentalMenuScreen::resetFont() { Font *font = FontFromPrefs(prefs_.font.get().value_or(PREFS_SOL_SMALL)); if (font) { - renderer_.setFont(font); + renderer_.setFont(FontHandle{font}); } } diff --git a/src/UI/Menu/FirstBookWelcomeMenuScreen.cpp b/src/UI/Menu/FirstBookWelcomeMenuScreen.cpp index e371dc2ca..11c78c83f 100644 --- a/src/UI/Menu/FirstBookWelcomeMenuScreen.cpp +++ b/src/UI/Menu/FirstBookWelcomeMenuScreen.cpp @@ -2,7 +2,7 @@ FirstBookWelcomeMenuScreen::FirstBookWelcomeMenuScreen(DisplaySystem &display) : EinkView(display), menuView_(display), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), rendererView_(display, renderer_) { menuView_.padding = 5; menuView_.itemsPerPage = 4; diff --git a/src/UI/Menu/FirstBookWelcomeMenuScreen.hpp b/src/UI/Menu/FirstBookWelcomeMenuScreen.hpp index fc8672388..c7dfd5f05 100644 --- a/src/UI/Menu/FirstBookWelcomeMenuScreen.hpp +++ b/src/UI/Menu/FirstBookWelcomeMenuScreen.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "Displays/DisplaySystem.hpp" #include "Inputs/RemoteInput.hpp" -#include "Renderers/Renderer.hpp" #include "UI/Components/EinkView.hpp" #include "UI/Components/RendererView.hpp" #include "UI/Menu/MenuView.hpp" diff --git a/src/UI/Menu/GenericSettingsMenuScreen.cpp b/src/UI/Menu/GenericSettingsMenuScreen.cpp index a9e3920b5..6f41bdea3 100644 --- a/src/UI/Menu/GenericSettingsMenuScreen.cpp +++ b/src/UI/Menu/GenericSettingsMenuScreen.cpp @@ -1,6 +1,6 @@ #include "GenericSettingsMenuScreen.hpp" -#include "Renderers/Renderer.hpp" +#include GenericSettingsMenuScreen::GenericSettingsMenuScreen(DisplaySystem &display, const std::string &backTitle) diff --git a/src/UI/Menu/InactivitySettingMenuScreen.cpp b/src/UI/Menu/InactivitySettingMenuScreen.cpp index 9f56569f5..3360a8fa4 100644 --- a/src/UI/Menu/InactivitySettingMenuScreen.cpp +++ b/src/UI/Menu/InactivitySettingMenuScreen.cpp @@ -1,6 +1,6 @@ #include "InactivitySettingMenuScreen.hpp" -#include "Renderers/Renderer.hpp" +#include InactivitySettingMenuScreen::InactivitySettingMenuScreen(DisplaySystem &display, const std::string &backTitle) diff --git a/src/UI/Menu/LetsFocusWelcomeMenuScreen.cpp b/src/UI/Menu/LetsFocusWelcomeMenuScreen.cpp index 47af88cad..661f4a7a9 100644 --- a/src/UI/Menu/LetsFocusWelcomeMenuScreen.cpp +++ b/src/UI/Menu/LetsFocusWelcomeMenuScreen.cpp @@ -3,7 +3,7 @@ LetsFocusWelcomeMenuScreen::LetsFocusWelcomeMenuScreen(DisplaySystem &display, PreferencesStore &prefs) : EinkView(display), prefs_(prefs), menuView_(display), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), rendererView_(display, renderer_), leftArrowView_(display, "Prev", ArrowDirection::ArrowLeft, ArrowSide::Left), rightArrowView_(display, "Next", ArrowDirection::ArrowRight, ArrowSide::Right) { diff --git a/src/UI/Menu/LetsFocusWelcomeMenuScreen.hpp b/src/UI/Menu/LetsFocusWelcomeMenuScreen.hpp index 00dcbc70e..7cb050053 100644 --- a/src/UI/Menu/LetsFocusWelcomeMenuScreen.hpp +++ b/src/UI/Menu/LetsFocusWelcomeMenuScreen.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "Displays/DisplaySystem.hpp" #include "Inputs/RemoteInput.hpp" -#include "Renderers/Renderer.hpp" #include "UI/Components/ArrowLabelView.hpp" #include "UI/Components/EinkView.hpp" #include "UI/Components/RendererView.hpp" diff --git a/src/UI/Menu/ReadingSettingsMenuScreen.cpp b/src/UI/Menu/ReadingSettingsMenuScreen.cpp index 41f4511a2..59bf2888c 100644 --- a/src/UI/Menu/ReadingSettingsMenuScreen.cpp +++ b/src/UI/Menu/ReadingSettingsMenuScreen.cpp @@ -9,7 +9,7 @@ ReadingSettingsMenuScreen::ReadingSettingsMenuScreen(DisplaySystem &display, PushScreenFunc pushScreen, PopScreenFunc popScreen) : EinkView(display), menuView_(display), backLabelView_(display, backTitle), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), rendererView_(display, renderer_), prefs_(prefs), telemetry_(telemetry), pushScreen_(std::move(pushScreen)), popScreen_(std::move(popScreen)) { menuView_.padding = 1; @@ -71,7 +71,7 @@ void ReadingSettingsMenuScreen::resetFont() { Font *font = FontFromPrefs(prefs_.font.get().value_or(PREFS_SOL_SMALL)); if (font) { - renderer_.setFont(font); + renderer_.setFont(FontHandle{font}); } } diff --git a/src/UI/Menu/TextPlacementWelcomeMenuScreen.cpp b/src/UI/Menu/TextPlacementWelcomeMenuScreen.cpp index e9e61a345..76bf5bcd0 100644 --- a/src/UI/Menu/TextPlacementWelcomeMenuScreen.cpp +++ b/src/UI/Menu/TextPlacementWelcomeMenuScreen.cpp @@ -2,7 +2,7 @@ TextPlacementWelcomeMenuScreen::TextPlacementWelcomeMenuScreen(DisplaySystem &display) : EinkView(display), menuView_(display), - renderer_(display, DisplayRect({0, 0, 0, 0}), static_cast(&fontFace0)), + renderer_(display, DisplayRect({0, 0, 0, 0}), FontHandle{&fontFace0}), rendererView_(display, renderer_), leftArrowView_(display, ArrowDirection::ArrowLeft), rightArrowView_(display, ArrowDirection::ArrowRight) { menuView_.padding = 5; @@ -100,15 +100,15 @@ void TextPlacementWelcomeMenuScreen::drawInRect(SolRect rect) { switch (textSelection_) { case 0: renderer_.setLineSpacing(0); - renderer_.setFont(&fontFace0); + renderer_.setFont(FontHandle{&fontFace0}); break; case 1: renderer_.setLineSpacing(-2); - renderer_.setFont(&fontFace0); + renderer_.setFont(FontHandle{&fontFace0}); break; case 2: renderer_.setLineSpacing(0); - renderer_.setFont(&fontFace1); + renderer_.setFont(FontHandle{&fontFace1}); break; } if (textSelection_ != 0) { diff --git a/src/UI/Menu/TextPlacementWelcomeMenuScreen.hpp b/src/UI/Menu/TextPlacementWelcomeMenuScreen.hpp index d20084855..cbdc5f1e7 100644 --- a/src/UI/Menu/TextPlacementWelcomeMenuScreen.hpp +++ b/src/UI/Menu/TextPlacementWelcomeMenuScreen.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "Displays/DisplaySystem.hpp" #include "Inputs/RemoteInput.hpp" -#include "Renderers/Renderer.hpp" #include "UI/Components/ArrowView.hpp" #include "UI/Components/EinkView.hpp" #include "UI/Components/RendererView.hpp" diff --git a/src/UI/NewReadablePopupMenuScreen.cpp b/src/UI/NewReadablePopupMenuScreen.cpp index f455c5ef0..0cd651cb3 100644 --- a/src/UI/NewReadablePopupMenuScreen.cpp +++ b/src/UI/NewReadablePopupMenuScreen.cpp @@ -11,13 +11,14 @@ void NewReadablePopupMenuScreen::setUpPopupMenu() { footer3TextView_.setText(""); if (place->isFor(DocType::EPUB)) { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; auto ePubPlace = std::dynamic_pointer_cast(place); auto &chapterHref = ePubPlace->getHRef(); - currentChapterIdx_ = stream->getChapterIdx(chapterHref); + currentChapterIdx_ = epubFile->getChapterIdx(chapterHref); if (currentChapterIdx_ >= 0) { - const auto &toc = stream->getToc(); + const auto &toc = epubFile->getToc(); auto &chapter = toc[currentChapterIdx_]; auto spineIdx = renderer_.getStreamPlace(renderer_.getCurrentPlace()).getSpineIdx(); // TODO(john): This isn't exactly right because `place->getOffset()` is a byte offset, @@ -26,16 +27,16 @@ void NewReadablePopupMenuScreen::setUpPopupMenu() { // If current spine item is not the first in the chapter, add offsets of previous spine // items in chapter to get the correct offset - auto previousSpineOffset = stream->getOffsetPreviousSpineItemsInChapter(spineIdx); + auto previousSpineOffset = epubFile->getOffsetPreviousSpineItemsInChapter(spineIdx); auto currentOffset = chapter.epubOffset + previousSpineOffset + renderer_.getCurrentPlace()->getOffset(); // Get the end offset of the last chapter (to determine the length of the book) - auto &lastChapter = toc[stream->getToc().size() - 1]; + auto &lastChapter = toc[epubFile->getToc().size() - 1]; auto lastChapterEndOffset = lastChapter.endEpubOffset; if (currentOffset < lastChapterEndOffset) { - if (stream->isAnArticle()) { + if (readable_->isArticle()) { placeStringForBottom = StringFormat("%d of %d page%s", SOL_PAGE_NUMBER(currentOffset), SOL_PAGE_NUMBER(lastChapterEndOffset), @@ -47,7 +48,7 @@ void NewReadablePopupMenuScreen::setUpPopupMenu() { } // Determine how many Sol Pages are left in the chapter - if (chapter.endEpubOffset > currentOffset && !stream->isAnArticle()) { + if (chapter.endEpubOffset > currentOffset && !readable_->isArticle()) { auto chapterPagesRemaining = chapter.endEpubOffset - currentOffset; chapterPagesRemainingString = StringFormat("%d page%s left", SOL_PAGE_NUMBER(chapterPagesRemaining), diff --git a/src/UI/NewReadablePopupMenuScreen.hpp b/src/UI/NewReadablePopupMenuScreen.hpp index 8d24e3772..86b1021ff 100644 --- a/src/UI/NewReadablePopupMenuScreen.hpp +++ b/src/UI/NewReadablePopupMenuScreen.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include "Misc/Telemetry.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/LocalStore.hpp" #include "UI/Components/EinkView.hpp" #include "UI/LightingAdjustScreen.hpp" diff --git a/src/UI/OffsetAdjustScreen.cpp b/src/UI/OffsetAdjustScreen.cpp index 20a4b180b..925b3145c 100644 --- a/src/UI/OffsetAdjustScreen.cpp +++ b/src/UI/OffsetAdjustScreen.cpp @@ -3,6 +3,7 @@ #include #include +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" @@ -12,7 +13,7 @@ OffsetAdjustScreen::OffsetAdjustScreen(DisplaySystem &display, PreferencesStore Telemetry &telemetry) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), prefs_(prefs), telemetry_(telemetry), rendererRef_(renderer_), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 5) { std::string message; @@ -40,7 +41,7 @@ OffsetAdjustScreen::OffsetAdjustScreen(DisplaySystem &display, PreferencesStore std::shared_ptr place) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), place_(std::move(place)), prefs_(prefs), telemetry_(telemetry), rendererRef_(renderer), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 5) { setupInstructions(); diff --git a/src/UI/OffsetAdjustScreen.hpp b/src/UI/OffsetAdjustScreen.hpp index 2bfeb5dbb..c39cd87b0 100644 --- a/src/UI/OffsetAdjustScreen.hpp +++ b/src/UI/OffsetAdjustScreen.hpp @@ -1,10 +1,11 @@ #ifndef OFFSET_ADJUST_SCREEN_HPP #define OFFSET_ADJUST_SCREEN_HPP +#include + #include "Misc/Battery.hpp" #include "Misc/Telemetry.hpp" #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/BoxView.hpp" #include "UI/Components/EinkView.hpp" diff --git a/src/UI/PowerDepletedScreen.hpp b/src/UI/PowerDepletedScreen.hpp index e5a645c2a..c50034ddd 100644 --- a/src/UI/PowerDepletedScreen.hpp +++ b/src/UI/PowerDepletedScreen.hpp @@ -1,5 +1,6 @@ -#include "Renderers/Renderer.hpp" +#include + #include "UI/BitmapScreen.hpp" #include "UI/Bitmaps/Battery_Low.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/ReadableDictionaryScreen.cpp b/src/UI/ReadableDictionaryScreen.cpp index 9eb4bb9d1..1a6b93aa7 100644 --- a/src/UI/ReadableDictionaryScreen.cpp +++ b/src/UI/ReadableDictionaryScreen.cpp @@ -1,7 +1,9 @@ -#include "ReadableDictionaryScreen.hpp" +#include "UI/ReadableDictionaryScreen.hpp" -#include -#include +#include "Displays/TextInsets.hpp" +#include "Storage/EPubAdditions.hpp" +#include "UI/Fonts.hpp" +#include "sindarin-debug.h" auto ReadableDictionaryScreen::layout(LayoutContext &context, SolSize size) -> SolSize { auto fullFrame = SolRect::fromSize(size); @@ -54,12 +56,12 @@ void ReadableDictionaryScreen::render(DisplaySystem &display) { if (rendererStream_ == nullptr) { rendererStream_ = renderer_.getStream(); } - auto epubStream = std::dynamic_pointer_cast(rendererStream_); + auto epubFile = + std::dynamic_pointer_cast(rendererStream_)->epubFile; int currentOffset = placeInReadable_->getOffset(); log_d("Loading dictionary for place: %s : %d", placeInReadable_->str().c_str(), currentOffset); - auto dictionaryString = - epubStream->getDictionaryContent(placeInReadable_, placeInDictionary); + auto dictionaryString = GetDictionaryContent(epubFile, placeInReadable_, placeInDictionary); if (preservePlace_) { placeInDictionary = preservePlace_; diff --git a/src/UI/ReadableDictionaryScreen.hpp b/src/UI/ReadableDictionaryScreen.hpp index 4835580d6..7870e65fc 100644 --- a/src/UI/ReadableDictionaryScreen.hpp +++ b/src/UI/ReadableDictionaryScreen.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -7,9 +9,7 @@ #include #include "Misc/Telemetry.hpp" -#include "Models/Place.hpp" #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/ArrowLabelView.hpp" #include "UI/Components/BoxView.hpp" diff --git a/src/UI/ReadablePlacemarkHistoryScreen.cpp b/src/UI/ReadablePlacemarkHistoryScreen.cpp index c012609e8..a379cbd2c 100644 --- a/src/UI/ReadablePlacemarkHistoryScreen.cpp +++ b/src/UI/ReadablePlacemarkHistoryScreen.cpp @@ -1,5 +1,6 @@ #include "UI/ReadablePlacemarkHistoryScreen.hpp" +#include #include void ReadablePlacemarkHistoryScreen::moveSelection(int delta) { @@ -77,10 +78,11 @@ void ReadablePlacemarkHistoryScreen::loadMenuItems() { } // Load a map of chapter href to title and accumulated offset - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; std::unordered_map chapterHrefToTitle; std::unordered_map chapterHrefToOffset; - for (const ReadableTableOfContentsChapter &chapter : stream->getToc()) { + for (const EPubTocChapter &chapter : epubFile->getToc()) { SpiramString href = chapter.href; if (chapter.title.empty()) { // Use book title if chapter title is empty @@ -123,7 +125,8 @@ void ReadablePlacemarkHistoryScreen::loadMenuItems() { } void ReadablePlacemarkHistoryScreen::setPlacemark(const std::shared_ptr &place) { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; readable_->place = place; renderer_.setCurrentPlace(readable_->place); prefs_.readablePlace.set(readable_->shortenedUuid.c_str(), readable_->place->str()); diff --git a/src/UI/ReadablePlacemarkHistoryScreen.hpp b/src/UI/ReadablePlacemarkHistoryScreen.hpp index e3a2aad28..c17d70cbd 100644 --- a/src/UI/ReadablePlacemarkHistoryScreen.hpp +++ b/src/UI/ReadablePlacemarkHistoryScreen.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,7 +8,6 @@ #include "Misc/Telemetry.hpp" #include "Models/Readable.hpp" #include "Models/UserReadablePlacemarkHistory.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/ArrowLabelView.hpp" #include "UI/Components/BoxView.hpp" diff --git a/src/UI/ReadableScreen.cpp b/src/UI/ReadableScreen.cpp index a432981c0..7dd4f54c0 100644 --- a/src/UI/ReadableScreen.cpp +++ b/src/UI/ReadableScreen.cpp @@ -6,8 +6,10 @@ #include #include +#include "Displays/TextInsets.hpp" #include "Misc/Platform.hpp" #include "Models/UserReadablePartialChapterSummary.hpp" +#include "Storage/EPubAdditions.hpp" #include "UI/AppDownloadScreen.hpp" #include "UI/Bitmaps/Auto_Pilot_Icons.hpp" #include "UI/BookQuestionsScreen.hpp" @@ -27,10 +29,12 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, FileSystem &fileSystem, LightingController &lightingController, LocalStore &localStore, Font *font) : EinkView(display), stream_(stream), - renderer_(display, GetTextRectFromPrefs(display, prefs), font), readable_(readable), - prefs_(prefs), telemetry_(telemetry), fileSystem_(fileSystem), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{font}), + readable_(readable), prefs_(prefs), telemetry_(telemetry), fileSystem_(fileSystem), lightingController_(lightingController), display_(display), localStore_(localStore) { + assert(stream != nullptr && "stream cannot be nullptr"); + renderer_.setLineSpacing(prefs.lineSpacing.get().value_or(0)); readablePlacemarkHistoryScreen_ = std::make_shared( @@ -82,14 +86,14 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, auto stream = renderer_.getStream(); if (stream && stream->isFor(DocType::EPUB) && - std::dynamic_pointer_cast(stream)->hasToc()) { + std::dynamic_pointer_cast(stream)->epubFile->hasToc()) { menuItem = std::make_shared("Contents", [this]() { auto toc = std::make_shared( prefs_, readable_, renderer_, display_, telemetry_); auto stream = renderer_.getStream(); if (stream->isFor(DocType::EPUB) && - std::dynamic_pointer_cast(stream)->hasToc()) { + std::dynamic_pointer_cast(stream)->epubFile->hasToc()) { toc->loadMenuItems(); toc->setNearestChapterSelection(renderer_.getCurrentPlace()); toc->setFirstLoad(); @@ -157,8 +161,9 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, // readablesToDelete dynamic_pointer_cast the stream_ to an EPubFile and // close it It's always an EPubFile if they uploaded it to us! Print out the // dynamic type of stream_ to make sure it's always an EPubFile - std::shared_ptr temp = std::dynamic_pointer_cast(stream_); - temp->close(); + auto epubFile = + std::dynamic_pointer_cast(stream_)->epubFile; + epubFile->close(); readablesToDelete.emplace_back(); readablesToDelete.back().uuid = readable_->shortenedUuid; readablesToDelete.back().isArchived = false; @@ -257,7 +262,8 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, menuItems->emplace_back(menuItem); if (renderer_.getCurrentPlace()->isFor(DocType::EPUB)) { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; auto placeHref = std::dynamic_pointer_cast(renderer_.getCurrentPlace())->getHRef(); @@ -289,7 +295,7 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, setCurrentScreen(bookQuestionsScreen_); })); - if (stream->hasSummary(placeHref)) { + if (HasSummary(epubFile, placeHref)) { menuItems->emplace_back(std::make_shared("Summary", [this]() { auto epubPlace = std::dynamic_pointer_cast(renderer_.getCurrentPlace()); @@ -391,7 +397,7 @@ ReadableScreen::ReadableScreen(const std::shared_ptr &stream, fontMenu->setBackCallback([this]() { Font *font = FontFromPrefs(prefs_.font.get().value_or(PREFS_SOL_SMALL)); if (font) { - this->renderer_.setFont(font); + this->renderer_.setFont(FontHandle{font}); } setCurrentScreen(nullptr); }); @@ -606,7 +612,8 @@ void ReadableScreen::updateBookmarkStatus() { } auto ReadableScreen::getScreenCurrentCharOffset() -> uint32_t { - auto epubFile = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; auto epubPlace = std::dynamic_pointer_cast(readable_->place); if ((epubFile != nullptr) && (epubPlace != nullptr)) { return epubFile->findCharOffsetAtFileOffset(epubPlace->getHRef(), epubPlace->getOffset()); diff --git a/src/UI/ReadableScreen.hpp b/src/UI/ReadableScreen.hpp index aa472da84..820017e18 100644 --- a/src/UI/ReadableScreen.hpp +++ b/src/UI/ReadableScreen.hpp @@ -1,10 +1,10 @@ +#include #include #include "Misc/PowerManager.hpp" #include "Misc/Telemetry.hpp" #include "Models/Readable.hpp" #include "Models/ReadableBookmark.hpp" -#include "Renderers/RendererStream.hpp" #include "Sensors/LightingController.hpp" #include "Storage/LocalStore.hpp" #include "Storage/PreferencesStore.hpp" diff --git a/src/UI/ReadableSeekScreen.hpp b/src/UI/ReadableSeekScreen.hpp index 781e234d5..2a74a21b6 100644 --- a/src/UI/ReadableSeekScreen.hpp +++ b/src/UI/ReadableSeekScreen.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/UI/ReadableSummaryScreen.cpp b/src/UI/ReadableSummaryScreen.cpp index e55f36af9..b513cc2e3 100644 --- a/src/UI/ReadableSummaryScreen.cpp +++ b/src/UI/ReadableSummaryScreen.cpp @@ -3,7 +3,10 @@ #include #include +#include "Displays/TextInsets.hpp" #include "Models/UserReadablePartialChapterSummary.hpp" +#include "Storage/EPubAdditions.hpp" +#include "sindarin-debug.h" #define HEIGHT_EVEN(x) ((((x) % 2 == 0) ? (x) : ((x) + 1))) @@ -69,12 +72,13 @@ void ReadableSummaryScreen::render(DisplaySystem &display) { if (rendererStream_ == nullptr) { rendererStream_ = renderer_.getStream(); } - auto epubStream = std::dynamic_pointer_cast(rendererStream_); + auto epubFile = + std::dynamic_pointer_cast(rendererStream_)->epubFile; auto partialChapterSummary = UserReadablePartialChapterSummary::loadForReadable(fileSystem_, readable_); - auto summaryString = epubStream->getSummaryContent(placeInReadable_, placeInSummary, - partialChapterSummary, showUnread_); + auto summaryString = GetSummaryContent(epubFile, placeInReadable_, placeInSummary, + partialChapterSummary, showUnread_); if (preservePlace_) { placeInSummary = preservePlace_; diff --git a/src/UI/ReadableSummaryScreen.hpp b/src/UI/ReadableSummaryScreen.hpp index ba333d7bf..c66af68d7 100644 --- a/src/UI/ReadableSummaryScreen.hpp +++ b/src/UI/ReadableSummaryScreen.hpp @@ -1,12 +1,12 @@ #pragma once +#include #include #include #include #include "Misc/Telemetry.hpp" #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/ArrowLabelView.hpp" #include "UI/Components/BoxView.hpp" diff --git a/src/UI/ReadableTableOfContentsScreen.cpp b/src/UI/ReadableTableOfContentsScreen.cpp index 80d676e56..ac52ec940 100644 --- a/src/UI/ReadableTableOfContentsScreen.cpp +++ b/src/UI/ReadableTableOfContentsScreen.cpp @@ -1,7 +1,10 @@ -#include "ReadableTableOfContentsScreen.hpp" +#include "UI/ReadableTableOfContentsScreen.hpp" #include +#include "Displays/TextInsets.hpp" +#include "sindarin-debug.h" + void ReadableTableOfContentsScreen::moveSelection(int delta) { auto newPage = menuView_.moveSelection(delta); if (newPage) { @@ -70,9 +73,10 @@ void ReadableTableOfContentsScreen::processCommand(RemoteCommand command) { void ReadableTableOfContentsScreen::loadMenuItems() { menuView_.resetMenuItems(); - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; - for (const ReadableTableOfContentsChapter &chapter : stream->getToc()) { + for (const EPubTocChapter &chapter : epubFile->getToc()) { std::string title = std::string(chapter.title); if (!chapter.inSubsectionHref.empty()) { title = StringFormat(" %s", chapter.title.c_str()); @@ -83,8 +87,9 @@ void ReadableTableOfContentsScreen::loadMenuItems() { } void ReadableTableOfContentsScreen::setPlacemark() { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); - auto chapter = stream->getToc()[menuView_.getSelectedIndex()]; + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; + auto chapter = epubFile->getToc()[menuView_.getSelectedIndex()]; if (renderer_.getStream()->isFor(DocType::EPUB)) { readable_->place = std::make_shared(0, std::string(chapter.href)); } @@ -99,13 +104,13 @@ void ReadableTableOfContentsScreen::setPlacemark() { void ReadableTableOfContentsScreen::setNearestChapterSelection( const std::shared_ptr &placeInFile) { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); int nearestChapter = 0; if (placeInFile->isFor(DocType::EPUB)) { - auto stream = std::dynamic_pointer_cast(renderer_.getStream()); + auto epubFile = + std::dynamic_pointer_cast(renderer_.getStream())->epubFile; auto ePubPlace = std::dynamic_pointer_cast(placeInFile); - nearestChapter = stream->getChapterIdx(ePubPlace); + nearestChapter = epubFile->getChapterIdx(ePubPlace->getHRef()); if (nearestChapter == -1) { log_w("Unable to find nearest chapter for %s", ePubPlace->getHRef().c_str()); diff --git a/src/UI/ReadableTableOfContentsScreen.hpp b/src/UI/ReadableTableOfContentsScreen.hpp index 18a38bcdf..1c89665f0 100644 --- a/src/UI/ReadableTableOfContentsScreen.hpp +++ b/src/UI/ReadableTableOfContentsScreen.hpp @@ -1,12 +1,12 @@ #pragma once +#include #include #include #include #include "Misc/Telemetry.hpp" #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/ArrowLabelView.hpp" #include "UI/Components/ArrowView.hpp" diff --git a/src/UI/ReleaseNotesScreen.cpp b/src/UI/ReleaseNotesScreen.cpp index 0e9b3c611..4f91cb700 100644 --- a/src/UI/ReleaseNotesScreen.cpp +++ b/src/UI/ReleaseNotesScreen.cpp @@ -2,6 +2,7 @@ #include +#include "Displays/TextInsets.hpp" #include "Misc/Platform.hpp" #include "UI/Fonts.hpp" #include "UI/Menu/MenuItemSingleLine.hpp" @@ -11,7 +12,7 @@ ReleaseNotesScreen::ReleaseNotesScreen(DisplaySystem &display, PreferencesStore PowerManager &power, LocalStore &localStore, const Firmware &firmware) : display_(display), - renderer_(display, GetTextRectFromPrefs(display, prefs), static_cast(&fontFace0)), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{&fontFace0}), prefs_(prefs), power_(power), localStore_(localStore), firmware_(firmware), menu_(prefs, display) { diff --git a/src/UI/ReleaseNotesScreen.hpp b/src/UI/ReleaseNotesScreen.hpp index 71d8bcdef..7a9c93036 100644 --- a/src/UI/ReleaseNotesScreen.hpp +++ b/src/UI/ReleaseNotesScreen.hpp @@ -1,9 +1,10 @@ #pragma once +#include + #include "Misc/Battery.hpp" #include "Misc/PowerManager.hpp" #include "Models/Firmware.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/LocalStore.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/LineMenuScreen.hpp" diff --git a/src/UI/ResizeBoxScreen.cpp b/src/UI/ResizeBoxScreen.cpp index 1124ff86a..753167810 100644 --- a/src/UI/ResizeBoxScreen.cpp +++ b/src/UI/ResizeBoxScreen.cpp @@ -1,5 +1,6 @@ #include "UI/ResizeBoxScreen.hpp" +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" @@ -12,7 +13,7 @@ ResizeBoxScreen::ResizeBoxScreen(DisplaySystem &display, PreferencesStore &prefs Telemetry &telemetry) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), prefs_(prefs), telemetry_(telemetry), rendererRef_(renderer_), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 5) { std::string message; @@ -39,7 +40,7 @@ ResizeBoxScreen::ResizeBoxScreen(DisplaySystem &display, PreferencesStore &prefs std::shared_ptr place) : EinkView(display), display_(display), renderer_(display, GetTextRectFromPrefs(display, prefs), - FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))), + FontHandle{FontFromPrefs(prefs.font.get().value_or(PREFS_SOL_SMALL))}), place_(std::move(place)), prefs_(prefs), telemetry_(telemetry), rendererRef_(renderer), outerBoxView_(display, 5), backgroundBoxView_(display, 5), explanationView_(display, 5) { setupInstructions(); diff --git a/src/UI/ResizeBoxScreen.hpp b/src/UI/ResizeBoxScreen.hpp index 494882f80..a37e9b1ac 100644 --- a/src/UI/ResizeBoxScreen.hpp +++ b/src/UI/ResizeBoxScreen.hpp @@ -1,9 +1,10 @@ #ifndef RESIZE_BOX_SCREEN_HPP #define RESIZE_BOX_SCREEN_HPP +#include + #include "Misc/Battery.hpp" #include "Misc/Telemetry.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Components/BoxView.hpp" #include "UI/Components/EinkView.hpp" diff --git a/src/UI/RootUI.cpp b/src/UI/RootUI.cpp index 6e8f854ba..5365ff212 100644 --- a/src/UI/RootUI.cpp +++ b/src/UI/RootUI.cpp @@ -1,5 +1,7 @@ #include "RootUI.hpp" +#include +#include #include #include #include @@ -10,7 +12,6 @@ #include "Communications/HtmlCleaner.hpp" #include "Communications/SindarinAPI.hpp" #include "Communications/WiFi.hpp" -#include "EPub/EPubFile.hpp" #include "Inputs/RemoteInput.hpp" #include "Misc/BootLoopDetection.hpp" #include "Misc/FactoryReset.hpp" @@ -21,12 +22,12 @@ #include "Misc/Telemetry.hpp" #include "Models/KindleBook.hpp" #include "Models/Readable.hpp" -#include "Renderers/Renderer.hpp" #if CONFIG_INCLUDE_DEFAULT_CONTENT #include "Storage/EPubs/LastQuestion.epub.hpp" #include "Storage/EPubs/PsalmsAndProverbs.epub.hpp" #endif #include "Storage/LocalStore.hpp" +#include "Storage/SFileUnzipperStream.hpp" #include "UI/AboutScreen.hpp" #include "UI/AlignmentScreen.hpp" #include "UI/AppDownloadScreen.hpp" @@ -253,12 +254,16 @@ void RootUI::setup(LocalStore &localStore, FileSystem &fileSystem, // Handle this is an in-memory epub auto file = SFile(std::make_shared(readable->localBytes, readable->localBytesSize)); - stream = std::make_shared(file, readable, solDrm_); + auto unzipperStream = std::make_shared(file); + auto epubFile = std::make_shared(unzipperStream, solDrm_); + stream = std::make_shared(epubFile); } else { auto [path, size] = localStore.readableFilePathForReading(readable); if (HasSuffix(path, ".epub")) { log_i("Opening %s as epub", path.c_str()); - stream = fileSystem.newEPubRendererStream(path.c_str(), readable, solDrm_); + auto epubStream = fileSystem.newEPubRendererStream(path.c_str(), solDrm_); + epubStream->epubFile->setIsAnArticle(readable->isArticle()); + stream = epubStream; } else if (HasSuffix(path, ".txt.gz")) { log_i("Opening %s as gzip", path.c_str()); stream = fileSystem.newGzipRendererStream(path.c_str()); diff --git a/src/UI/RootUI.hpp b/src/UI/RootUI.hpp index 76d520443..819d8413d 100644 --- a/src/UI/RootUI.hpp +++ b/src/UI/RootUI.hpp @@ -21,7 +21,7 @@ class RootUI : public Screen { public: RootUI(DisplaySystem &display, PreferencesStore &prefs, PowerManager &power, - Telemetry &telemetry, SolDrm &solDrm) noexcept + Telemetry &telemetry, const std::shared_ptr &solDrm) noexcept : display_(display), prefs_(prefs), power_(power), telemetry_(telemetry), solDrm_(solDrm) {} RootUI(RootUI const &) = delete; @@ -95,5 +95,5 @@ class RootUI : public Screen { PreferencesStore &prefs_; PowerManager &power_; Telemetry &telemetry_; - SolDrm &solDrm_; + std::shared_ptr solDrm_; }; diff --git a/src/UI/TutorialScreen.cpp b/src/UI/TutorialScreen.cpp index 8ebac9e2d..f7a2fd6fb 100644 --- a/src/UI/TutorialScreen.cpp +++ b/src/UI/TutorialScreen.cpp @@ -2,6 +2,7 @@ #include +#include "Displays/TextInsets.hpp" #include "Misc/Platform.hpp" #include "UI/Fonts.hpp" #include "UI/Menu/MenuItemSingleLine.hpp" @@ -10,7 +11,7 @@ TutorialScreen::TutorialScreen(DisplaySystem &display, PreferencesStore &prefs, SpiramString text, int bit) : display_(display), - renderer_(display, GetTextRectFromPrefs(display, prefs), static_cast(&fontFace0)), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{&fontFace0}), prefs_(prefs), menu_(prefs, display) { auto stream = std::make_shared(text); diff --git a/src/UI/TutorialScreen.hpp b/src/UI/TutorialScreen.hpp index c51db84c3..60315559d 100644 --- a/src/UI/TutorialScreen.hpp +++ b/src/UI/TutorialScreen.hpp @@ -1,6 +1,7 @@ #pragma once -#include "Renderers/Renderer.hpp" +#include + #include "Storage/PreferencesStore.hpp" #include "UI/LineMenuScreen.hpp" diff --git a/src/UI/VcomAdjustScreen.cpp b/src/UI/VcomAdjustScreen.cpp index 615b3e28e..a30ede1f1 100644 --- a/src/UI/VcomAdjustScreen.cpp +++ b/src/UI/VcomAdjustScreen.cpp @@ -4,16 +4,16 @@ #include #include "Displays/DisplayEinkET013TT1.hpp" +#include "Displays/TextInsets.hpp" #include "UI/Fonts.hpp" #include "sindarin-debug.h" - #define STEP_IN_MV 50 #define MIN_VCOM_IN_MV (-4500) #define MAX_VCOM_IN_MV (-3500) VcomAdjustScreen::VcomAdjustScreen(DisplaySystem &display, PreferencesStore &prefs) : display_(display), - renderer_(display, GetTextRectFromPrefs(display, prefs), static_cast(&fontFace0)), + renderer_(display, GetTextRectFromPrefs(display, prefs), FontHandle{&fontFace0}), prefs_(prefs) { auto *driver = (dynamic_cast(&display))->getDriver(); diff --git a/src/UI/VcomAdjustScreen.hpp b/src/UI/VcomAdjustScreen.hpp index ce3ace0b5..c654b1c9b 100644 --- a/src/UI/VcomAdjustScreen.hpp +++ b/src/UI/VcomAdjustScreen.hpp @@ -1,8 +1,9 @@ #ifndef VCOM_ADJUST_SCREEN_HPP #define VCOM_ADJUST_SCREEN_HPP +#include + #include "Misc/Battery.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PreferencesStore.hpp" #include "UI/Screen.hpp" diff --git a/src/idf_component.yml b/src/idf_component.yml index a25dbabb4..442f7f514 100644 --- a/src/idf_component.yml +++ b/src/idf_component.yml @@ -19,9 +19,21 @@ dependencies: version: "*" rules: - if: target == esp32p4 + miniz: + path: ../components/miniz + bmpimage: + path: ../components/cpp-misc/bmpimage defer: path: ../components/cpp-misc/defer + idflog: + path: ../components/cpp-misc/idflog spiram-cpp: path: ../components/cpp-misc/spiram-cpp stringutil: path: ../components/cpp-misc/stringutil + unzipper: + path: ../components/cpp-misc/unzipper + renderer: + path: ../components/tiny-epub/renderer + epubfile: + path: ../components/tiny-epub/epubfile diff --git a/src/main.cpp b/src/main.cpp index 8c0916313..122fc9639 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -132,7 +132,7 @@ TTRemoteInput ttRemoteInput(prefs, telemetry); USBSerial usbSerial; PowerManager powerManager(fileSystem, prefs, lightingController, myDisplay, telemetry, usbSerial); -SolDrm solDrm(prefs); +auto solDrm = std::make_shared(prefs); LocalStore localStore(fileSystem, prefs, powerManager.battery, telemetry, solDrm); RootUI rootUi(myDisplay, prefs, powerManager, telemetry, solDrm); SolClock solClock(prefs); diff --git a/test/CatchTests/ContentSyncTest.cpp b/test/CatchTests/ContentSyncTest.cpp index 13cc4f639..5be8d8a3c 100644 --- a/test/CatchTests/ContentSyncTest.cpp +++ b/test/CatchTests/ContentSyncTest.cpp @@ -87,7 +87,7 @@ TEST_CASE("Run a basic content sync", "[.][content-sync]") { battery.loop(); auto api = SindarinAPI::getInstance(); api->overrideBaseUrl("http://localhost:55011"); - SolDrm solDrm(prefs); + auto solDrm = std::make_shared(prefs); LocalStore localStore(fs, prefs, battery, telemetry, solDrm); diff --git a/test/CatchTests/KindleTests.cpp b/test/CatchTests/KindleTests.cpp index ffe59242c..381ca5f00 100644 --- a/test/CatchTests/KindleTests.cpp +++ b/test/CatchTests/KindleTests.cpp @@ -378,7 +378,7 @@ TEST_CASE("Test KindleReadableScreen views independently", "[.][no-kindle]") { Telemetry telemetry(fileSystem, prefs); Battery battery(fileSystem, prefs, telemetry); - SolDrm solDrm(prefs); + auto solDrm = std::make_shared(prefs); LocalStore localStore(fileSystem, prefs, battery, telemetry, solDrm); LightingController lightingController(prefs); @@ -812,7 +812,7 @@ TEST_CASE("Test mock server URL override", "[.][no-kindle]") { Telemetry telemetry(fileSystem, prefs); Battery battery(fileSystem, prefs, telemetry); - SolDrm solDrm(prefs); + auto solDrm = std::make_shared(prefs); LocalStore localStore(fileSystem, prefs, battery, telemetry, solDrm); auto display = createDisplay(); diff --git a/test/CatchTests/SpiramAllocatorTest.cpp b/test/CatchTests/SpiramAllocatorTest.cpp index a9762445c..73505e24b 100644 --- a/test/CatchTests/SpiramAllocatorTest.cpp +++ b/test/CatchTests/SpiramAllocatorTest.cpp @@ -1,9 +1,9 @@ +#include #include #include #include #include -#include "Renderers/RendererStream.hpp" #include "sindarin-debug.h" int mallocSize; diff --git a/test/CatchTests/TestEPub.cpp b/test/CatchTests/TestEPub.cpp index 4ee7e501d..55a421fe3 100644 --- a/test/CatchTests/TestEPub.cpp +++ b/test/CatchTests/TestEPub.cpp @@ -4,11 +4,11 @@ // Allow access to protected methods from tests #define protected public #define private public -#include "Misc/BMPImage.hpp" +#include + #include "Misc/Telemetry.hpp" -#include "Renderers/RendererDefs.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/PosixFileSystem.hpp" +#include "Storage/SFileUnzipperStream.hpp" #include "UI/BitmapScreen.hpp" #include "UI/LayoutTestScreen.hpp" #include "UI/LineSpacingScreen.hpp" @@ -40,11 +40,13 @@ TEST_CASE("Test some individual EPub formatting edge cases", "[epub-format]") { auto file = fs.open("/Fixtures/epub/formatting.epub"); auto prefs = PreferencesStore(); - SolDrm solDrm(prefs); - auto epubFile = std::make_shared(file, readable, solDrm); + auto solDrm = std::make_shared(prefs); + auto epubFile = + std::make_shared(std::make_shared(file), solDrm); + auto epubStream = std::make_shared(epubFile); - Renderer renderer(*display, display->defaultTextRect(), static_cast(&fontFace0)); - renderer.setStream(epubFile); + Renderer renderer(*display, display->defaultTextRect(), FontHandle{&fontFace0}); + renderer.setStream(epubStream); renderer.renderPage(std::make_shared(0, "newlines_without_spaces.xhtml")); display->updateDisplay(DisplaySystem::UpdateType::FULL, true); @@ -57,11 +59,13 @@ TEST_CASE("Test some individual EPub formatting edge cases", "[epub-format]") { auto file = fs.open("/Fixtures/epub/formatting.epub"); auto prefs = PreferencesStore(); - SolDrm solDrm(prefs); - auto epubFile = std::make_shared(file, readable, solDrm); + auto solDrm = std::make_shared(prefs); + auto epubFile = + std::make_shared(std::make_shared(file), solDrm); + auto epubStream = std::make_shared(epubFile); - Renderer renderer(*display, display->defaultTextRect(), static_cast(&fontFace0)); - renderer.setStream(epubFile); + Renderer renderer(*display, display->defaultTextRect(), FontHandle{&fontFace0}); + renderer.setStream(epubStream); renderer.renderPage(std::make_shared(0, "multiple_nbsp.xhtml")); display->updateDisplay(DisplaySystem::UpdateType::FULL, true); @@ -75,17 +79,20 @@ TEST_CASE("Test some individual EPub formatting edge cases", "[epub-format]") { auto file = fs.open("/Fixtures/epub/glyphs.epub"); auto prefs = PreferencesStore(); - SolDrm solDrm(prefs); - auto epubFile = std::make_shared(file, readable, solDrm); + auto solDrm = std::make_shared(prefs); + auto epubFile = + std::make_shared(std::make_shared(file), solDrm); + auto epubStream = std::make_shared(epubFile); - Renderer renderer(*display, display->defaultTextRect(), static_cast(&fontFace0)); - renderer.setStream(epubFile); + Renderer renderer(*display, display->defaultTextRect(), FontHandle{&fontFace0}); + renderer.setStream(epubStream); auto place = std::make_shared(0, "Basic Latin.xhtml"); // Render all pages int i = 0; while (!renderer.atEndOfFile()) { - auto chapter = epubFile->getToc()[epubFile->getChapterIdx(place)]; + auto spineIdx = epubFile->getSpineIdx(place->getHRef()); + auto chapter = epubFile->getToc()[epubFile->getChapterIdx(spineIdx)]; auto [newPlace, _] = renderer.renderPage(place); place = std::dynamic_pointer_cast(newPlace); display->updateDisplay(DisplaySystem::UpdateType::FULL, true); @@ -127,7 +134,9 @@ TEST_CASE("Test EPub Books Rendering", "[.][epub]") { int pageNbr = 1; ChapterIdx chapterIdx = 0; - auto epubFile = std::dynamic_pointer_cast(readableScreen->getStream()); + auto epubFile = + std::dynamic_pointer_cast(readableScreen->getStream()) + ->epubFile; if (epubFile != nullptr) { for (;;) { diff --git a/test/CatchTests/TestPlace.cpp b/test/CatchTests/TestPlace.cpp index a8a808bd2..c53b9b37b 100644 --- a/test/CatchTests/TestPlace.cpp +++ b/test/CatchTests/TestPlace.cpp @@ -3,7 +3,7 @@ // Allow access to protected methods from tests #define protected public #define private public -#include "Models/Place.hpp" +#include #undef protected #undef private diff --git a/test/CatchTests/TestSolDrm.cpp b/test/CatchTests/TestSolDrm.cpp index 4cffbc672..8d15aee8e 100644 --- a/test/CatchTests/TestSolDrm.cpp +++ b/test/CatchTests/TestSolDrm.cpp @@ -6,9 +6,11 @@ #undef protected #undef private +#include + #include "DisplayUtil.hpp" -#include "Renderers/Renderer.hpp" #include "Storage/PosixFileSystem.hpp" +#include "Storage/SFileUnzipperStream.hpp" #include "ViewTestCommon.hpp" TEST_CASE("Test generateKeypair", "[SolDrm]") { @@ -86,11 +88,13 @@ TEST_CASE("Test DRM'd EPub", "[SolDrm]") { 0x34, 0x3a, 0x3d, 0xbe, 0x6f, 0x54, 0x96, 0x75, 0x4f, 0x81, 0xf0, 0x2f, 0xed, 0x43, 0x39, 0x65, 0x8f, 0x54, 0xba, 0x7d, 0xef, 0x50}; prefs.privateKey.set(privateKey); - SolDrm solDrm(prefs); - auto epubFile = std::make_shared(file, readable, solDrm); + auto solDrm = std::make_shared(prefs); + auto epubFile = + std::make_shared(std::make_shared(file), solDrm); + auto epubStream = std::make_shared(epubFile); - Renderer renderer(*display, display->defaultTextRect(), static_cast(&fontFace0)); - renderer.setStream(epubFile); + Renderer renderer(*display, display->defaultTextRect(), FontHandle{&fontFace0}); + renderer.setStream(epubStream); renderer.renderPage(std::make_shared(0, "OEBPS/9781665515849_split_006.xhtml")); display->updateDisplay(DisplaySystem::UpdateType::FULL, true); diff --git a/test/CatchTests/TestViews.cpp b/test/CatchTests/TestViews.cpp index e31f48433..fdf4f4253 100644 --- a/test/CatchTests/TestViews.cpp +++ b/test/CatchTests/TestViews.cpp @@ -1,14 +1,12 @@ #include +#include #include // Allow access to protected methods from tests #define protected public #define private public - #include "Communications/USBSerial.hpp" -#include "Misc/BMPImage.hpp" #include "Misc/Telemetry.hpp" -#include "Renderers/RendererStream.hpp" #include "Storage/PosixFileSystem.hpp" #include "UI/AboutScreen.hpp" #include "UI/BitmapScreen.hpp" @@ -346,7 +344,7 @@ TEST_CASE("Test some views independently") { PosixFileSystem fileSystem("./test/CatchTests/TestData"); Telemetry telemetry = Telemetry(fileSystem, prefs); USBSerial usbSerial; - SolDrm solDrm(prefs); + auto solDrm = std::make_shared(prefs); SECTION("Test things that need power") { LightingController lightingController(prefs); @@ -482,10 +480,9 @@ TEST_CASE("Test some views independently") { auto prefs = PreferencesStore(); readable->fromJson((*readablesJsonDoc)["readables"][1]); - Renderer renderer(*display, display->defaultTextRect(), - static_cast(&fontFace0)); + Renderer renderer(*display, display->defaultTextRect(), FontHandle{&fontFace0}); auto [path, size] = LocalStore::readableFilePathForReading(fileSystem, readable); - auto stream = fileSystem.newEPubRendererStream(path.c_str(), readable, solDrm); + auto stream = fileSystem.newEPubRendererStream(path.c_str(), solDrm); renderer.setStream(stream); auto screen = std::make_shared(prefs, readable, renderer, *display, telemetry); @@ -506,11 +503,8 @@ TEST_CASE("Test some views independently") { auto readablesJsonDoc = LoadJsonDoc(fileSystem, "/readables.json"); readable->fromJson((*readablesJsonDoc)["readables"][0]); - Renderer renderer(*display, display->defaultTextRect(), - static_cast(&fontFace0)); auto [path, size] = LocalStore::readableFilePathForReading(fileSystem, readable); auto stream = fileSystem.newGzipRendererStream(path.c_str()); - renderer.setStream(stream); auto readableScreen = std::make_shared( stream, *display, readable, prefs, telemetry, fileSystem, lightingController, @@ -526,10 +520,8 @@ TEST_CASE("Test some views independently") { auto readablesJsonDoc = LoadJsonDoc(fileSystem, "/readables-lq-epub.json"); readable->fromJson((*readablesJsonDoc)["readables"][0]); - Renderer renderer(*display, {10, 20, 236, 236}, static_cast(&fontFace0)); auto [path, size] = LocalStore::readableFilePathForReading(fileSystem, readable); - auto stream = fileSystem.newEPubRendererStream(path.c_str(), readable, solDrm); - renderer.setStream(stream); + auto stream = fileSystem.newEPubRendererStream(path.c_str(), solDrm); auto readableScreen = std::make_shared( stream, *display, readable, prefs, telemetry, fileSystem, lightingController, @@ -569,34 +561,3 @@ TEST_CASE("Test some views independently") { } } } - -TEST_CASE("Test bmp file rendering") { - PosixFileSystem fs("./test/CatchTests/TestData"); - fs.begin(); - // create and render a full screen bitmap view - auto display = createDisplay(); - - SECTION("Test bitmap view Books (grayscale)") { - SFile file = fs.open("/books.bmp"); - ContainerStream stream(file); - BMPImage image; - image.readFromStream(stream); - - auto screen = - std::make_shared(image.getBuffer(), image.getWidth(), image.getHeight()); - screen->render(*display); - ASSERT_VIEW_UNCHANGED(display, "BitmapViewBooks"); - } - - SECTION("Test bitmap view Books (pre-dithered)") { - SFile file = fs.open("/books_dithered.bmp"); - ContainerStream stream(file); - BMPImage image; - image.readFromStream(stream); - - auto screen = - std::make_shared(image.getBuffer(), image.getWidth(), image.getHeight()); - screen->render(*display); - ASSERT_VIEW_UNCHANGED(display, "BitmapViewBooks"); - } -}