From 4848a77e1ba4b0eb02533b91388e5b70bf4b2c82 Mon Sep 17 00:00:00 2001 From: Eliz Date: Tue, 27 Jan 2026 13:21:59 +0000 Subject: [PATCH 01/13] feat: Add support to B&W filters to image covers (#476) ## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Implementation of a new feature in Display options as Image Filter * **What changes are included?** Black & White and Inverted Black & White options are added. ## Additional Context Here are some examples: | None | Contrast | Inverted | | --- | --- | --- | | image | image | image | | image | image | image | * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). I have also tried adding Color inversion, but could not see much difference with that. It might be because my implementation was wrong. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _** PARTIALLY **_ --------- Co-authored-by: Dave Allie --- USER_GUIDE.md | 4 ++++ src/CrossPointSettings.cpp | 9 ++++++--- src/CrossPointSettings.h | 8 ++++++++ src/activities/boot_sleep/SleepActivity.cpp | 11 ++++++++++- src/activities/settings/SettingsActivity.cpp | 4 +++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index bdc0f036..06973c92 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -105,6 +105,10 @@ The Settings screen allows you to configure the device's behavior. There are a f - **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected: - "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary - "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected) +- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected + - "None" (default) - The cover image will be converted to a grayscale image and displayed as it is + - "Contrast" - The image will be displayed as a black & white image without grayscale conversion + - "Inverted" - The image will be inverted as in white&black and will be displayed without grayscale conversion - **Status Bar**: Configure the status bar displayed while reading: - "None" - No status bar - "No Progress" - Show status bar without reading progress diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index f3a7a524..232c7c57 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -22,7 +22,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) { namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 22; +constexpr uint8_t SETTINGS_COUNT = 23; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -57,9 +57,10 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, hideBatteryPercentage); serialization::writePod(outputFile, longPressChapterSkip); serialization::writePod(outputFile, hyphenationEnabled); - // New fields added at end for backward compatibility serialization::writeString(outputFile, std::string(opdsUsername)); serialization::writeString(outputFile, std::string(opdsPassword)); + serialization::writePod(outputFile, sleepScreenCoverFilter); + // New fields added at end for backward compatibility outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -131,7 +132,6 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, hyphenationEnabled); if (++settingsRead >= fileSettingsCount) break; - // New fields added at end for backward compatibility { std::string usernameStr; serialization::readString(inputFile, usernameStr); @@ -146,6 +146,9 @@ bool CrossPointSettings::loadFromFile() { opdsPassword[sizeof(opdsPassword) - 1] = '\0'; } if (++settingsRead >= fileSettingsCount) break; + readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT); + if (++settingsRead >= fileSettingsCount) break; + // New fields added at end for backward compatibility } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 6385f4f1..c450d348 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -17,6 +17,12 @@ class CrossPointSettings { enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4, SLEEP_SCREEN_MODE_COUNT }; enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT }; + enum SLEEP_SCREEN_COVER_FILTER { + NO_FILTER = 0, + BLACK_AND_WHITE = 1, + INVERTED_BLACK_AND_WHITE = 2, + SLEEP_SCREEN_COVER_FILTER_COUNT + }; // Status bar display type enum enum STATUS_BAR_MODE { @@ -95,6 +101,8 @@ class CrossPointSettings { uint8_t sleepScreen = DARK; // Sleep screen cover mode settings uint8_t sleepScreenCoverMode = FIT; + // Sleep screen cover filter + uint8_t sleepScreenCoverFilter = NO_FILTER; // Status bar settings uint8_t statusBar = FULL; // Text rendering settings diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index c4b98968..95fe742f 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -179,10 +179,19 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y); renderer.clearScreen(); + + const bool hasGreyscale = bitmap.hasGreyscale() && + SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER; + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); + + if (SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { + renderer.invertScreen(); + } + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); - if (bitmap.hasGreyscale()) { + if (hasGreyscale) { bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index a211e033..7316db05 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -11,11 +11,13 @@ const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; namespace { -constexpr int displaySettingsCount = 5; +constexpr int displaySettingsCount = 6; const SettingInfo displaySettings[displaySettingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), + SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter, + {"None", "Contrast", "Inverted"}), SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), From 8e0d2bece232f54c7cfbf01e58ad9643c37a38da Mon Sep 17 00:00:00 2001 From: Lalo <86909609+la-lo-go@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:17:48 +0100 Subject: [PATCH 02/13] feat: Add Spanish hyphenation support (#558) ## Summary * **What is the goal of this PR?** Add Spanish language hyphenation support to improve text rendering for Spanish books. * **What changes are included?** - Added Spanish hyphenation trie (`hyph-es.trie.h`) generated from Typst's hypher patterns - Registered `spanishHyphenator` in `LanguageRegistry.cpp` for language tag `es` - Added Spanish to the hyphenation evaluation test suite - Added Spanish test data file with 5000 test cases ## Additional Context * **Test Results:** Spanish hyphenation achieves 99.02% F1 Score (97.72% perfect matches out of 5000 test cases) * **Compatibility:** Works automatically for EPUBs with `es` (or es-ES, es-MX, etc.) imagen | Metric | Value | |--------|-------| | Perfect matches | 97.72% | | Overall Precision | 99.33% | | Overall Recall | 99.42% | | Overall F1 Score | 99.38% | --- ### AI Usage Did you use AI tools to help write this code? _**PARTIALLY**_ AI assisted with: - Guiding and compile - Preparing the PR description --- .../Epub/hyphenation/LanguageRegistry.cpp | 7 +- .../Epub/hyphenation/generated/hyph-es.trie.h | 734 +++ .../HyphenationEvaluationTest.cpp | 1 + .../resources/spanish_hyphenation_tests.txt | 5012 +++++++++++++++++ 4 files changed, 5752 insertions(+), 2 deletions(-) create mode 100644 lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h create mode 100644 test/hyphenation_eval/resources/spanish_hyphenation_tests.txt diff --git a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp index 0643a9fa..5efd76bb 100644 --- a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp +++ b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp @@ -6,6 +6,7 @@ #include "HyphenationCommon.h" #include "generated/hyph-de.trie.h" #include "generated/hyph-en.trie.h" +#include "generated/hyph-es.trie.h" #include "generated/hyph-fr.trie.h" #include "generated/hyph-ru.trie.h" @@ -16,14 +17,16 @@ LanguageHyphenator englishHyphenator(en_us_patterns, isLatinLetter, toLowerLatin LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin); LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin); LanguageHyphenator russianHyphenator(ru_ru_patterns, isCyrillicLetter, toLowerCyrillic); +LanguageHyphenator spanishHyphenator(es_patterns, isLatinLetter, toLowerLatin); -using EntryArray = std::array; +using EntryArray = std::array; const EntryArray& entries() { static const EntryArray kEntries = {{{"english", "en", &englishHyphenator}, {"french", "fr", &frenchHyphenator}, {"german", "de", &germanHyphenator}, - {"russian", "ru", &russianHyphenator}}}; + {"russian", "ru", &russianHyphenator}, + {"spanish", "es", &spanishHyphenator}}}; return kEntries; } diff --git a/lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h b/lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h new file mode 100644 index 00000000..0df8819a --- /dev/null +++ b/lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h @@ -0,0 +1,734 @@ +#pragma once + +#include +#include + +#include "../SerializedHyphenationTrie.h" + +// Auto-generated by generate_hyphenation_trie.py. Do not edit manually. +alignas(4) constexpr uint8_t es_trie_data[] = { + 0x00, 0x00, 0x34, 0xFC, 0x01, 0x04, 0x16, 0x02, 0x0E, 0x0C, 0x02, 0x16, 0x02, 0x0D, 0x0C, 0x22, 0x0F, 0x2C, 0x0F, + 0x22, 0x0D, 0x2C, 0x0D, 0x0B, 0x16, 0x0B, 0x20, 0x15, 0x16, 0x15, 0x0C, 0x02, 0x0C, 0x17, 0x0E, 0x04, 0x2C, 0x05, + 0x04, 0x0D, 0x04, 0x21, 0x04, 0x18, 0x0D, 0x04, 0x17, 0x04, 0x0D, 0x17, 0x04, 0x0E, 0x0D, 0x04, 0x0D, 0x21, 0x04, + 0x0D, 0x21, 0x21, 0x0F, 0x0E, 0x0F, 0x0E, 0x0D, 0x0F, 0x0E, 0x17, 0x33, 0x33, 0x0C, 0x33, 0x16, 0x29, 0x29, 0x0C, + 0x29, 0x16, 0x21, 0x0C, 0x21, 0x0E, 0x34, 0x0D, 0x3E, 0x36, 0x0D, 0x3F, 0x2B, 0x16, 0x0D, 0x3D, 0x3D, 0x0C, 0x3D, + 0x16, 0x1F, 0x1F, 0x16, 0x2A, 0x2C, 0x0D, 0x0E, 0x0E, 0x21, 0x1F, 0x0C, 0x2A, 0x0D, 0x2A, 0x0B, 0x2A, 0x0B, 0x0C, + 0x2A, 0x0B, 0x16, 0x37, 0x20, 0x0C, 0x20, 0x16, 0x35, 0x24, 0x47, 0x47, 0x0C, 0x47, 0x16, 0x20, 0x0B, 0x20, 0x0D, + 0x0C, 0x20, 0x0D, 0x16, 0x20, 0x20, 0x03, 0x17, 0x0E, 0x0D, 0x23, 0x0E, 0x17, 0x17, 0x17, 0x21, 0x16, 0x0D, 0x18, + 0x48, 0x49, 0x16, 0x0C, 0x0C, 0x16, 0x0C, 0x16, 0x2D, 0x2B, 0x0E, 0x0D, 0x2B, 0x0E, 0x17, 0x17, 0x2B, 0x34, 0x0B, + 0x34, 0x0B, 0x0C, 0x34, 0x0B, 0x16, 0x21, 0x20, 0x0D, 0x21, 0x0E, 0x17, 0x20, 0x0D, 0x04, 0x0F, 0x19, 0x0C, 0x0D, + 0x2E, 0x0F, 0x0E, 0x21, 0x17, 0x0E, 0x2D, 0x0E, 0x2B, 0x0E, 0x22, 0x17, 0x17, 0x0E, 0x22, 0x0D, 0x0E, 0x38, 0x19, + 0x18, 0x03, 0x0C, 0x22, 0x0B, 0x0E, 0x22, 0x0B, 0x18, 0x40, 0x2A, 0x0C, 0x0C, 0x2A, 0x0C, 0x16, 0x18, 0x0D, 0x0C, + 0x18, 0x0D, 0x0E, 0x2B, 0x21, 0x2B, 0x17, 0x2A, 0x16, 0x02, 0x33, 0x02, 0x33, 0x0C, 0x02, 0x33, 0x16, 0x35, 0x0E, + 0x04, 0x0C, 0x20, 0x0C, 0x0C, 0x20, 0x0C, 0x16, 0x2B, 0x0E, 0x0E, 0x2B, 0x0E, 0x18, 0x04, 0x0D, 0x0E, 0x0D, 0x19, + 0x0E, 0x41, 0x10, 0x2A, 0x20, 0x04, 0x0C, 0x0D, 0x03, 0x0E, 0x16, 0x0D, 0x0E, 0x18, 0x0F, 0x05, 0x0E, 0x07, 0x0E, + 0xA0, 0x00, 0x51, 0xA0, 0x00, 0x71, 0xA0, 0x00, 0xC3, 0xA3, 0x00, 0x71, 0x74, 0x6E, 0x7A, 0xFD, 0xFD, 0xFD, 0xA1, + 0x00, 0x71, 0x74, 0xF4, 0xA1, 0x00, 0x71, 0x6E, 0xEF, 0xA3, 0x00, 0x71, 0x74, 0x73, 0x6E, 0xEA, 0xEA, 0xEA, 0xA2, + 0x00, 0x71, 0x7A, 0x73, 0xE1, 0xE1, 0xA0, 0x00, 0xA2, 0xB6, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, + 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xD1, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0, + 0x01, 0xD2, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x05, 0xB1, 0xA0, 0x05, 0xC2, 0xA0, 0x05, + 0xE2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, + 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21, 0x6F, 0xF1, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA0, 0x05, + 0x81, 0xA0, 0x06, 0x72, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0xA2, 0x05, 0x81, 0x6F, 0x61, 0xFA, 0xFD, 0xAE, 0x06, + 0x31, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x70, 0x71, 0x73, 0x74, 0x76, 0x7A, 0xED, 0xED, 0xF9, 0xED, + 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0x21, 0x6E, 0xE1, 0xA0, 0x06, 0x01, 0xA0, 0x06, 0x92, + 0xA0, 0x06, 0x12, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0, 0x02, 0x51, 0x21, 0x61, + 0xFD, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6F, 0xFD, 0x28, 0x68, 0x61, 0x65, 0x69, 0x6F, + 0x75, 0xC3, 0x6C, 0xDA, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE3, 0xFD, 0x44, 0x75, 0x62, 0x65, 0x6F, 0xFF, 0x65, 0xFF, + 0x91, 0xFF, 0xC6, 0xFF, 0xEF, 0xA0, 0x04, 0x41, 0xA0, 0x04, 0x52, 0xA0, 0x04, 0x72, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, + 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEF, 0xF5, 0x21, 0x61, 0xF1, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, + 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x69, 0x75, + 0xFE, 0xC5, 0xFE, 0xC8, 0xFE, 0xCE, 0xFE, 0xC8, 0xFE, 0xD7, 0xFE, 0xDC, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, + 0xDC, 0xFE, 0xC8, 0xFE, 0xE1, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xEA, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8, + 0xFE, 0xC8, 0xFE, 0xF4, 0xFE, 0xF4, 0xFF, 0xC7, 0xFF, 0xFD, 0x41, 0x6C, 0xFF, 0x45, 0x21, 0x61, 0xFC, 0x21, 0x75, + 0xFD, 0x41, 0x72, 0xFF, 0x3B, 0x22, 0x6E, 0x75, 0xF9, 0xFC, 0x41, 0x78, 0xFF, 0x32, 0x41, 0x78, 0xFF, 0x34, 0x21, + 0xB3, 0xFC, 0x41, 0x6E, 0xFF, 0x27, 0xA0, 0x01, 0x52, 0x21, 0x64, 0xFD, 0xA0, 0x06, 0x43, 0x21, 0x61, 0xFD, 0x21, + 0x65, 0xFA, 0x23, 0x6E, 0x70, 0x76, 0xF4, 0xFA, 0xFD, 0x21, 0x74, 0xEA, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFA, 0x21, + 0x69, 0xED, 0x21, 0x6C, 0xFD, 0x24, 0x61, 0x65, 0x69, 0x6F, 0xEA, 0xF4, 0xF7, 0xFD, 0x21, 0x6E, 0xF7, 0x25, 0x61, + 0x6F, 0xC3, 0x75, 0x65, 0xBB, 0xC0, 0xC8, 0xCB, 0xFD, 0xA1, 0x00, 0x61, 0x69, 0xF5, 0xA0, 0x07, 0xB1, 0x21, 0x62, + 0xFD, 0xA0, 0x00, 0xF1, 0x21, 0x68, 0xFD, 0x22, 0x69, 0x6F, 0xFA, 0xFA, 0x21, 0x74, 0xF5, 0x21, 0x6E, 0xFD, 0x21, + 0x65, 0xFD, 0x24, 0x63, 0x73, 0x72, 0x74, 0xEF, 0xF2, 0xFD, 0xEC, 0xA2, 0x06, 0x01, 0x69, 0x65, 0xE0, 0xF7, 0xA0, + 0x02, 0x91, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFA, 0x21, 0x65, 0xFD, 0x22, 0x65, 0x72, 0xF7, 0xFD, 0x21, 0x6E, 0xEF, + 0x41, 0x6C, 0xFE, 0x6F, 0x22, 0x65, 0x75, 0xF9, 0xFC, 0x21, 0x74, 0xE3, 0x21, 0x73, 0xFD, 0x21, 0xB3, 0xFD, 0x21, + 0xC3, 0xFD, 0x41, 0x63, 0xFE, 0x5A, 0x21, 0x73, 0xFC, 0x22, 0x65, 0x69, 0xFD, 0xF9, 0x21, 0x64, 0xCB, 0x21, 0x6E, + 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xD3, 0x21, 0x69, 0xFD, 0x21, 0x6F, 0xB9, 0x21, 0x74, 0xFD, + 0xA7, 0x07, 0x62, 0x63, 0x67, 0x70, 0x6C, 0x72, 0x78, 0x75, 0xBF, 0xCB, 0xD9, 0xE3, 0xF1, 0xF7, 0xFD, 0x42, 0x63, + 0x74, 0xFF, 0xA2, 0xFF, 0xA2, 0x41, 0x63, 0xFF, 0x9B, 0x22, 0x69, 0x75, 0xF5, 0xFC, 0x41, 0x69, 0xFF, 0x92, 0x21, + 0x63, 0xFC, 0x21, 0x69, 0xFD, 0x41, 0xA1, 0xFE, 0x0B, 0x21, 0xC3, 0xFC, 0x41, 0x73, 0xFF, 0x81, 0x21, 0x69, 0xFC, + 0xA4, 0x07, 0x62, 0x64, 0x66, 0x74, 0x78, 0xE3, 0xEF, 0xF6, 0xFD, 0x41, 0x75, 0xFF, 0x8C, 0x21, 0x70, 0xFC, 0x41, + 0x6F, 0xFD, 0xEB, 0xA2, 0x07, 0x62, 0x6D, 0x74, 0xF9, 0xFC, 0xA0, 0x00, 0xF2, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x32, + 0x21, 0x72, 0xFD, 0x21, 0xA9, 0xFD, 0x43, 0x65, 0xC3, 0x74, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0x2A, 0x41, 0x6E, 0xFF, + 0x20, 0x21, 0xAD, 0xFC, 0x23, 0x65, 0x69, 0xC3, 0xF9, 0xF9, 0xFD, 0x21, 0x64, 0xF9, 0xA3, 0x05, 0x02, 0x6B, 0x70, + 0x72, 0xD9, 0xE5, 0xFD, 0xA0, 0x07, 0x62, 0xA0, 0x07, 0xA1, 0x21, 0x6C, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x07, 0x82, + 0x67, 0xFD, 0xA0, 0x07, 0x82, 0xC1, 0x07, 0x82, 0x70, 0xFE, 0xFD, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF2, 0xF7, + 0xF7, 0xFA, 0xF7, 0xA0, 0x01, 0xA1, 0x21, 0x62, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x48, 0x68, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0xC3, 0x6E, 0xFE, 0xF2, 0xFF, 0x46, 0xFF, 0x7F, 0xFF, 0x95, 0xFF, 0xC6, 0xFF, 0xCF, 0xFF, 0xE9, + 0xFF, 0xFD, 0xA0, 0x0A, 0x01, 0x21, 0x74, 0xFD, 0x21, 0x75, 0xFD, 0xA2, 0x00, 0x61, 0x6F, 0x61, 0xDE, 0xFD, 0xA0, + 0x08, 0x12, 0xA0, 0x08, 0x33, 0xC2, 0x07, 0x82, 0x6D, 0x6E, 0xFD, 0x4D, 0xFD, 0x4D, 0xA0, 0x0B, 0x45, 0x23, 0xA1, + 0xA9, 0xB3, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F, 0xC3, 0xF6, 0xF6, 0xF6, 0xF9, 0x21, 0x73, 0xF7, 0x21, 0x65, + 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0xA1, 0x07, 0x82, 0x6E, 0xFD, 0xA0, 0x08, 0x63, 0xA0, + 0x08, 0x92, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFA, 0xFD, 0xFD, 0xFD, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, + 0x75, 0xC3, 0xFF, 0xB9, 0xFF, 0xBC, 0xFF, 0xBF, 0xFF, 0xEA, 0xFF, 0x70, 0xFF, 0x70, 0xFF, 0xF5, 0x42, 0x73, 0x75, + 0xFF, 0xEA, 0xFF, 0x96, 0xA0, 0x09, 0x91, 0x21, 0x68, 0xFD, 0xA1, 0x09, 0x81, 0x63, 0xFD, 0x21, 0x6F, 0xFB, 0x21, + 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x65, 0xFD, 0xA2, 0x00, 0x61, 0x65, 0x69, 0xE2, 0xFD, 0xA0, 0x00, 0x61, 0xA0, + 0x0C, 0xC3, 0x21, 0x75, 0xFD, 0xA0, 0x04, 0x91, 0x21, 0x74, 0xFD, 0x22, 0x64, 0x73, 0xF7, 0xFD, 0x22, 0x6E, 0x73, + 0xF5, 0xF5, 0x21, 0x6F, 0xFB, 0x22, 0x74, 0x7A, 0xED, 0xED, 0x21, 0x6E, 0xFB, 0x21, 0x61, 0xFD, 0x21, 0x64, 0xFD, + 0x43, 0x63, 0x65, 0x6E, 0xFF, 0xEF, 0xFD, 0x20, 0xFF, 0xFD, 0x21, 0x6E, 0xD8, 0x23, 0x65, 0x61, 0x69, 0xD8, 0xF3, + 0xFD, 0x21, 0x6C, 0xF9, 0x41, 0x69, 0xFD, 0x1D, 0xA0, 0x04, 0xA2, 0xA0, 0x04, 0xC2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, + 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xB3, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEF, 0xF5, 0x22, 0x6C, 0x6F, 0xDC, 0xF1, 0xA2, 0x00, 0x61, 0x61, 0x69, 0xD4, 0xFB, 0xA0, 0x0D, 0x43, 0x21, 0x69, + 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x62, 0xF4, 0x21, 0xA1, 0xFD, 0x22, 0xC3, 0x61, 0xFD, 0xFA, 0x23, + 0x66, 0x6D, 0x72, 0xEF, 0xF2, 0xFB, 0xA0, 0x0D, 0x73, 0x21, 0x62, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0xA0, + 0x0D, 0x42, 0x21, 0x69, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x70, 0xFD, 0x22, 0xA1, 0xB3, 0xF1, 0xFD, 0x21, 0x70, 0xEF, + 0x21, 0x6F, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x6D, 0xE3, 0x21, 0xA1, 0xFD, 0x22, 0x61, 0xC3, 0xFA, + 0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFE, 0x28, 0x21, 0x73, 0xFC, 0x21, 0x6C, 0xCB, 0x22, 0x69, + 0x65, 0xFA, 0xFD, 0x45, 0x61, 0xC3, 0x65, 0x69, 0x68, 0xFF, 0xB0, 0xFF, 0xCF, 0xFF, 0xDD, 0xFF, 0xEE, 0xFF, 0xFB, + 0x21, 0x6E, 0xF0, 0xA0, 0x06, 0xD2, 0x41, 0x69, 0xFB, 0xE3, 0xA0, 0x0E, 0x92, 0x21, 0x65, 0xFD, 0xC3, 0x0D, 0xB3, + 0x63, 0x72, 0x6A, 0xFF, 0xF6, 0xFB, 0xD9, 0xFF, 0xFD, 0x21, 0x6F, 0xEE, 0x21, 0xAD, 0xFD, 0x43, 0x67, 0x69, 0xC3, + 0xFB, 0xC7, 0xFF, 0xE8, 0xFF, 0xFD, 0xA0, 0x06, 0xB2, 0xA1, 0x0E, 0x92, 0x61, 0xDB, 0x22, 0x63, 0x72, 0xF8, 0xFB, + 0x21, 0x65, 0xFB, 0x41, 0x72, 0xFB, 0xAD, 0xA1, 0x0E, 0x92, 0x73, 0xCA, 0x23, 0x65, 0x61, 0x69, 0xC5, 0xFB, 0xC5, + 0x21, 0x61, 0xBE, 0xC6, 0x0D, 0xB3, 0x72, 0x6C, 0x61, 0x6D, 0x74, 0x6F, 0xFF, 0xD3, 0xFF, 0xEA, 0xFF, 0xED, 0xFF, + 0xF6, 0xFF, 0xFD, 0xFB, 0x9A, 0xA0, 0x05, 0x71, 0x21, 0x6F, 0xFD, 0x21, 0x64, 0xFD, 0xC3, 0x05, 0x81, 0x64, 0x65, + 0x75, 0xFC, 0x8E, 0xFF, 0x9D, 0xFF, 0xFD, 0xC1, 0x0E, 0x92, 0x6F, 0xFF, 0x91, 0x43, 0xA1, 0xA9, 0xB3, 0xFF, 0x8B, + 0xFF, 0x8B, 0xFF, 0x8B, 0x45, 0x61, 0x65, 0x6C, 0x6F, 0xC3, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xF0, 0xFF, 0x81, 0xFF, + 0xF6, 0x42, 0x61, 0x6F, 0xFF, 0x71, 0xFF, 0x71, 0x41, 0x72, 0xFF, 0x8C, 0x21, 0x70, 0xFC, 0x41, 0x72, 0xFF, 0x63, + 0x21, 0x65, 0xFC, 0x41, 0x61, 0xFB, 0x3B, 0x42, 0x6D, 0x74, 0xFB, 0x37, 0xFF, 0xFC, 0xC7, 0x0D, 0xB3, 0x6E, 0x67, + 0x6C, 0x7A, 0x6D, 0x63, 0x73, 0xFF, 0xB4, 0xFF, 0x63, 0xFF, 0xD0, 0xFF, 0xE0, 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xF9, + 0x41, 0x65, 0xFF, 0x5B, 0x41, 0x73, 0xFF, 0x8F, 0x21, 0x65, 0xFC, 0xA2, 0x0D, 0xB3, 0x70, 0x72, 0xF5, 0xFD, 0x42, + 0x61, 0x65, 0xFF, 0x27, 0xFF, 0x39, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFF, 0x20, 0xFF, 0x95, 0xFF, 0x20, 0xFF, 0x20, + 0xA2, 0x0D, 0xB3, 0x72, 0x6C, 0xEC, 0xF3, 0xA0, 0x0D, 0xE3, 0xC1, 0x0D, 0xE3, 0x6E, 0xFA, 0xE8, 0xA0, 0x0E, 0x72, + 0xA1, 0x05, 0x81, 0x69, 0xFD, 0xA1, 0x0D, 0xE3, 0x6E, 0xFB, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xEA, 0xEA, 0xED, + 0xFB, 0xEA, 0x41, 0x76, 0xFF, 0x0D, 0x41, 0x6D, 0xFF, 0x09, 0x22, 0x65, 0x6F, 0xF8, 0xFC, 0x48, 0x68, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0xC3, 0x72, 0xFE, 0xD7, 0xFE, 0xE4, 0xFF, 0x23, 0xFF, 0x8D, 0xFF, 0xB0, 0xFF, 0xCB, 0xFF, 0xE8, + 0xFF, 0xFB, 0x21, 0x74, 0xE7, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFB, 0xB0, 0x21, 0xBA, 0xFC, 0x22, 0x75, 0xC3, 0xF9, + 0xFD, 0xA0, 0x01, 0x11, 0x21, 0x6E, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD, 0x21, 0x64, 0xFB, 0xA2, + 0x04, 0xA2, 0x63, 0x72, 0xEA, 0xFD, 0x21, 0x6C, 0xE8, 0x21, 0x75, 0xFD, 0x21, 0x62, 0xFD, 0xA1, 0x04, 0xC2, 0x6D, + 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xFB, 0xFD, 0xE3, 0xFD, 0xE3, 0xFD, 0xE3, 0xFD, 0xE3, 0x47, 0x68, + 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFD, 0x94, 0xFD, 0xD0, 0xFD, 0xD0, 0xFD, 0xD0, 0xFF, 0xDB, 0xFD, 0xD0, 0xFF, + 0xF0, 0x22, 0xA1, 0xAD, 0xB4, 0xB4, 0x24, 0x61, 0xC3, 0x65, 0x69, 0xAF, 0xFB, 0xAF, 0xAF, 0x21, 0x62, 0xF7, 0xC2, + 0x01, 0x11, 0x61, 0x6F, 0xFF, 0xA3, 0xFF, 0xA3, 0x21, 0x62, 0xF7, 0x21, 0xAD, 0xFD, 0xA2, 0x04, 0x91, 0x69, 0xC3, + 0xEE, 0xFD, 0x41, 0x74, 0xFA, 0x1F, 0x21, 0x72, 0xFC, 0x21, 0x6F, 0xFD, 0xA1, 0x0D, 0xB2, 0x62, 0xFD, 0x41, 0x72, + 0xFE, 0x63, 0x21, 0x61, 0xFC, 0xA1, 0x0D, 0xB2, 0x74, 0xFD, 0xA0, 0x0D, 0xB2, 0xA0, 0x0E, 0xB2, 0x25, 0xA1, 0xA9, + 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xCD, 0xDE, 0xEA, + 0xEF, 0xEF, 0xEF, 0xF5, 0x42, 0x65, 0x6F, 0xFF, 0x88, 0xFF, 0xF1, 0xC3, 0x00, 0x61, 0x61, 0x6F, 0x72, 0xFD, 0xF4, + 0xFF, 0x3C, 0xFF, 0xF9, 0x41, 0x72, 0xFB, 0x6B, 0x21, 0x65, 0xFC, 0x41, 0xB3, 0xFB, 0x4A, 0x42, 0x6F, 0xC3, 0xFB, + 0x46, 0xFF, 0xFC, 0x43, 0x72, 0x69, 0x73, 0xFB, 0x3C, 0xFF, 0xF2, 0xFF, 0xF9, 0x42, 0x73, 0x74, 0xFB, 0x32, 0xFB, + 0x32, 0x41, 0xAD, 0xFB, 0x48, 0x22, 0x69, 0xC3, 0xF5, 0xFC, 0x21, 0x6D, 0xFB, 0x41, 0x6D, 0xFB, 0x1F, 0x21, 0x72, + 0xFC, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD, 0x41, 0x76, 0xFB, 0x10, 0x21, 0xA1, 0xFC, 0xA0, 0x04, 0xE2, + 0x21, 0x70, 0xFD, 0x23, 0x61, 0xC3, 0x75, 0xF3, 0xF7, 0xFD, 0x21, 0x72, 0xF9, 0x41, 0x69, 0xFB, 0x5E, 0x21, 0x64, + 0xFC, 0x21, 0x6E, 0xFD, 0x41, 0xB1, 0xFA, 0xEF, 0x21, 0xC3, 0xFC, 0x21, 0xBA, 0xFD, 0x23, 0x6F, 0x75, 0xC3, 0xF3, + 0xFA, 0xFD, 0x41, 0x73, 0xFF, 0x42, 0x21, 0xBA, 0xFC, 0x42, 0x75, 0xC3, 0xFA, 0xF7, 0xFF, 0xFD, 0x41, 0x67, 0xFA, + 0xD3, 0x41, 0x7A, 0xFE, 0x14, 0x41, 0x6A, 0xFA, 0xC8, 0x23, 0xA9, 0xAD, 0xB3, 0xF4, 0xF8, 0xFC, 0x42, 0x61, 0xC3, + 0xF9, 0x40, 0xFB, 0x35, 0x42, 0x6D, 0x74, 0xF9, 0x39, 0xF9, 0x39, 0x43, 0x7A, 0x6D, 0x73, 0xFF, 0xF2, 0xFA, 0xAF, + 0xFF, 0xF9, 0x45, 0x65, 0xC3, 0x69, 0x6F, 0x71, 0xFF, 0xD5, 0xFF, 0xE1, 0xFF, 0xF6, 0xFF, 0xDD, 0xFA, 0xA5, 0x41, + 0xAD, 0xFF, 0x76, 0x42, 0x69, 0xC3, 0xFF, 0x72, 0xFF, 0xFC, 0x43, 0xA1, 0xA9, 0xB3, 0xFA, 0x8A, 0xFA, 0x8A, 0xFA, + 0x8A, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFA, 0x80, 0xFF, 0xF6, 0xFA, 0x80, 0xFA, 0x80, 0x41, 0x65, 0xFA, 0xD8, 0x21, + 0x72, 0xFC, 0x42, 0x6E, 0x74, 0xFA, 0xA1, 0xFA, 0x6C, 0xA0, 0x00, 0x40, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0x42, + 0xA9, 0xAD, 0xFA, 0x94, 0xFF, 0xFD, 0x22, 0x65, 0xC3, 0xE9, 0xF9, 0x22, 0x61, 0x72, 0xE1, 0xFB, 0x41, 0x67, 0xFF, + 0x42, 0x41, 0xBA, 0xFF, 0x28, 0x42, 0x75, 0xC3, 0xFF, 0x24, 0xFF, 0xFC, 0xCE, 0x07, 0x62, 0x62, 0x64, 0x66, 0x67, + 0x63, 0x6A, 0x6C, 0x6E, 0x6D, 0x70, 0x65, 0x71, 0x7A, 0x73, 0xFF, 0x00, 0xFF, 0x1A, 0xFF, 0x27, 0xFF, 0x40, 0xFF, + 0x57, 0xFF, 0x65, 0xFF, 0x97, 0xFF, 0xAB, 0xFF, 0xBC, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0x33, 0xFF, 0x33, 0xFF, 0xF9, + 0xA0, 0x05, 0x02, 0x49, 0x6F, 0x63, 0x69, 0x66, 0x67, 0x76, 0x61, 0x73, 0x74, 0xF8, 0x8F, 0xFA, 0x0C, 0xFA, 0x71, + 0xFA, 0x0C, 0xFA, 0x0C, 0xFA, 0x0C, 0xF8, 0x8F, 0xFA, 0x0C, 0xFA, 0x0C, 0x41, 0x64, 0xF8, 0x73, 0x21, 0x6E, 0xFC, + 0x21, 0x69, 0xFD, 0xC3, 0x07, 0x62, 0x6E, 0x6D, 0x76, 0xFF, 0xDA, 0xFE, 0xDD, 0xFF, 0xFD, 0x41, 0x6E, 0xF9, 0xF7, + 0x21, 0x65, 0xFC, 0x41, 0x61, 0xF9, 0xD3, 0x22, 0x69, 0x67, 0xF9, 0xFC, 0xC4, 0x07, 0x62, 0x62, 0x72, 0x63, 0x6A, + 0xFE, 0xC1, 0xFF, 0xFB, 0xF9, 0xCA, 0xFA, 0x73, 0x42, 0xA1, 0xB3, 0xF9, 0xBB, 0xF9, 0xBB, 0x43, 0x61, 0xC3, 0x6F, + 0xF9, 0xB4, 0xFF, 0xF9, 0xF9, 0xB4, 0x42, 0x63, 0x71, 0xFF, 0xF6, 0xF9, 0xAA, 0x42, 0x63, 0x71, 0xFF, 0xD0, 0xF9, + 0xA3, 0x21, 0xAD, 0xF9, 0x22, 0x69, 0xC3, 0xEF, 0xFD, 0xC1, 0x05, 0x81, 0x74, 0xFC, 0x34, 0x41, 0x74, 0xFC, 0x2E, + 0x21, 0xA1, 0xFC, 0x22, 0x61, 0xC3, 0xF3, 0xFD, 0x42, 0xA9, 0xB3, 0xF8, 0x05, 0xF8, 0x05, 0x48, 0x72, 0x61, 0x73, + 0x6D, 0x65, 0xC3, 0x64, 0x66, 0xF7, 0xFE, 0xF7, 0xFE, 0xF7, 0xFE, 0xFF, 0x16, 0xF7, 0xFE, 0xFF, 0xF9, 0xF7, 0xFE, + 0xF9, 0x7B, 0xC1, 0x05, 0x81, 0x72, 0xF7, 0xE5, 0x42, 0xAD, 0xA1, 0xFF, 0xFA, 0xF7, 0xDF, 0x43, 0x69, 0xC3, 0x74, + 0xFF, 0xDA, 0xFF, 0xF9, 0xF9, 0x55, 0x41, 0xA1, 0xF9, 0x4E, 0x42, 0x61, 0xC3, 0xF9, 0x4A, 0xFF, 0xFC, 0x41, 0x7A, + 0xF9, 0x40, 0x21, 0xAD, 0xFC, 0x22, 0x69, 0xC3, 0xF9, 0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x69, 0xFD, 0xC5, 0x07, 0x62, + 0x62, 0x6D, 0x6E, 0x73, 0x74, 0xFF, 0x95, 0xFF, 0xA7, 0xFF, 0xD9, 0xFF, 0xE7, 0xFF, 0xFD, 0x43, 0x61, 0x65, 0x6F, + 0xF9, 0x1C, 0xF9, 0x1C, 0xF9, 0x1C, 0xC2, 0x07, 0x82, 0x62, 0x6D, 0xF9, 0x15, 0xFF, 0xF6, 0x45, 0xA1, 0xA9, 0xAD, + 0xB3, 0xBA, 0xFF, 0xF7, 0xF9, 0xF0, 0xF9, 0xF0, 0xF9, 0xF0, 0xF9, 0xF0, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, + 0xFE, 0xBD, 0xFE, 0xEA, 0xFF, 0x13, 0xFF, 0x2F, 0xFF, 0xCB, 0xFF, 0xF0, 0xA1, 0x00, 0x61, 0x65, 0xED, 0x43, 0x6E, + 0x72, 0x73, 0xFE, 0xD2, 0xF8, 0xE1, 0xF8, 0xE1, 0xA0, 0x0C, 0x12, 0xA1, 0x0C, 0x12, 0x72, 0xFD, 0x21, 0x6E, 0xF8, + 0x21, 0xB3, 0xFD, 0x23, 0x61, 0x6F, 0xC3, 0xF2, 0xF5, 0xFD, 0x41, 0x70, 0xF7, 0x45, 0x41, 0x6E, 0xF7, 0x41, 0x21, + 0x65, 0xFC, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x22, 0x73, 0x74, 0xEC, 0xFD, 0x41, 0xA9, 0xF8, + 0xBA, 0x21, 0x65, 0xD6, 0x21, 0x69, 0xFD, 0xC7, 0x0F, 0x93, 0x65, 0x6C, 0x64, 0x6E, 0x72, 0xC3, 0x6D, 0xFF, 0xBE, + 0xF9, 0x7B, 0xFF, 0xD6, 0xFF, 0xF1, 0xF8, 0x9F, 0xFF, 0xF6, 0xFF, 0xFD, 0x41, 0xA1, 0xFC, 0xEB, 0x21, 0xC3, 0xFC, + 0x42, 0x70, 0x74, 0xF8, 0xA9, 0xF7, 0x03, 0x22, 0x75, 0x65, 0xF6, 0xF9, 0x41, 0xA1, 0xF8, 0x74, 0x44, 0x61, 0xC3, + 0x65, 0x6F, 0xF8, 0x70, 0xFF, 0xFC, 0xF8, 0x70, 0xF8, 0x70, 0x21, 0x74, 0xF3, 0x41, 0x65, 0xF6, 0xE3, 0x21, 0x75, + 0xFC, 0x21, 0x6C, 0xFD, 0x41, 0x61, 0xFA, 0xF6, 0x41, 0x6E, 0xFC, 0xB6, 0x21, 0x65, 0xFC, 0x21, 0x6D, 0xFD, 0x41, + 0x65, 0xF8, 0x4B, 0x23, 0x63, 0x69, 0x74, 0xEE, 0xF9, 0xFC, 0x41, 0x69, 0xF8, 0x66, 0x21, 0x6D, 0xFC, 0x21, 0xB3, + 0xFD, 0x21, 0xC3, 0xFD, 0xC6, 0x0F, 0x93, 0x63, 0x73, 0x66, 0x6C, 0x72, 0x74, 0xFF, 0xB7, 0xFF, 0xCD, 0xFF, 0xD7, + 0xFF, 0xEC, 0xFB, 0x06, 0xFF, 0xFD, 0x41, 0x75, 0xFC, 0x7F, 0x21, 0x63, 0xFC, 0x21, 0x65, 0xFD, 0x41, 0x6D, 0xFF, + 0x57, 0x21, 0x65, 0xFC, 0x21, 0x6C, 0xAA, 0x21, 0x70, 0xFD, 0x41, 0x74, 0xFF, 0x4A, 0x41, 0x65, 0xF8, 0x29, 0x41, + 0x6D, 0xF6, 0x7F, 0x21, 0xAD, 0xFC, 0x41, 0x75, 0xF8, 0x1E, 0x44, 0x61, 0x69, 0xC3, 0x72, 0xF8, 0x1A, 0xFF, 0xF5, + 0xFF, 0xF9, 0xFF, 0xFC, 0x22, 0x70, 0x74, 0xE4, 0xF3, 0xA5, 0x0F, 0x93, 0x6A, 0x6C, 0x6D, 0x6E, 0x73, 0xCB, 0xD2, + 0xD8, 0xDB, 0xFB, 0x41, 0x69, 0xFC, 0x36, 0x21, 0x70, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x41, 0x63, 0xFA, + 0x65, 0x21, 0x69, 0xFC, 0x41, 0xAD, 0xF7, 0xCF, 0x42, 0x69, 0xC3, 0xF7, 0xCB, 0xFF, 0xFC, 0x21, 0x64, 0xF9, 0xA3, + 0x0F, 0x93, 0x63, 0x66, 0x72, 0xE8, 0xEF, 0xFD, 0x41, 0x62, 0xFA, 0xEF, 0xA1, 0x0F, 0x93, 0x72, 0xFC, 0x42, 0xA9, + 0xB3, 0xF7, 0x9E, 0xF7, 0x9E, 0x42, 0x61, 0xC3, 0xF7, 0x97, 0xFF, 0xF9, 0x21, 0x74, 0xF9, 0x41, 0x74, 0xFF, 0x50, + 0xA2, 0x0F, 0xC3, 0x73, 0x72, 0xF9, 0xFC, 0xA0, 0x0F, 0xC3, 0xC3, 0x0F, 0xC3, 0x6E, 0x6D, 0x72, 0xFD, 0x8F, 0xFA, + 0x1F, 0xF7, 0x7F, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xEA, 0xF1, 0xF4, 0xF1, 0xF1, 0x41, 0x79, 0xF8, 0x11, 0x21, + 0x61, 0xFC, 0x48, 0x69, 0x68, 0x61, 0x65, 0x6F, 0x75, 0xC3, 0x72, 0xFE, 0xC2, 0xF8, 0x91, 0xFF, 0x31, 0xFF, 0x82, + 0xFF, 0xB1, 0xFF, 0xBE, 0xFF, 0xEE, 0xFF, 0xFD, 0x41, 0x74, 0xF8, 0x78, 0x21, 0x73, 0xFC, 0x41, 0x73, 0xF8, 0x71, + 0x21, 0x65, 0xFC, 0x22, 0x65, 0x6F, 0xF6, 0xFD, 0x22, 0x62, 0x72, 0xD4, 0xFB, 0x41, 0x73, 0xFD, 0x21, 0x21, 0x61, + 0xFC, 0x41, 0x61, 0xFD, 0x39, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x62, + 0xFD, 0xA3, 0x00, 0x61, 0x75, 0x6F, 0x65, 0xE1, 0xEA, 0xFD, 0x41, 0x70, 0xF6, 0x09, 0x21, 0x6D, 0xFC, 0x41, 0x6A, + 0xF6, 0x02, 0xA0, 0x05, 0x52, 0x21, 0x74, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0xC3, 0xFD, 0x22, 0x62, 0x6C, 0xF0, 0xFD, + 0x22, 0x69, 0x6F, 0xE8, 0xFB, 0x21, 0x65, 0xFB, 0x21, 0x6C, 0xFD, 0xC1, 0x0E, 0xB2, 0x67, 0xF9, 0x8A, 0x41, 0x67, + 0xF5, 0x63, 0xA1, 0x0E, 0xB2, 0x65, 0xFC, 0x41, 0xB1, 0xF9, 0x7B, 0xA1, 0x0E, 0xB2, 0xC3, 0xFC, 0x43, 0xA1, 0xA9, + 0xB3, 0xF5, 0x51, 0xF5, 0x51, 0xF5, 0x51, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xF5, 0x47, 0xFF, 0xF6, 0xF5, 0x47, 0xF5, + 0x47, 0x21, 0x74, 0xF3, 0xC2, 0x0E, 0xB2, 0x64, 0x6E, 0xFA, 0x38, 0xFF, 0xFD, 0xA0, 0x10, 0xD2, 0x25, 0xA1, 0xA9, + 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF9, 0x3A, 0xFB, + 0x1F, 0xFF, 0xB7, 0xFF, 0xC1, 0xFF, 0xCA, 0xFF, 0xE9, 0xFF, 0xF5, 0x21, 0x73, 0xEA, 0x41, 0x78, 0xF8, 0x7E, 0x21, + 0xB3, 0xFC, 0x21, 0xC3, 0xFD, 0x22, 0x61, 0x69, 0xF3, 0xFD, 0xC2, 0x00, 0x61, 0x65, 0x72, 0xFF, 0x8C, 0xFF, 0xFB, + 0x42, 0x61, 0x6F, 0xF6, 0x48, 0xF6, 0x48, 0x21, 0x65, 0xF9, 0x21, 0x6D, 0xFD, 0x41, 0x65, 0xF6, 0x3B, 0x21, 0x65, + 0xFC, 0x21, 0x6D, 0xFD, 0x22, 0x75, 0x65, 0xF3, 0xFD, 0x41, 0x65, 0xF5, 0x60, 0x21, 0x72, 0xFC, 0x41, 0x6F, 0xF5, + 0x59, 0x21, 0x72, 0xFC, 0x41, 0x72, 0xFB, 0x39, 0x21, 0x67, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x70, 0xFD, 0x41, 0x68, + 0xFB, 0x2C, 0x21, 0x6F, 0xFC, 0x21, 0x63, 0xFD, 0x41, 0x69, 0xF6, 0x72, 0x21, 0x6E, 0xFC, 0x41, 0x72, 0xF6, 0x6B, + 0x23, 0x6C, 0x6D, 0x65, 0xF2, 0xF9, 0xFC, 0x41, 0xB3, 0xFC, 0x0A, 0x42, 0x6F, 0xC3, 0xFC, 0x06, 0xFF, 0xFC, 0x21, + 0x73, 0xF9, 0xA0, 0x05, 0x22, 0x21, 0x65, 0xFD, 0x42, 0x6A, 0x6E, 0xFF, 0xFD, 0xF9, 0x78, 0x21, 0x6F, 0xF9, 0x42, + 0x65, 0x69, 0xFF, 0xFD, 0xF5, 0x0B, 0x24, 0x65, 0x61, 0x69, 0x74, 0xBC, 0xD4, 0xE6, 0xF9, 0x41, 0x74, 0xFF, 0xA2, + 0xC4, 0x02, 0xB1, 0x62, 0x63, 0x6E, 0x74, 0xFF, 0x9B, 0xFF, 0xA2, 0xFF, 0xF3, 0xFF, 0xFC, 0x41, 0x74, 0xF6, 0xD3, + 0x21, 0x73, 0xFC, 0xA1, 0x01, 0x82, 0x65, 0xFD, 0x42, 0x6F, 0xC3, 0xF8, 0xA2, 0xFA, 0x85, 0x41, 0x69, 0xF5, 0xE2, + 0x21, 0x65, 0xFC, 0x41, 0xA9, 0xFD, 0x00, 0x42, 0x65, 0xC3, 0xFC, 0xFC, 0xFF, 0xFC, 0x41, 0x62, 0xF5, 0xB3, 0xA4, + 0x09, 0xA3, 0x6D, 0x63, 0x6A, 0x72, 0xE3, 0xEE, 0xF5, 0xFC, 0x41, 0xAD, 0xFA, 0xC6, 0x42, 0x69, 0xC3, 0xFA, 0xC2, + 0xFF, 0xFC, 0xA1, 0x09, 0xA3, 0x6D, 0xF9, 0xA0, 0x09, 0xA3, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFC, 0x2F, 0xFE, 0xC3, + 0xF4, 0x14, 0xF4, 0x14, 0xA1, 0x09, 0xA3, 0x6A, 0xF3, 0x43, 0x61, 0xC3, 0x65, 0xF4, 0x02, 0xF5, 0xF7, 0xF4, 0x02, + 0x21, 0x72, 0xF6, 0x21, 0x65, 0xFD, 0xA1, 0x09, 0xA3, 0x6D, 0xFD, 0xA0, 0x09, 0xD3, 0xC1, 0x09, 0xD3, 0x6A, 0xF6, + 0x40, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF7, 0xF7, 0xF7, 0xFA, 0xF7, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, + 0xC3, 0xFF, 0x85, 0xFF, 0xA7, 0xFF, 0xBD, 0xFF, 0xC2, 0xFF, 0xD2, 0xFF, 0xE7, 0xFF, 0xF5, 0x41, 0x73, 0xF6, 0x3B, + 0x42, 0x6C, 0x75, 0xF6, 0x37, 0xFF, 0xFC, 0x41, 0x6C, 0xF6, 0x30, 0x41, 0x72, 0xFF, 0x59, 0x41, 0x6D, 0xF6, 0x28, + 0x44, 0xA1, 0xAD, 0xB3, 0xBA, 0xFF, 0xF4, 0xF6, 0x27, 0xFF, 0xF8, 0xFF, 0xFC, 0xC5, 0x01, 0x82, 0x61, 0xC3, 0x69, + 0x6F, 0x75, 0xFF, 0xE0, 0xFF, 0xF3, 0xF6, 0x1A, 0xFF, 0xEB, 0xFF, 0xEF, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, + 0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFF, 0xDE, + 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0xF0, 0x42, 0x6E, 0x78, 0xFF, 0x8E, 0xFF, 0xEA, + 0xA0, 0x01, 0x82, 0x41, 0x72, 0xF5, 0x3F, 0x41, 0x72, 0xF5, 0x0B, 0x22, 0x61, 0x6F, 0xF8, 0xFC, 0x42, 0x6E, 0x70, + 0xF4, 0xEA, 0xF4, 0xEA, 0x21, 0x65, 0xF9, 0x41, 0x70, 0xF4, 0xE0, 0x22, 0x61, 0x6F, 0xFC, 0xFC, 0x41, 0x61, 0xFA, + 0xE0, 0x21, 0x75, 0xFC, 0x41, 0x6D, 0xFF, 0x00, 0x21, 0xA1, 0xFC, 0x41, 0x65, 0xF4, 0xBD, 0x22, 0xC3, 0x69, 0xF9, + 0xFC, 0x41, 0x69, 0xFB, 0x63, 0x21, 0x6C, 0xFC, 0x42, 0x6D, 0x63, 0xF4, 0x9C, 0xF3, 0x1F, 0x22, 0x61, 0x69, 0xF6, + 0xF9, 0x41, 0x61, 0xFE, 0xDD, 0x21, 0x67, 0xFC, 0x41, 0x6C, 0xF4, 0x89, 0x42, 0x61, 0x69, 0xFB, 0x45, 0xF4, 0xEA, + 0x43, 0x63, 0x68, 0x6E, 0xF4, 0xEC, 0xFF, 0xD2, 0xF4, 0xFD, 0x21, 0x65, 0xF6, 0x24, 0x61, 0x65, 0x6C, 0x72, 0xE5, + 0xE8, 0xEC, 0xFD, 0x41, 0x63, 0xF4, 0x85, 0x21, 0x65, 0xFC, 0x41, 0xB3, 0xF4, 0x72, 0x21, 0xC3, 0xFC, 0x41, 0x67, + 0xF4, 0x5A, 0x21, 0x75, 0xFC, 0x22, 0x6D, 0x72, 0xF6, 0xFD, 0x41, 0x69, 0xF4, 0x6E, 0x41, 0x62, 0xF2, 0xCD, 0x21, + 0x69, 0xFC, 0x21, 0x76, 0xFD, 0x21, 0x6F, 0xFD, 0xCC, 0x09, 0xA3, 0x62, 0x63, 0x64, 0x67, 0x6C, 0x6E, 0x70, 0x66, + 0x72, 0x73, 0x74, 0x6D, 0xFF, 0x6B, 0xFF, 0x77, 0xFF, 0x7E, 0xFF, 0x87, 0xFF, 0x95, 0xFF, 0xA8, 0xFF, 0xCC, 0xFF, + 0xD9, 0xFF, 0xEA, 0xFF, 0xEF, 0xFA, 0x67, 0xFF, 0xFD, 0xC1, 0x02, 0x91, 0x69, 0xF4, 0x16, 0x21, 0x63, 0xFA, 0x21, + 0x69, 0xFD, 0x41, 0x64, 0xF4, 0x78, 0x22, 0x75, 0x65, 0xFC, 0xAC, 0x41, 0x6F, 0xFA, 0x27, 0x42, 0x63, 0x61, 0xFF, + 0xFC, 0xF8, 0x70, 0x41, 0x76, 0xF2, 0x79, 0x21, 0xAD, 0xFC, 0x42, 0x69, 0xC3, 0xF4, 0x24, 0xFF, 0xFD, 0x21, 0x75, + 0xF9, 0xC2, 0x02, 0x91, 0x61, 0x68, 0xFF, 0x7D, 0xFA, 0x12, 0xC6, 0x09, 0xA3, 0x66, 0x6C, 0x6E, 0x71, 0x78, 0x76, + 0xFF, 0xCF, 0xFF, 0xD6, 0xFF, 0xDF, 0xFF, 0xF4, 0xFF, 0xF7, 0xFE, 0x17, 0x42, 0x6F, 0x61, 0xF2, 0x4A, 0xF2, 0x4A, + 0x21, 0x75, 0xF9, 0x41, 0x6C, 0xFF, 0x2D, 0x21, 0x61, 0xFC, 0x21, 0x75, 0xFD, 0xC3, 0x09, 0xA3, 0x63, 0x67, 0x6E, + 0xFF, 0xF3, 0xFF, 0xFD, 0xF3, 0xB3, 0x41, 0x73, 0xFB, 0x5F, 0x44, 0x74, 0x61, 0xC3, 0x65, 0xF3, 0xA3, 0xF2, 0x26, + 0xF4, 0x1B, 0xF2, 0x26, 0x43, 0x6F, 0x61, 0x6C, 0xF2, 0x19, 0xF2, 0x19, 0xFF, 0xF3, 0x42, 0x63, 0x74, 0xF2, 0x0F, + 0xF2, 0x0F, 0x21, 0x6E, 0xF9, 0x22, 0x75, 0x65, 0xEC, 0xFD, 0x41, 0x73, 0xF2, 0x00, 0x21, 0x6E, 0xFC, 0x21, 0x65, + 0xFD, 0x41, 0x6F, 0xF8, 0x25, 0xA4, 0x09, 0xA3, 0x62, 0x63, 0x66, 0x70, 0xC8, 0xED, 0xF9, 0xFC, 0x41, 0x7A, 0xF1, + 0xE7, 0x21, 0x69, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x09, 0xA3, 0x74, 0xFD, 0x41, 0x6D, 0xF4, 0x2B, + 0x21, 0x69, 0xFC, 0xA1, 0x09, 0xD3, 0x6E, 0xFD, 0x41, 0x74, 0xF4, 0x1F, 0x21, 0x69, 0xFC, 0xA1, 0x09, 0xD3, 0x64, + 0xFD, 0x41, 0x69, 0xF4, 0x16, 0xA1, 0x09, 0xD3, 0x74, 0xFC, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xE6, 0xFF, + 0xF2, 0xFD, 0xC7, 0xFD, 0xC7, 0xFF, 0xFB, 0xA0, 0x0C, 0x13, 0x21, 0x67, 0xFD, 0x22, 0x63, 0x74, 0xFA, 0xFA, 0x21, + 0x70, 0xF5, 0x22, 0x70, 0x6D, 0xF8, 0xFD, 0xA2, 0x05, 0x22, 0x6F, 0x75, 0xF0, 0xFB, 0xA0, 0x0A, 0x92, 0xA0, 0x0A, + 0xB3, 0xA0, 0x0B, 0x13, 0x23, 0xA1, 0xA9, 0xB3, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F, 0xC3, 0xF6, 0xF6, 0xF6, + 0xF9, 0xA1, 0x0A, 0xB3, 0x73, 0xF7, 0xA0, 0x0A, 0xE3, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0x28, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xCD, 0xD4, 0xD7, 0xED, 0xD7, 0xD7, 0xD7, 0xF5, 0x21, + 0x72, 0xEF, 0x21, 0x65, 0xFD, 0x48, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0x74, 0xFD, 0xE7, 0xFE, 0x87, 0xFE, + 0xE8, 0xFF, 0x11, 0xFF, 0x55, 0xFF, 0x6D, 0xFF, 0x93, 0xFF, 0xFD, 0x21, 0x6E, 0xE7, 0x58, 0x62, 0x63, 0x64, 0x66, + 0x67, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x68, 0x61, 0x65, + 0x69, 0xF2, 0x79, 0xF3, 0xD1, 0xF4, 0x53, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0xC4, + 0xF4, 0x5A, 0xF7, 0x4E, 0xF4, 0x5A, 0xF9, 0xC2, 0xFB, 0x92, 0xFC, 0x33, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, + 0x5A, 0xF4, 0x5A, 0xFC, 0x53, 0xFC, 0xC1, 0xFD, 0xC4, 0xFF, 0xFD, 0x41, 0x63, 0xFC, 0x16, 0xA0, 0x0D, 0x22, 0x21, + 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0xC3, 0x00, 0x71, 0x2E, 0x69, 0x65, 0xF0, 0x3F, 0xFF, 0xF3, 0xFF, 0xFD, 0xC3, 0x00, + 0x71, 0x2E, 0x7A, 0x73, 0xF0, 0x33, 0xF0, 0x39, 0xF0, 0x39, 0xC1, 0x00, 0x71, 0x2E, 0xF0, 0x27, 0xD6, 0x00, 0x81, + 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0xF0, 0x21, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, + 0xF0, 0x24, 0xF3, 0xE6, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF3, 0xE6, 0xF0, 0x24, 0xF0, 0x24, 0xF0, + 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0x41, 0x74, 0xF0, 0x69, 0x21, 0x70, 0xFC, 0xD7, 0x00, 0x91, + 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x65, 0xEF, 0xD5, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, + 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, + 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xFF, 0xFD, 0x42, 0x6F, 0x70, 0xF3, 0xA8, 0xFF, 0xB1, + 0x41, 0x6E, 0xFB, 0x50, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x69, 0x6F, 0xEF, 0x82, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, + 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, + 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xFF, + 0xF5, 0xFF, 0xFC, 0x41, 0x74, 0xF1, 0xF3, 0x21, 0x70, 0xFC, 0x41, 0x6F, 0xFC, 0x90, 0x21, 0x6C, 0xFC, 0x41, 0x74, + 0xF0, 0x5B, 0x41, 0x6D, 0xFA, 0xEF, 0x41, 0x61, 0xEF, 0x9F, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x25, 0x6D, 0x75, + 0x72, 0x73, 0x6E, 0xE4, 0xEB, 0xEE, 0xF2, 0xFD, 0xA0, 0x02, 0x32, 0x21, 0x61, 0xFD, 0x41, 0x2E, 0xEF, 0x06, 0xA1, + 0x02, 0x32, 0x73, 0xFC, 0x22, 0x6F, 0x61, 0xF1, 0xFB, 0x41, 0x64, 0xEF, 0x88, 0x23, 0x63, 0x67, 0x72, 0xEB, 0xF7, + 0xFC, 0x21, 0x6F, 0xE1, 0x41, 0x75, 0xEF, 0x68, 0x21, 0x72, 0xFC, 0x42, 0x64, 0x73, 0xFF, 0xFD, 0xF2, 0xE9, 0x22, + 0x6C, 0x61, 0xEF, 0xF9, 0x41, 0x6C, 0xEF, 0x64, 0x21, 0x61, 0xFC, 0xA0, 0x07, 0x51, 0x21, 0x61, 0xFD, 0x21, 0x65, + 0xFD, 0xA1, 0x04, 0x72, 0x72, 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xFB, 0xEF, 0xD7, 0xEF, 0xD7, 0xEF, + 0xD7, 0xEF, 0xD7, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEF, 0xC1, 0xEF, 0xC4, 0xEF, 0xC4, 0xEF, 0xC4, + 0xEF, 0xC4, 0xEF, 0xC4, 0xFF, 0xF0, 0x21, 0x69, 0xEA, 0x21, 0x74, 0xFD, 0x22, 0x66, 0x6E, 0xC3, 0xFD, 0x42, 0x68, + 0x6F, 0xF2, 0x5F, 0xEF, 0xB4, 0x21, 0x6E, 0xF9, 0xA0, 0x06, 0xF3, 0xA0, 0x07, 0x23, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, + 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x48, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF3, 0x4F, 0xF3, 0x26, + 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF5, 0x21, 0x72, 0xE7, 0x21, 0x65, 0xFD, 0x41, + 0x6C, 0xFA, 0x21, 0x41, 0x6F, 0xF2, 0x6E, 0x24, 0x61, 0x62, 0x63, 0x74, 0xC5, 0xF5, 0xF8, 0xFC, 0x41, 0x6F, 0xEF, + 0x25, 0x21, 0x63, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xA9, 0xFD, + 0xDC, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x68, 0x6C, 0x72, 0x6F, 0x61, 0x75, 0x65, 0x69, 0xC3, 0xEE, 0x30, 0xEE, 0x33, 0xEE, 0x39, 0xEE, + 0x33, 0xEE, 0x42, 0xEE, 0x47, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x47, 0xFD, 0xF1, 0xEE, 0x4C, 0xEE, 0x33, 0xEE, 0x33, + 0xFD, 0xFD, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x33, 0xFE, 0x09, 0xFE, 0x0F, 0xFE, 0x5B, 0xFE, 0xAE, 0xFF, + 0x19, 0xFF, 0x3C, 0xFF, 0x54, 0xFF, 0x9A, 0xFF, 0xE1, 0xFF, 0xFD, 0x41, 0x74, 0xF2, 0xB2, 0x21, 0x6E, 0xFC, 0x21, + 0x65, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x04, 0xA2, 0x6D, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF1, + 0x95, 0xF1, 0xD1, 0xF1, 0xD1, 0xFF, 0xFB, 0xF1, 0xD1, 0xF1, 0xD1, 0xF1, 0xD7, 0x21, 0x61, 0xEA, 0xA0, 0x07, 0xC1, + 0xA0, 0x07, 0xD2, 0xA0, 0x07, 0xF2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, + 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21, 0x6F, 0xF1, 0x21, 0x74, 0xFD, + 0x42, 0x61, 0x6F, 0xFF, 0xFD, 0xEE, 0xA8, 0x21, 0x6D, 0xF9, 0xA0, 0x05, 0x92, 0x21, 0x65, 0xFD, 0x21, 0x64, 0xFD, + 0xA0, 0x09, 0x32, 0x21, 0x61, 0xFD, 0x22, 0x72, 0x6C, 0xF7, 0xFD, 0xA0, 0x02, 0x12, 0x21, 0x69, 0xFD, 0x21, 0x63, + 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x61, 0xEF, 0x4A, 0x22, 0x68, 0x6C, 0xF9, 0xFC, 0x22, 0x61, 0x65, 0xE0, 0xE0, 0x21, + 0x68, 0xFB, 0x21, 0x74, 0xE3, 0x22, 0x63, 0x72, 0xFA, 0xFD, 0x23, 0xB3, 0xA1, 0xA9, 0xD6, 0xEB, 0xFB, 0x21, 0x6A, + 0xC0, 0x21, 0x6C, 0xBD, 0x21, 0x74, 0xBA, 0x21, 0x6E, 0xFD, 0x22, 0x6C, 0x65, 0xF7, 0xFD, 0xA0, 0x02, 0x11, 0x21, + 0x6A, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x69, 0xFF, 0xA6, 0x21, 0x6E, 0xFC, 0x21, 0x61, 0xFD, 0x41, 0x6D, 0xFF, 0x9C, + 0x21, 0x61, 0xFC, 0x46, 0x64, 0x65, 0x69, 0x74, 0x67, 0x6E, 0xFF, 0x98, 0xFF, 0xD5, 0xFF, 0xE1, 0xFF, 0xEC, 0xFF, + 0xF6, 0xFF, 0xFD, 0x42, 0x63, 0x7A, 0xFF, 0x82, 0xFF, 0x82, 0x41, 0x6E, 0xFF, 0x7B, 0x21, 0x65, 0xFC, 0x22, 0x65, + 0x69, 0xF2, 0xFD, 0x21, 0x64, 0xFB, 0x41, 0x67, 0xFF, 0x6C, 0x21, 0x69, 0xFC, 0x41, 0x72, 0xFF, 0x65, 0x21, 0x74, + 0xFC, 0x23, 0x65, 0x6C, 0x73, 0xEF, 0xF6, 0xFD, 0xA0, 0x09, 0x12, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFF, 0x51, 0x21, + 0xBA, 0xFC, 0x23, 0x61, 0x75, 0xC3, 0xF6, 0xF9, 0xFD, 0x21, 0x6F, 0xDE, 0xC2, 0x09, 0x12, 0x63, 0x64, 0xFF, 0x91, + 0xFF, 0x91, 0x23, 0xA1, 0xA9, 0xB3, 0xE0, 0xE0, 0xE0, 0x45, 0x61, 0xC3, 0x65, 0x6F, 0x6C, 0xFF, 0xF0, 0xFF, 0xF9, + 0xFF, 0xD9, 0xFF, 0xD9, 0xFF, 0x81, 0x41, 0x69, 0xFF, 0x84, 0x21, 0x72, 0xFC, 0x41, 0x65, 0xFF, 0x6A, 0x21, 0x63, + 0xFC, 0x42, 0xA1, 0xA9, 0xFF, 0x12, 0xFF, 0x12, 0x43, 0x61, 0xC3, 0x69, 0xFF, 0x0B, 0xFF, 0xF9, 0xFF, 0x0B, 0x41, + 0xA9, 0xFF, 0x01, 0x42, 0x65, 0xC3, 0xFE, 0xFD, 0xFF, 0xFC, 0x41, 0x67, 0xFF, 0x0A, 0x21, 0x65, 0xFC, 0x4B, 0x72, + 0x62, 0x63, 0x64, 0x6C, 0x70, 0x6E, 0x76, 0x78, 0x79, 0x73, 0xFF, 0x5A, 0xFF, 0x91, 0xFF, 0xA5, 0xFF, 0xAC, 0xFF, + 0xBF, 0xFF, 0xD3, 0xFF, 0xDA, 0xFF, 0xE4, 0xFF, 0x49, 0xFF, 0xF2, 0xFF, 0xFD, 0xA0, 0x08, 0xC3, 0x21, 0x64, 0xFD, + 0x21, 0x69, 0xFD, 0x21, 0x72, 0xF7, 0x22, 0x72, 0x6F, 0xFA, 0xFD, 0x22, 0x7A, 0x63, 0xEF, 0xEF, 0x21, 0x69, 0xFB, + 0x21, 0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0x23, 0xA1, 0xA9, 0xB3, 0xDE, 0xDE, 0xDE, 0x22, 0x61, 0xC3, + 0xD7, 0xF9, 0x23, 0x61, 0x65, 0x6F, 0xD2, 0xD2, 0xD2, 0x21, 0xAD, 0xF9, 0x22, 0x69, 0xC3, 0xF1, 0xFD, 0xA0, 0x08, + 0xF2, 0x21, 0x73, 0xC0, 0x22, 0x61, 0x69, 0xFA, 0xFD, 0x21, 0x75, 0xFB, 0x21, 0xAD, 0xC6, 0x22, 0x69, 0xC3, 0xC3, + 0xFD, 0x42, 0x76, 0x6E, 0xFF, 0xAD, 0xFF, 0xFB, 0x42, 0x61, 0x69, 0xED, 0xDD, 0xFF, 0xF9, 0x41, 0x6C, 0xFE, 0x80, + 0x42, 0x72, 0x65, 0xFE, 0x7C, 0xFF, 0xFC, 0x21, 0x67, 0xF9, 0x41, 0x76, 0xFF, 0x91, 0x21, 0x69, 0xFC, 0x21, 0x73, + 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x6C, 0xFF, 0x7E, 0x21, 0x6C, 0xFC, 0x21, 0x6F, + 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x72, 0xFE, 0x52, 0x43, 0x61, 0x65, 0x74, 0xF1, 0xB9, 0xF1, 0xB9, 0xFF, 0xFC, 0x41, + 0x73, 0xFF, 0xA0, 0x21, 0x65, 0xFC, 0x41, 0x6E, 0xFF, 0x5C, 0x21, 0x75, 0xFC, 0x21, 0xB3, 0xF9, 0x22, 0xC3, 0x6F, + 0xFD, 0xF6, 0x4D, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x79, 0x7A, 0xFF, 0x59, 0xFF, + 0x6C, 0xFF, 0x85, 0xFF, 0x95, 0xFE, 0x37, 0xFF, 0xA7, 0xFF, 0xB9, 0xFF, 0xCC, 0xFF, 0xD9, 0xFF, 0xE0, 0xFF, 0xEE, + 0xFF, 0xF5, 0xFF, 0xFB, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFF, 0x25, 0xFF, 0x47, 0xFF, 0x25, 0xFF, 0x25, 0x21, 0x6A, + 0xF3, 0x41, 0x6A, 0xFF, 0x43, 0x21, 0xA9, 0xFC, 0x41, 0xB1, 0xFD, 0xEF, 0x21, 0xC3, 0xFC, 0x21, 0xA9, 0xFD, 0x22, + 0x65, 0xC3, 0xFA, 0xFD, 0x23, 0x65, 0xC3, 0x70, 0xE7, 0xEE, 0xFB, 0x41, 0x6E, 0xFD, 0xD9, 0x21, 0xA9, 0xFC, 0x22, + 0x65, 0xC3, 0xF9, 0xFD, 0x21, 0x72, 0xFB, 0x21, 0x66, 0xFD, 0xC6, 0x02, 0x11, 0x6E, 0x72, 0x62, 0x64, 0x6D, 0x73, + 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0x46, 0x6E, 0x72, 0x62, 0x64, 0x6D, 0x73, + 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0x21, 0xA1, 0xED, 0xC6, 0x09, 0x12, 0x6E, + 0x72, 0x62, 0x64, 0x6D, 0x73, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0x42, 0xA1, + 0xB3, 0xFF, 0xEB, 0xFE, 0x1C, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFE, 0x15, 0xFE, 0x35, 0xFE, 0x15, 0xFE, 0x15, 0x44, + 0x6F, 0x61, 0xC3, 0x68, 0xFE, 0x08, 0xFF, 0xD7, 0xFF, 0xEC, 0xFF, 0xF3, 0x41, 0xA9, 0xFE, 0x85, 0x42, 0x65, 0xC3, + 0xFE, 0x81, 0xFF, 0xFC, 0xA1, 0x05, 0x92, 0x75, 0xF9, 0x41, 0x66, 0xFD, 0x42, 0x42, 0x63, 0x71, 0xFD, 0x3E, 0xFD, + 0x3E, 0x22, 0x69, 0x75, 0xF5, 0xF9, 0x41, 0x62, 0xFD, 0xCD, 0x21, 0x6D, 0xFC, 0x21, 0x6F, 0xFD, 0x41, 0x7A, 0xFD, + 0x28, 0x42, 0x63, 0x6E, 0xFD, 0x75, 0xFF, 0xFC, 0x21, 0x61, 0xF9, 0x21, 0x72, 0xFD, 0x44, 0x61, 0x65, 0x69, 0x75, + 0xFD, 0x17, 0xFF, 0xFD, 0xFD, 0x9C, 0xFD, 0x7B, 0x41, 0x69, 0xFD, 0x4D, 0x41, 0x69, 0xFD, 0x8B, 0x43, 0x62, 0x63, + 0x6C, 0xFF, 0xF8, 0xFD, 0x5C, 0xFF, 0xFC, 0x41, 0x73, 0xFC, 0xF8, 0x41, 0x63, 0xFC, 0xF4, 0x22, 0x65, 0x75, 0xF8, + 0xFC, 0x43, 0x61, 0x69, 0x72, 0xFF, 0xE9, 0xFD, 0x4F, 0xFF, 0xFB, 0x23, 0x63, 0x70, 0x74, 0xB6, 0xCA, 0xF6, 0x42, + 0x63, 0x74, 0xFC, 0xF1, 0xFC, 0xEE, 0x4A, 0x6D, 0x6E, 0x6F, 0x61, 0xC3, 0x63, 0x71, 0x64, 0x73, 0x72, 0xFF, 0x07, + 0xFF, 0x1D, 0xFD, 0x24, 0xFF, 0x20, 0xFF, 0x48, 0xFF, 0x74, 0xFF, 0x8C, 0xFF, 0x9C, 0xFF, 0xF2, 0xFF, 0xF9, 0x42, + 0x72, 0x6F, 0xFD, 0x05, 0xFC, 0xF7, 0x42, 0x61, 0x6F, 0xFC, 0xFE, 0xFC, 0xFE, 0x22, 0x65, 0x69, 0xF2, 0xF9, 0x41, + 0x74, 0xFC, 0xF2, 0x21, 0x72, 0xFC, 0x41, 0xA1, 0xFC, 0xDD, 0x42, 0x61, 0xC3, 0xFC, 0xD9, 0xFF, 0xFC, 0x42, 0x6E, + 0x75, 0xFC, 0xE0, 0xFF, 0xF9, 0x41, 0xB3, 0xFD, 0x0D, 0x42, 0x6F, 0xC3, 0xFD, 0x09, 0xFF, 0xFC, 0x21, 0x69, 0xF9, + 0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0x42, 0x67, 0x6E, 0xFF, 0x6E, 0xFC, 0x74, 0x41, 0x65, 0xFF, 0x75, 0x42, 0x6F, + 0x72, 0xFC, 0xEE, 0xFF, 0xFC, 0x22, 0x61, 0x70, 0xEE, 0xF9, 0x41, 0x72, 0xFD, 0x0C, 0x41, 0x73, 0xFC, 0x9F, 0x21, + 0x75, 0xFC, 0x44, 0x65, 0x6C, 0x6F, 0x72, 0xFC, 0x9B, 0xFF, 0x4C, 0xFF, 0xF5, 0xFF, 0xFD, 0x42, 0x63, 0x74, 0xFC, + 0xEE, 0xFC, 0xEE, 0x21, 0x6E, 0xF9, 0x41, 0x63, 0xFC, 0x8C, 0x41, 0x72, 0xFC, 0x7D, 0xC1, 0x05, 0x92, 0x61, 0xFC, + 0x97, 0x41, 0x72, 0xFC, 0x91, 0x24, 0x65, 0x61, 0x6C, 0x6F, 0xEE, 0xF2, 0xF6, 0xFC, 0x41, 0x62, 0xFC, 0x20, 0x21, + 0x69, 0xFC, 0x41, 0x63, 0xFC, 0x5F, 0x41, 0x61, 0xFC, 0x58, 0x22, 0x65, 0x74, 0xF8, 0xFC, 0x42, 0x67, 0x72, 0xFD, + 0xCE, 0xFC, 0x20, 0x41, 0x78, 0xFC, 0x05, 0x23, 0x65, 0x6F, 0x75, 0xF5, 0xFC, 0xE1, 0x41, 0x65, 0xFC, 0x95, 0x47, + 0x63, 0x65, 0x66, 0x68, 0x73, 0x74, 0x76, 0xFF, 0xA4, 0xFF, 0xB8, 0xFF, 0xCD, 0xFF, 0xDA, 0xFF, 0xE5, 0xFF, 0xF5, + 0xFF, 0xFC, 0x41, 0x6E, 0xFC, 0x31, 0x21, 0x65, 0xFC, 0x21, 0x74, 0xFD, 0x47, 0x64, 0x65, 0x67, 0x6C, 0x6D, 0x6E, + 0x73, 0xFF, 0x30, 0xFF, 0x39, 0xFF, 0x47, 0xFF, 0x5F, 0xFF, 0x74, 0xFF, 0xE0, 0xFF, 0xFD, 0x43, 0x72, 0x73, 0x6E, + 0xFC, 0x69, 0xFC, 0x69, 0xFC, 0x69, 0x21, 0x61, 0xF6, 0x41, 0x6C, 0xFC, 0x04, 0x21, 0x6C, 0xFC, 0x41, 0x61, 0xFD, + 0xE7, 0x21, 0x74, 0xFC, 0xA0, 0x09, 0x53, 0x22, 0x63, 0x71, 0xFD, 0xFD, 0x22, 0x73, 0x69, 0xF5, 0xFB, 0xC1, 0x05, + 0x92, 0x61, 0xFB, 0x98, 0x43, 0x6E, 0x72, 0x73, 0xFB, 0x92, 0xFF, 0xFA, 0xFB, 0x92, 0x43, 0x6E, 0x72, 0x73, 0xFB, + 0x88, 0xFB, 0x88, 0xFB, 0x88, 0x42, 0xA9, 0xB3, 0xFF, 0xF6, 0xFB, 0x7E, 0x45, 0x72, 0x64, 0x65, 0xC3, 0x6D, 0xFB, + 0x77, 0xFB, 0x77, 0xFF, 0xE5, 0xFF, 0xF9, 0xFB, 0x77, 0x42, 0x6E, 0x73, 0xFB, 0x67, 0xFB, 0x67, 0x42, 0xA1, 0xAD, + 0xFB, 0x60, 0xFF, 0xC8, 0x45, 0x69, 0x61, 0x65, 0x6F, 0xC3, 0xFF, 0xE2, 0xFF, 0xF2, 0xFB, 0x59, 0xFB, 0x59, 0xFF, + 0xF9, 0x41, 0xA1, 0xFB, 0x49, 0x42, 0x61, 0xC3, 0xFB, 0x45, 0xFF, 0xFC, 0x21, 0xB1, 0xF9, 0x41, 0x62, 0xFB, 0x9C, + 0x47, 0x64, 0x65, 0x62, 0x73, 0x6E, 0xC3, 0x72, 0xFF, 0x81, 0xFF, 0x88, 0xFF, 0x9A, 0xFF, 0x8F, 0xFF, 0xDE, 0xFF, + 0xF9, 0xFF, 0xFC, 0x46, 0xC3, 0x6F, 0x61, 0x65, 0x69, 0x75, 0xFB, 0x5A, 0xFC, 0x32, 0xFD, 0x07, 0xFE, 0x4E, 0xFF, + 0x4B, 0xFF, 0xEA, 0x41, 0x69, 0xFB, 0x5F, 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x45, 0x63, 0x6E, 0x72, 0x73, 0x69, + 0xFA, 0xCE, 0xF4, 0xA7, 0xFB, 0x01, 0xFF, 0xE3, 0xFF, 0xFD, 0xA0, 0x11, 0x72, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD, + 0x22, 0x65, 0xC3, 0xFA, 0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x65, 0xFD, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, + 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x72, 0x65, 0x69, + 0xE8, 0x5B, 0xE8, 0x5E, 0xE8, 0x64, 0xE8, 0x5E, 0xE8, 0x6D, 0xE8, 0x72, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, + 0x5E, 0xE8, 0x72, 0xE8, 0x5E, 0xE8, 0x77, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x80, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x5E, + 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x8A, 0xFF, 0xDC, 0xFF, 0xFD, 0x42, 0x6D, 0x72, 0xF4, 0x38, 0xF3, 0xDE, 0x41, 0x69, + 0xF3, 0xD3, 0x43, 0x6C, 0x73, 0x74, 0xF9, 0xB2, 0xFF, 0xFC, 0xF9, 0xB2, 0x42, 0x6E, 0x74, 0xF9, 0xA8, 0xF9, 0xA8, + 0x41, 0x69, 0xEB, 0x9B, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, + 0x21, 0x6D, 0xFD, 0xDA, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, + 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x65, 0x69, 0x6F, 0x61, 0xE7, 0xDE, 0xE7, 0xE1, 0xE7, 0xE1, + 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, + 0xE1, 0xE7, 0xE1, 0xF7, 0xB7, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE8, 0x0D, 0xE8, 0x0D, + 0xFF, 0xCE, 0xFF, 0xD9, 0xFF, 0xE3, 0xFF, 0xFD, 0xD7, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x75, 0xE7, 0x8D, 0xE7, 0xB9, + 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, + 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, + 0xE7, 0xB9, 0xF7, 0x41, 0xA0, 0x08, 0xB1, 0x21, 0x2E, 0xFD, 0x49, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0x2E, + 0x73, 0xE8, 0x4E, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x57, 0xFF, 0xFA, 0xFF, 0xFD, + 0x22, 0x2E, 0x73, 0xDE, 0xE1, 0x21, 0x61, 0xFB, 0x21, 0xAD, 0xFD, 0x23, 0x6F, 0x61, 0xC3, 0xD9, 0xF5, 0xFD, 0x21, + 0x66, 0xF9, 0xD7, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0xE7, 0x0E, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, + 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, + 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xFF, 0xFD, 0x41, 0x73, + 0xFF, 0x84, 0x42, 0x2E, 0x65, 0xFF, 0x7D, 0xFF, 0xFC, 0x21, 0x6C, 0xF9, 0x42, 0x6F, 0x61, 0xFF, 0x95, 0xFF, 0xFD, + 0x21, 0x6E, 0xF9, 0x41, 0x72, 0xF9, 0x23, 0x42, 0x65, 0x72, 0xFF, 0xFC, 0xE7, 0x37, 0x21, 0x74, 0xF9, 0x42, 0x6C, + 0x73, 0xF8, 0x4D, 0xFF, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE7, 0x64, 0xE7, 0x67, 0xE7, 0x67, + 0xE7, 0x67, 0xE7, 0x67, 0xE7, 0x67, 0xE7, 0x6D, 0x41, 0x6E, 0xF8, 0xFB, 0x21, 0x6F, 0xFC, 0x22, 0x6F, 0x72, 0xE3, + 0xFD, 0x41, 0x63, 0xE7, 0x04, 0x21, 0x65, 0xFC, 0x41, 0x61, 0xEA, 0x8B, 0x22, 0x6E, 0x67, 0xF9, 0xFC, 0x41, 0x64, + 0xF7, 0x46, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, + 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x6F, 0x61, 0x65, 0x69, 0x75, + 0xE6, 0x5D, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, + 0x60, 0xF6, 0x36, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, + 0xE6, 0x60, 0xFE, 0xD0, 0xFF, 0x4F, 0xFF, 0xAC, 0xFF, 0xBD, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0xFD, 0x41, 0x2E, 0xE6, + 0x0C, 0x42, 0x2E, 0x73, 0xE6, 0x08, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x21, 0x6C, 0xFB, 0x42, 0x6F, 0x61, + 0xE6, 0xD5, 0xE6, 0xD5, 0x21, 0x6E, 0xF9, 0x21, 0x61, 0xFD, 0x22, 0x65, 0x6D, 0xF0, 0xFD, 0x41, 0x65, 0xFE, 0x9F, + 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFA, 0x22, 0x6C, 0x69, 0xFA, 0xFD, 0x42, 0x6C, + 0x62, 0xF7, 0x7C, 0xFF, 0xFB, 0x42, 0x63, 0x6F, 0xE6, 0x55, 0xE6, 0xEB, 0x21, 0x69, 0xF9, 0x41, 0x2E, 0xE8, 0xAA, + 0x42, 0x2E, 0x73, 0xE8, 0xA6, 0xFF, 0xFC, 0x21, 0x61, 0xF9, 0xA1, 0x04, 0xA2, 0x6C, 0xFD, 0x47, 0x68, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0xC3, 0xE9, 0x79, 0xE9, 0xB5, 0xE9, 0xB5, 0xE9, 0xB5, 0xFF, 0xFB, 0xE9, 0xB5, 0xE9, 0xBB, 0x43, + 0x61, 0x69, 0x6F, 0xF5, 0xB9, 0xFF, 0xEA, 0xE9, 0xB0, 0x42, 0x61, 0x74, 0xF5, 0xAF, 0xE6, 0xBD, 0x41, 0x72, 0xE6, + 0x11, 0x21, 0x65, 0xFC, 0x46, 0x63, 0x6C, 0x6D, 0x70, 0x74, 0x78, 0xF1, 0xA5, 0xFF, 0xBC, 0xFF, 0xE8, 0xFF, 0xF2, + 0xFF, 0xFD, 0xFF, 0x0D, 0xA0, 0x0A, 0x13, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, + 0xA1, 0x06, 0xF3, 0x63, 0xFD, 0x21, 0x69, 0xEC, 0x21, 0x6D, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD, + 0x21, 0x61, 0xDE, 0x21, 0x69, 0xFD, 0xA2, 0x06, 0xF3, 0x6E, 0x78, 0xF5, 0xFD, 0xA0, 0x0A, 0x43, 0x21, 0x6F, 0xFD, + 0x21, 0x6D, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x07, 0x23, 0x6E, 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF6, 0xA6, + 0xF6, 0xA6, 0xF6, 0xA6, 0xFF, 0xFB, 0xF6, 0xA6, 0x48, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE9, 0xF3, + 0xE9, 0xCA, 0xF6, 0x93, 0xF6, 0x93, 0xFF, 0xBF, 0xFF, 0xD8, 0xF6, 0x93, 0xFF, 0xF0, 0x21, 0x72, 0xE7, 0x42, 0x65, + 0x6F, 0xFF, 0xFD, 0xE9, 0x19, 0x43, 0x64, 0x70, 0x73, 0xF0, 0xC5, 0xFF, 0xF9, 0xF1, 0x1F, 0x42, 0x6F, 0x65, 0xE9, + 0x08, 0xF0, 0xB7, 0x42, 0x6C, 0x6D, 0xF6, 0x93, 0xFF, 0xF9, 0x5B, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x75, 0x61, 0x65, 0x69, 0x6F, + 0xE4, 0xDF, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, + 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, + 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xFE, 0xF6, 0xFF, 0x10, 0xFF, 0x62, 0xFF, 0xE8, 0xFF, 0xF9, 0xD6, 0x00, 0x41, + 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0xE4, 0x8D, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, + 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, + 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0x41, 0x6C, 0xF5, 0xF5, 0xD7, 0x00, 0x41, 0x2E, 0x62, 0x63, + 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, + 0x69, 0xE4, 0x44, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, + 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, + 0x47, 0xE4, 0x47, 0xE4, 0x73, 0xE4, 0x73, 0xFF, 0xFC, 0xD6, 0x00, 0x81, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, + 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xE3, 0xFC, 0xE3, 0xFF, + 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, + 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, + 0xE3, 0xFF, 0x41, 0x75, 0xF3, 0x6B, 0x41, 0x66, 0xEF, 0x7D, 0xA0, 0x0D, 0x02, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFD, + 0x21, 0x72, 0xFD, 0x21, 0xA1, 0xFD, 0x44, 0x6E, 0x70, 0x74, 0xC3, 0xFF, 0xED, 0xF5, 0x4D, 0xF5, 0x4D, 0xFF, 0xFD, + 0x41, 0x61, 0xFC, 0x4E, 0x21, 0xAD, 0xFC, 0x21, 0xC3, 0xFD, 0x21, 0x67, 0xFD, 0xD9, 0x00, 0x41, 0x2E, 0x62, 0x63, + 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, + 0x65, 0x69, 0x6F, 0xE3, 0x86, 0xE3, 0x89, 0xE3, 0x8F, 0xE3, 0x89, 0xE3, 0x98, 0xE3, 0x9D, 0xE3, 0x89, 0xE3, 0x89, + 0xE3, 0x89, 0xE3, 0x9D, 0xE3, 0x89, 0xE3, 0xA2, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0xAB, 0xE3, 0x89, 0xE3, + 0x89, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0x89, 0xFF, 0x8A, 0xFF, 0xCF, 0xFF, 0xE6, 0xFF, 0xFD, 0x42, 0x2E, 0x73, 0xE3, + 0x38, 0xF4, 0x32, 0x21, 0x65, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x62, 0xFD, 0x41, 0x2E, 0xE4, 0x07, 0x21, 0x65, 0xFC, + 0x21, 0x74, 0xFD, 0x48, 0x6C, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0xAB, 0xE6, 0xEC, 0xE7, 0x28, 0xE7, + 0x28, 0xE7, 0x28, 0xE7, 0x28, 0xE7, 0x28, 0xE7, 0x2E, 0x21, 0x61, 0xE7, 0x41, 0x6E, 0xE3, 0x8F, 0x21, 0x61, 0xFC, + 0x47, 0x6F, 0x61, 0x6E, 0x67, 0x6C, 0x73, 0x74, 0xF3, 0xF5, 0xFF, 0xD0, 0xFF, 0xDA, 0xFF, 0xF6, 0xFF, 0xFD, 0xF4, + 0xA8, 0xFC, 0x8B, 0xA0, 0x05, 0x51, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0xA0, 0x02, 0xB2, 0xCC, + 0x01, 0xA1, 0x68, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6D, 0x70, 0x71, 0x73, 0x74, 0x76, 0xFF, 0xFD, 0xE4, 0xE9, 0xE4, + 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, + 0x41, 0x69, 0xE6, 0xCA, 0x44, 0x6E, 0x63, 0x6C, 0x78, 0xFF, 0xCF, 0xEE, 0x79, 0xFF, 0xD5, 0xFF, 0xFC, 0x41, 0x72, + 0xE8, 0xA2, 0x21, 0x61, 0xFC, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x12, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x04, + 0xA2, 0x74, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE6, 0x54, 0xFF, 0xFB, 0xE6, 0x90, 0xE6, 0x90, + 0xE6, 0x90, 0xE6, 0x90, 0xE6, 0x96, 0x21, 0x69, 0xEA, 0x41, 0x69, 0xE3, 0x9F, 0x44, 0x63, 0x6C, 0x6E, 0x72, 0xEE, + 0x37, 0xFF, 0xD2, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xE6, 0x62, 0x21, 0x6C, 0xFC, 0x43, 0x6E, 0x72, 0x74, 0xF4, + 0x02, 0xFE, 0xA2, 0xF4, 0x02, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, + 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x65, 0x61, 0x69, 0x75, 0x6F, 0xE2, 0x4B, 0xE2, + 0x4E, 0xE2, 0x54, 0xE2, 0x4E, 0xE2, 0x5D, 0xE2, 0x62, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x62, + 0xF2, 0x24, 0xE2, 0x67, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x70, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, + 0x4E, 0xE2, 0x4E, 0xFF, 0x50, 0xFF, 0xA0, 0xFF, 0xE2, 0xFF, 0xF3, 0xFF, 0xF6, 0xA0, 0x0B, 0x95, 0x21, 0x6E, 0xFD, + 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0xC3, 0x00, 0x71, 0x7A, 0x73, 0x65, 0xE1, 0xF1, 0xE1, 0xF1, 0xFF, 0xFD, 0x41, + 0x74, 0xED, 0xA2, 0x42, 0x2E, 0x72, 0xE1, 0xDE, 0xFF, 0xFC, 0x43, 0x6D, 0x6E, 0x72, 0xF3, 0x81, 0xF3, 0x81, 0xF1, + 0x88, 0x45, 0x63, 0x66, 0x6F, 0x74, 0x75, 0xED, 0x98, 0xED, 0x98, 0xFB, 0x31, 0xF3, 0x77, 0xF2, 0xA5, 0xD9, 0x00, + 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x6F, 0x61, 0x65, 0xE1, 0xBA, 0xE1, 0xBD, 0xE1, 0xC3, 0xE1, 0xBD, 0xE1, 0xCC, 0xE1, 0xD1, + 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xD1, 0xE1, 0xBD, 0xE1, 0xD6, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, + 0xBD, 0xFF, 0xCF, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xFF, 0xDF, 0xFF, 0xE6, 0xFF, 0xF0, + 0xC1, 0x0D, 0x22, 0x6F, 0xE2, 0x8F, 0x42, 0x63, 0x71, 0xFF, 0xFA, 0xF1, 0x1E, 0xC2, 0x00, 0x71, 0x2E, 0x69, 0xE1, + 0x5F, 0xFF, 0xF9, 0xC2, 0x00, 0x71, 0x2E, 0x65, 0xE1, 0x56, 0xED, 0x24, 0x41, 0x74, 0xFE, 0xB9, 0x21, 0x63, 0xFC, + 0x21, 0x6E, 0xFD, 0x41, 0x72, 0xE5, 0x49, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x75, 0xE1, 0x3F, 0xE1, 0x6B, + 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, + 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, + 0xE1, 0x6B, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x70, 0xE2, 0xB2, 0x42, 0x6D, 0x74, 0xFF, 0xFC, 0xEC, 0xBA, 0xD7, 0x00, + 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x6F, 0xE0, 0xE9, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, + 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, + 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xFF, 0xF9, 0x42, 0x61, 0x6F, 0xF1, 0x95, 0xF1, + 0x95, 0x21, 0x74, 0xF9, 0x41, 0x61, 0xF4, 0x4F, 0x21, 0x69, 0xFC, 0x21, 0x6D, 0xFD, 0xA0, 0x10, 0x92, 0x21, 0x65, + 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x10, 0xB2, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, + 0x21, 0x6F, 0xFD, 0x23, 0x65, 0x61, 0x70, 0xE2, 0xEE, 0xFD, 0x44, 0x64, 0x72, 0x6E, 0x74, 0xF1, 0x7E, 0xFF, 0xF9, + 0xF1, 0x42, 0xF9, 0xFB, 0x41, 0x6E, 0xEB, 0x6F, 0x21, 0x6F, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x65, + 0xEC, 0x1B, 0xA0, 0x06, 0x31, 0x41, 0xB1, 0xE1, 0xF2, 0x21, 0xC3, 0xFC, 0x22, 0x2E, 0x65, 0xF6, 0xFD, 0xA1, 0x04, + 0xA2, 0x73, 0xFB, 0x41, 0x61, 0xE6, 0x3D, 0x21, 0x74, 0xFC, 0x21, 0x61, 0xFD, 0xA1, 0x04, 0xA2, 0x6C, 0xFD, 0x41, + 0x6F, 0xE6, 0x2E, 0xA1, 0x04, 0xC2, 0x73, 0xFC, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xE4, 0x2E, 0xE4, 0x2E, 0xFF, + 0xFB, 0xE4, 0x2E, 0xE4, 0x2E, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0xDF, 0xE4, 0x1B, 0xE4, 0x1B, + 0xFF, 0xD3, 0xE4, 0x1B, 0xFF, 0xE2, 0xFF, 0xF0, 0x21, 0x61, 0xEA, 0x23, 0x6E, 0x6C, 0x72, 0xA4, 0xA7, 0xFD, 0x41, + 0x7A, 0xEB, 0xBB, 0x43, 0x63, 0x65, 0x72, 0xF1, 0x9A, 0xFF, 0xFC, 0xF1, 0x9A, 0x42, 0x71, 0x63, 0xE5, 0xE7, 0xFF, + 0xAA, 0x41, 0x65, 0xFF, 0xA3, 0x42, 0x64, 0x74, 0xFD, 0x3A, 0xFF, 0xFC, 0xA2, 0x04, 0xA2, 0x72, 0x6E, 0xEE, 0xF9, + 0x41, 0x65, 0xFD, 0x36, 0x21, 0x69, 0xFC, 0xA1, 0x04, 0xA2, 0x6D, 0xFD, 0xC1, 0x04, 0xA2, 0x72, 0xE1, 0x66, 0x41, + 0x71, 0xE5, 0xBC, 0xA1, 0x04, 0xC2, 0x72, 0xFC, 0x41, 0x65, 0xE5, 0xB3, 0x21, 0x74, 0xFC, 0xA1, 0x04, 0xC2, 0x73, + 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xEF, 0xFF, 0xFB, 0xE3, 0xB0, 0xE3, 0xB0, 0xE3, 0xB0, 0x47, 0x68, + 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0x61, 0xFF, 0xC2, 0xE3, 0x9D, 0xE3, 0x9D, 0xFF, 0xD0, 0xFF, 0xD5, 0xFF, + 0xF0, 0x21, 0x69, 0xEA, 0x41, 0x6F, 0xEA, 0x8B, 0xA1, 0x04, 0x52, 0x72, 0xFC, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, + 0x75, 0xC3, 0xE0, 0x80, 0xE0, 0x83, 0xFF, 0xFB, 0xE0, 0x83, 0xE0, 0x83, 0xE0, 0x83, 0xE0, 0x89, 0x21, 0x61, 0xEA, + 0x21, 0x74, 0xFD, 0x41, 0x72, 0xFC, 0x7C, 0x21, 0x70, 0xFC, 0x41, 0x64, 0xFC, 0x75, 0x22, 0x6D, 0x6E, 0xF9, 0xFC, + 0xA0, 0x0E, 0x13, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x70, 0xFD, 0xA0, 0x0E, 0x43, 0x21, 0x74, 0xFD, 0x21, + 0x63, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xD8, 0x22, 0x6C, 0x73, 0xFA, 0xFD, 0x41, 0x2E, 0xE1, 0x38, 0x42, 0x2E, + 0x73, 0xE1, 0x34, 0xFF, 0xFC, 0x42, 0x61, 0x73, 0xFF, 0xF9, 0xE1, 0xD0, 0x24, 0x69, 0x6F, 0x65, 0x74, 0xC9, 0xD7, + 0xE9, 0xF9, 0x43, 0x6C, 0x72, 0x73, 0xFF, 0x8D, 0xFF, 0xB2, 0xFF, 0xF7, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, + 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x75, + 0x65, 0x61, 0x69, 0x6F, 0xDF, 0x00, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, + 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xEE, 0xD9, 0xDF, 0x03, 0xDF, 0x03, 0xFD, 0xA1, 0xFD, 0xAA, 0xDF, 0x03, 0xDF, 0x03, + 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xFD, 0xC1, 0xFE, 0x17, 0xFE, 0x66, 0xFE, 0x95, 0xFF, 0x08, 0xFF, 0x13, 0xFF, + 0xF6, 0x42, 0x6D, 0x72, 0xDF, 0x3C, 0xEA, 0x76, 0x42, 0x65, 0x69, 0xFC, 0xC6, 0xFF, 0xF9, 0xD7, 0x00, 0x41, 0x2E, + 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x75, 0xDE, 0x9E, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, + 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, + 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xFF, 0xF9, 0xC2, 0x00, 0x71, 0x6E, 0x61, 0xDE, 0x5C, 0xEE, + 0xD0, 0x41, 0xA1, 0xF0, 0xDB, 0x43, 0x61, 0xC3, 0x65, 0xF0, 0xD7, 0xFF, 0xFC, 0xF0, 0xD7, 0x21, 0x69, 0xF6, 0x21, + 0x63, 0xFD, 0xA0, 0x0A, 0x72, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69, + 0xC3, 0xEE, 0xFD, 0x21, 0x6E, 0xFB, 0x42, 0x65, 0x72, 0xE2, 0x3D, 0xE9, 0xEC, 0x22, 0x69, 0x74, 0xF6, 0xF9, 0xA0, + 0x0B, 0xB1, 0x23, 0xA1, 0xA9, 0xAD, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0xC3, 0x65, 0x6F, 0xF6, 0xF9, 0xF6, 0xF6, 0x43, + 0x64, 0x6E, 0x72, 0xF5, 0xFA, 0xED, 0xB7, 0xFF, 0xF7, 0x41, 0x6D, 0xEF, 0xA6, 0xD9, 0x00, 0x41, 0x2E, 0x62, 0x63, + 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x72, + 0x65, 0x61, 0x6F, 0xDD, 0xF5, 0xDD, 0xF8, 0xDD, 0xFE, 0xDD, 0xF8, 0xDE, 0x07, 0xDE, 0x0C, 0xDD, 0xF8, 0xDD, 0xF8, + 0xDD, 0xF8, 0xDD, 0xF8, 0xFF, 0x9F, 0xDD, 0xF8, 0xDE, 0x11, 0xDD, 0xF8, 0xDD, 0xF8, 0xDE, 0x1A, 0xDD, 0xF8, 0xDD, + 0xF8, 0xDD, 0xF8, 0xDD, 0xF8, 0xDD, 0xF8, 0xDE, 0x24, 0xFF, 0xDA, 0xFF, 0xF2, 0xFF, 0xFC, 0xC4, 0x00, 0x71, 0x74, + 0x73, 0x6E, 0x61, 0xDD, 0xAD, 0xDD, 0xAD, 0xDD, 0xAD, 0xEE, 0x21, 0xA0, 0x00, 0xD1, 0x21, 0x2E, 0xFD, 0x22, 0x2E, + 0x73, 0xFA, 0xFD, 0xA0, 0x03, 0x02, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x22, 0x2E, 0x65, 0xEC, 0xFD, 0x21, 0x6C, + 0xFB, 0x22, 0x2E, 0x73, 0xEF, 0xF2, 0x21, 0x6E, 0xED, 0x21, 0xB3, 0xFD, 0x21, 0x65, 0xEA, 0x21, 0x6E, 0xFD, 0x23, + 0x61, 0xC3, 0x6F, 0xEF, 0xF7, 0xFD, 0x21, 0x6C, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x73, 0xC9, 0x23, 0x2E, 0x61, 0x65, + 0xC3, 0xC9, 0xFD, 0x21, 0x72, 0xF9, 0xC6, 0x00, 0x71, 0x7A, 0x73, 0x65, 0x61, 0x69, 0x6F, 0xDD, 0x57, 0xDD, 0x57, + 0xFF, 0xBF, 0xFF, 0xD2, 0xFF, 0xF0, 0xFF, 0xFD, 0x41, 0x74, 0xDF, 0xF2, 0x21, 0x63, 0xFC, 0x41, 0x76, 0xDE, 0x67, + 0x44, 0x6E, 0x2E, 0x73, 0x6C, 0xFF, 0xF9, 0xF5, 0xEC, 0xF5, 0xEF, 0xFF, 0xFC, 0x41, 0x65, 0xFA, 0x22, 0x41, 0x76, + 0xE8, 0xEA, 0xA0, 0x0E, 0xD2, 0xA0, 0x0E, 0xF3, 0xA0, 0x0F, 0x23, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21, + 0x6F, 0xF1, 0x21, 0x64, 0xFD, 0x44, 0x6C, 0x6D, 0x72, 0x75, 0xFF, 0xCF, 0xFA, 0x44, 0xFF, 0xD3, 0xFF, 0xFD, 0xA0, + 0x0F, 0x52, 0xA1, 0x0F, 0x52, 0x73, 0xFD, 0x21, 0x61, 0xFB, 0xA1, 0x04, 0x52, 0x73, 0xFD, 0x47, 0x68, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0xC3, 0xDD, 0xE5, 0xFF, 0xFB, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xEE, 0x21, + 0x65, 0xEA, 0x21, 0x72, 0xFD, 0x42, 0x62, 0x63, 0xFF, 0xFD, 0xF4, 0xB1, 0xA0, 0x0F, 0xF3, 0xA1, 0x06, 0xF3, 0x72, + 0xFD, 0x41, 0x72, 0xF9, 0xC6, 0xA1, 0x06, 0xF3, 0x6F, 0xFC, 0xA0, 0x10, 0x23, 0x41, 0x2E, 0xF7, 0x64, 0x42, 0x2E, + 0x73, 0xF7, 0x60, 0xFF, 0xFC, 0x21, 0x74, 0xF9, 0x21, 0x69, 0xFD, 0xA2, 0x07, 0x23, 0x72, 0x76, 0xEC, 0xFD, 0x45, + 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xF9, 0xEE, 0x03, 0xEE, 0x03, 0xEE, 0x03, 0xEE, 0x03, 0x48, 0x72, 0x68, 0x61, + 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE1, 0x50, 0xE1, 0x27, 0xFF, 0xC7, 0xED, 0xF0, 0xFF, 0xD0, 0xED, 0xF0, 0xED, 0xF0, + 0xFF, 0xF0, 0x21, 0x72, 0xE7, 0xC7, 0x07, 0xB1, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xDD, 0x6A, 0xDD, 0x6D, + 0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x73, 0x21, 0x61, 0xE8, 0x22, 0x65, 0x72, 0xE2, 0xFD, 0xA0, + 0x11, 0x43, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, + 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x23, 0x70, 0x64, 0x72, 0xE0, 0xFD, 0xFD, 0xDA, 0x00, 0x41, 0x2E, 0x62, + 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, + 0x7A, 0x61, 0x65, 0x6F, 0x75, 0xDC, 0x19, 0xDC, 0x1C, 0xDC, 0x22, 0xDC, 0x1C, 0xDC, 0x2B, 0xDC, 0x30, 0xDC, 0x1C, + 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x30, 0xDC, 0x1C, 0xFE, 0x72, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xFE, + 0xC8, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xFE, 0xE8, 0xFF, 0x26, 0xFF, 0x5F, 0xFF, 0xF9, + 0x41, 0x65, 0xE1, 0x23, 0x41, 0x6E, 0xDF, 0x92, 0x21, 0x69, 0xFC, 0x22, 0x74, 0x64, 0xF5, 0xFD, 0x41, 0x6C, 0xDF, + 0x86, 0x21, 0x65, 0xFC, 0x21, 0x75, 0xFD, 0x41, 0x62, 0xDF, 0x7C, 0x21, 0x6F, 0xFC, 0x41, 0x72, 0xDF, 0x75, 0x21, + 0x61, 0xFC, 0x43, 0x63, 0x70, 0x74, 0xFF, 0xF6, 0xDF, 0x6E, 0xFF, 0xFD, 0x41, 0xA1, 0xDF, 0x8F, 0x21, 0xC3, 0xFC, + 0x21, 0x6C, 0xFD, 0x24, 0x6E, 0x62, 0x6C, 0x74, 0xCF, 0xDB, 0xEC, 0xFD, 0x21, 0xA1, 0xBF, 0x21, 0xC3, 0xFD, 0x21, + 0x65, 0xFD, 0x21, 0x63, 0xFD, 0x41, 0x2E, 0xE4, 0xB3, 0x42, 0x2E, 0x73, 0xE4, 0xAF, 0xFF, 0xFC, 0x22, 0x6F, 0x61, + 0xF9, 0xF9, 0x21, 0x72, 0xFB, 0x23, 0x61, 0x6F, 0x65, 0xD8, 0xEA, 0xFD, 0x41, 0x73, 0xDE, 0x49, 0x21, 0x61, 0xFC, + 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x64, 0xE0, 0x00, 0x41, 0x6C, 0xDF, 0xFC, 0x41, 0x69, 0xE9, 0x65, 0x42, + 0x63, 0x74, 0xDF, 0xF7, 0xFF, 0xFC, 0xA4, 0x0E, 0xB2, 0x6D, 0x6E, 0x74, 0x63, 0xEA, 0xED, 0xF1, 0xF9, 0x41, 0xBA, + 0xE4, 0x87, 0x41, 0x75, 0xDF, 0xE5, 0xA2, 0x0E, 0xB2, 0xC3, 0x78, 0xF8, 0xFC, 0x41, 0x6E, 0xDF, 0xDA, 0x21, 0x61, + 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0xB3, 0xF0, 0x22, 0x6F, 0xC3, 0xED, 0xFD, 0x21, + 0x69, 0xFB, 0x41, 0x2E, 0xDB, 0x9E, 0x42, 0x2E, 0x73, 0xDB, 0x9A, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x41, + 0xAD, 0xDF, 0xAF, 0x43, 0x69, 0xC3, 0x65, 0xDF, 0xAB, 0xFF, 0xFC, 0xDF, 0xAB, 0x41, 0xA1, 0xDF, 0xA1, 0x43, 0x61, + 0xC3, 0x6F, 0xDF, 0x9D, 0xFF, 0xFC, 0xDF, 0x9D, 0x41, 0x61, 0xE4, 0x31, 0x21, 0x76, 0xFC, 0x41, 0x74, 0xDD, 0x80, + 0x41, 0x69, 0xDF, 0x88, 0xA1, 0x0E, 0x92, 0x72, 0xFC, 0x41, 0x76, 0xDF, 0x7F, 0x45, 0x61, 0xC3, 0x65, 0x6F, 0x69, + 0xDF, 0x7B, 0xDF, 0xF0, 0xDF, 0x7B, 0xFF, 0xF7, 0xFF, 0xFC, 0xC8, 0x0E, 0xB2, 0x62, 0x63, 0x64, 0x67, 0x6A, 0x6C, + 0x73, 0x74, 0xFF, 0x9E, 0xFF, 0xA9, 0xFF, 0xB7, 0xFF, 0xC0, 0xFF, 0xCE, 0xFF, 0xDC, 0xFF, 0xDF, 0xFF, 0xF0, 0x41, + 0x65, 0xDF, 0x49, 0x41, 0x69, 0xDD, 0x81, 0x21, 0x63, 0xFC, 0x21, 0x61, 0xFD, 0xA2, 0x0E, 0xB2, 0x63, 0x72, 0xF2, + 0xFD, 0xC3, 0x0E, 0xB2, 0x72, 0x62, 0x73, 0xDF, 0x34, 0xE1, 0xB9, 0xE0, 0xFB, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, + 0x75, 0xC3, 0xDF, 0x28, 0xFF, 0x3B, 0xFF, 0x4E, 0xFF, 0xC4, 0xFF, 0xED, 0xFF, 0xF4, 0xE5, 0xE3, 0x21, 0x73, 0xEA, + 0x42, 0x73, 0x6E, 0xFE, 0xFB, 0xFF, 0xFD, 0x41, 0x70, 0xE6, 0x22, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, + 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x6F, + 0xDA, 0x54, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, + 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, + 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xFF, 0xF5, 0xFF, 0xFC, 0x41, 0x68, 0xEC, 0xA2, 0x21, 0x63, 0xFC, 0xC2, 0x01, + 0xF2, 0x2E, 0x73, 0xDA, 0x02, 0xFF, 0xFD, 0xC1, 0x01, 0xF2, 0x2E, 0xD9, 0xF9, 0xA0, 0x01, 0xF2, 0x42, 0x61, 0x72, + 0xF6, 0xB8, 0xDB, 0x22, 0x41, 0x65, 0xDE, 0x04, 0x42, 0x61, 0x6D, 0xDE, 0x00, 0xE5, 0xAF, 0x44, 0x74, 0x6C, 0x63, + 0x72, 0xFF, 0xEE, 0xFF, 0xF5, 0xEA, 0x58, 0xFF, 0xF9, 0x41, 0x75, 0xEC, 0x56, 0x41, 0x6F, 0xEC, 0x52, 0x22, 0x71, + 0x63, 0xF8, 0xFC, 0x21, 0x6F, 0xFB, 0x41, 0x6C, 0xEA, 0x9C, 0x41, 0x70, 0xEB, 0x6A, 0x41, 0x62, 0xE5, 0x83, 0x21, + 0x72, 0xFC, 0x41, 0x63, 0xDA, 0x91, 0x21, 0x69, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD, 0xDC, + 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x74, 0x76, 0x77, 0x79, + 0x72, 0x7A, 0x73, 0x6C, 0x78, 0x65, 0x69, 0x61, 0x6F, 0x75, 0xC3, 0xD9, 0xA2, 0xD9, 0xA5, 0xD9, 0xAB, 0xD9, 0xA5, + 0xD9, 0xB4, 0xD9, 0xB9, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xB9, 0xD9, 0xA5, 0xD9, 0xBE, 0xD9, 0xA5, 0xD9, + 0xC7, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xA5, 0xFF, 0x4E, 0xFF, 0xA0, 0xFF, 0xA9, 0xFF, 0xAF, 0xFF, 0xAF, 0xFF, 0xC4, + 0xFF, 0xDE, 0xFF, 0xE1, 0xFF, 0xE5, 0xFF, 0xED, 0xFF, 0xFD, 0x42, 0x63, 0x64, 0xFF, 0x62, 0xF8, 0xFA, 0xD7, 0x00, + 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0x6C, 0x72, 0x69, 0xD9, 0x44, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, + 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, + 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x73, 0xD9, 0x73, 0xFF, 0xF9, 0x41, 0x73, 0xFE, 0xF3, 0xD7, 0x00, + 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x61, 0xD8, 0xF8, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, + 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, + 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xFF, 0xFC, 0x42, 0x6E, 0x72, 0xEA, 0x5D, 0xEA, + 0x5D, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x65, 0x69, 0xD8, 0xA9, 0xD8, 0xAC, 0xD8, 0xB2, 0xD8, 0xAC, 0xD8, 0xBB, + 0xD8, 0xC0, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xC0, 0xD8, 0xAC, 0xD8, 0xC5, 0xD8, 0xAC, 0xD8, + 0xAC, 0xD8, 0xAC, 0xD8, 0xCE, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xFF, 0xF9, 0xF4, 0x61, + 0xD6, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xD8, 0x5E, 0xD8, 0x61, 0xD8, 0x67, 0xD8, 0x61, 0xD8, 0x70, 0xD8, 0x75, 0xD8, + 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x75, 0xD8, 0x61, 0xD8, 0x7A, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, + 0xD8, 0x83, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0x41, 0x6F, 0xF1, 0x80, 0xD7, 0x00, 0x41, + 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x6F, 0xD8, 0x15, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, + 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, + 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xFF, 0xFC, 0xC1, 0x00, 0x41, 0x2E, 0xD7, 0xCD, 0x41, + 0x73, 0xE8, 0xC1, 0xA0, 0x02, 0x82, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x43, 0x65, 0x6F, 0x61, 0xF4, + 0x80, 0xF4, 0x80, 0xFF, 0xFB, 0x21, 0x6C, 0xF6, 0x21, 0x65, 0xFD, 0x43, 0x65, 0x6F, 0x61, 0xF4, 0x70, 0xF4, 0x70, + 0xF4, 0x70, 0x21, 0x6C, 0xF6, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFA, 0x21, 0x6F, 0xFD, 0xA0, 0x02, 0xD2, 0x21, 0x2E, + 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x22, 0x6F, 0x61, 0xFB, 0xFB, 0x21, 0x63, 0xFB, 0x21, 0x69, 0xFD, 0x25, 0x6D, + 0x74, 0x73, 0x6E, 0x72, 0xD1, 0xD1, 0xE1, 0xE7, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xE5, 0xE5, 0xE5, 0x21, 0x6C, 0xF9, + 0x21, 0x73, 0xFD, 0x25, 0x6D, 0x74, 0x73, 0x6E, 0x6F, 0xB9, 0xB9, 0xC9, 0xCF, 0xFD, 0x46, 0x73, 0x69, 0x2E, 0x64, + 0x6F, 0x72, 0xD7, 0x59, 0xFF, 0x92, 0xD7, 0x59, 0xFF, 0xDD, 0xFF, 0xC1, 0xFF, 0xF5, 0x41, 0x73, 0xFF, 0x86, 0x21, + 0x6F, 0xFC, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xD7, 0x3F, 0xE8, 0x39, 0xFF, 0xFD, 0xFF, 0x78, 0xE8, 0x39, 0xA0, + 0x02, 0xA3, 0x21, 0x2E, 0xFD, 0x23, 0x2E, 0x73, 0x69, 0xFA, 0xFD, 0xE3, 0x42, 0x6F, 0x61, 0xF3, 0xEA, 0xF3, 0xEA, + 0x21, 0x63, 0xF9, 0x43, 0x65, 0x61, 0x69, 0xFF, 0xEF, 0xF3, 0xE0, 0xFF, 0xFD, 0x41, 0x6F, 0xF3, 0xD6, 0x22, 0x74, + 0x6D, 0xF2, 0xFC, 0x41, 0x73, 0xFF, 0x76, 0x21, 0x65, 0xFC, 0x21, 0x6F, 0xF9, 0x41, 0x65, 0xFF, 0x6F, 0x21, 0x6C, + 0xFC, 0x47, 0x61, 0x2E, 0x73, 0x74, 0x6D, 0x64, 0x62, 0xFF, 0xB5, 0xD6, 0xF4, 0xFF, 0xEA, 0xFF, 0xF3, 0xFF, 0xF6, + 0xFF, 0x6D, 0xFF, 0xFD, 0x21, 0x6D, 0xE0, 0x42, 0x2E, 0x65, 0xD6, 0xDB, 0xFF, 0xFD, 0x21, 0x61, 0xF6, 0xA0, 0x02, + 0xA2, 0x42, 0x2E, 0x73, 0xFF, 0xFD, 0xFF, 0xA2, 0x42, 0x2E, 0x73, 0xFF, 0x98, 0xFF, 0x9B, 0x23, 0x65, 0x6F, 0x61, + 0xF2, 0xF2, 0xF9, 0x21, 0x6C, 0xF9, 0x21, 0x65, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xEC, 0xEC, 0xEC, 0x21, 0x6C, 0xF9, + 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFA, 0x21, 0x6F, 0xFD, 0x47, 0x61, 0x65, 0x6D, 0x74, 0x73, 0x6E, 0x6F, 0xFF, 0xC2, + 0xFF, 0xC2, 0xFF, 0xEA, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0x08, 0x44, 0x6D, 0x74, 0x73, 0x6E, 0xFE, 0xDF, + 0xFE, 0xDF, 0xFE, 0xEF, 0xFE, 0xF5, 0x41, 0x6F, 0xFE, 0xB6, 0x43, 0x6F, 0x61, 0x65, 0xF3, 0x41, 0xF3, 0x41, 0xF3, + 0x41, 0x42, 0x2E, 0x6C, 0xD6, 0x6F, 0xFF, 0xF6, 0x21, 0x65, 0xF9, 0x41, 0x65, 0xE7, 0x5F, 0x44, 0x2E, 0x6D, 0x6C, + 0x6E, 0xD6, 0x61, 0xFF, 0xFC, 0xFF, 0xE8, 0xFF, 0xE4, 0x21, 0x65, 0xF3, 0x46, 0x6C, 0x6E, 0x6F, 0x6D, 0x74, 0x73, + 0xFE, 0xA9, 0xFF, 0xD4, 0xFE, 0x8A, 0xFF, 0xE9, 0xFF, 0xFD, 0xFF, 0xFD, 0x21, 0x6F, 0xED, 0x21, 0x64, 0xFD, 0x47, + 0x73, 0x69, 0x62, 0x72, 0x64, 0x6F, 0x6E, 0xFF, 0x5D, 0xFE, 0x71, 0xFF, 0x64, 0xFF, 0x98, 0xFF, 0xAE, 0xFE, 0xA0, + 0xFF, 0xFD, 0x41, 0x67, 0xFE, 0x9B, 0x21, 0x6F, 0xFC, 0x41, 0x63, 0xD6, 0x1E, 0x21, 0x69, 0xFC, 0x41, 0x65, 0xFF, + 0x06, 0x21, 0x74, 0xFC, 0x45, 0x2E, 0x6C, 0x74, 0x6E, 0x73, 0xD6, 0x0D, 0xFF, 0xEF, 0xFF, 0xF6, 0xE7, 0x07, 0xFF, + 0xFD, 0x45, 0xB1, 0xA9, 0xAD, 0xA1, 0xB3, 0xFE, 0x30, 0xFE, 0xA4, 0xFF, 0x09, 0xFF, 0xC5, 0xFF, 0xF0, 0xA0, 0x01, + 0x72, 0xA0, 0x01, 0x92, 0x21, 0xB3, 0xFD, 0x22, 0x75, 0xC3, 0xF7, 0xFD, 0x21, 0x65, 0xF2, 0xA0, 0x02, 0x62, 0x21, + 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x65, 0xF5, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, + 0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73, 0x6D, 0xE6, 0xE9, 0xFD, 0x22, 0x6F, 0x61, 0xE5, 0xF9, 0x21, 0x63, 0xFB, 0x21, + 0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0x61, 0xD4, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x67, + 0xFD, 0x41, 0x67, 0xE1, 0x68, 0x23, 0xC3, 0x6F, 0x69, 0xED, 0xF9, 0xFC, 0xA0, 0x00, 0xC2, 0x21, 0x2E, 0xFD, 0x22, + 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x65, 0xF8, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73, + 0x6D, 0xE9, 0xEC, 0xFD, 0x44, 0x2E, 0x6F, 0x61, 0x74, 0xD5, 0x78, 0xFF, 0xE8, 0xFF, 0xF9, 0xF5, 0x24, 0x42, 0x6F, + 0x61, 0xD9, 0x83, 0xD9, 0x83, 0x21, 0x74, 0xF9, 0x41, 0x6E, 0xF2, 0xAF, 0x43, 0x63, 0x74, 0x65, 0xE7, 0x07, 0xE7, + 0x07, 0xFD, 0x93, 0x41, 0x74, 0xE6, 0xFD, 0x41, 0x69, 0xE5, 0x70, 0x41, 0x61, 0xD6, 0xF0, 0x21, 0xAD, 0xFC, 0x21, + 0xC3, 0xFD, 0xA1, 0x04, 0xA2, 0x70, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xD9, 0x07, 0xD9, 0x43, + 0xFF, 0xFB, 0xD9, 0x43, 0xD9, 0x43, 0xD9, 0x43, 0xD9, 0x49, 0x21, 0x6F, 0xEA, 0x22, 0x6E, 0x74, 0xD4, 0xFD, 0xA0, + 0x00, 0x91, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x0F, 0x72, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, + 0x22, 0x6F, 0x61, 0xFB, 0xFB, 0xA0, 0x03, 0x32, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0xA0, 0x10, 0xF3, + 0x21, 0x2E, 0xFD, 0x23, 0x61, 0x2E, 0x73, 0xF5, 0xFA, 0xFD, 0x21, 0x73, 0xEB, 0x22, 0x2E, 0x65, 0xE5, 0xFD, 0x21, + 0x6C, 0xFB, 0x22, 0x65, 0x61, 0xEE, 0xFD, 0x22, 0x63, 0x64, 0xD3, 0xFB, 0x4D, 0x65, 0x61, 0x2E, 0x78, 0x6C, 0x73, + 0x63, 0x6D, 0x6E, 0x70, 0x72, 0x6F, 0x69, 0xFE, 0xF1, 0xFE, 0xF6, 0xD4, 0xD5, 0xFF, 0x04, 0xFF, 0x3B, 0xFF, 0x60, + 0xFF, 0x74, 0xFF, 0x77, 0xFF, 0x7B, 0xFF, 0x85, 0xFF, 0xB5, 0xFF, 0xC0, 0xFF, 0xFB, 0x42, 0x65, 0xC3, 0xFE, 0xC0, + 0xFE, 0xC6, 0xA0, 0x03, 0x23, 0x21, 0x2E, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x67, 0xFD, 0x43, 0x2E, + 0x73, 0x69, 0xD4, 0x97, 0xE5, 0x91, 0xFC, 0xD0, 0x21, 0x65, 0xF6, 0x41, 0x73, 0xFE, 0xB1, 0x44, 0x2E, 0x73, 0x69, + 0x6E, 0xFE, 0xAA, 0xFE, 0xAD, 0xFF, 0xFC, 0xFE, 0xAD, 0x43, 0x2E, 0x74, 0x65, 0xD4, 0x79, 0xFF, 0xEC, 0xFF, 0xF3, + 0xA0, 0x0C, 0xF1, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA0, 0x11, 0x22, 0x21, 0x6E, 0xFD, 0x21, + 0x61, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x23, 0x6F, 0x69, 0x65, 0xC7, 0xEB, 0xFD, 0x42, + 0x6F, 0x72, 0xD4, 0x4A, 0xE0, 0x14, 0x45, 0x2E, 0x64, 0x66, 0x67, 0x74, 0xD4, 0x43, 0xFF, 0xF9, 0xF1, 0x94, 0xE5, + 0xEC, 0xFA, 0x5A, 0x41, 0x65, 0xFC, 0x6C, 0x42, 0x6E, 0x73, 0xFE, 0x56, 0xFE, 0x56, 0x41, 0x6F, 0xFF, 0x9E, 0x41, + 0x73, 0xFE, 0x48, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xFE, 0x44, 0xFE, 0x47, 0xFF, 0xF8, 0xFF, 0xFC, 0xFE, 0x47, + 0x42, 0x61, 0x73, 0xFF, 0xF0, 0xFE, 0x37, 0x43, 0x2E, 0x73, 0x69, 0xFE, 0x2D, 0xFE, 0x30, 0xFF, 0x7F, 0x43, 0x73, + 0x2E, 0x6E, 0xFE, 0x26, 0xFE, 0x23, 0xFE, 0x26, 0x23, 0xAD, 0xA9, 0xA1, 0xE5, 0xEC, 0xF6, 0x45, 0x6D, 0x2E, 0x73, + 0x69, 0x6E, 0xFF, 0xC6, 0xFE, 0x12, 0xFE, 0x15, 0xFF, 0x64, 0xFE, 0x15, 0xA0, 0x03, 0x22, 0x21, 0x2E, 0xFD, 0x21, + 0x65, 0xFD, 0x41, 0x65, 0xFF, 0x32, 0x42, 0x2E, 0x73, 0xFF, 0x2B, 0xFF, 0x2E, 0x23, 0x65, 0x61, 0x6F, 0xF9, 0xF9, + 0xF9, 0x41, 0x73, 0xFF, 0x20, 0x21, 0x6F, 0xFC, 0x41, 0x68, 0xD7, 0xC2, 0xC2, 0x00, 0xD1, 0x2E, 0x73, 0xFD, 0xDC, + 0xFD, 0xDF, 0x22, 0x6F, 0x61, 0xF7, 0xF7, 0x4C, 0x6F, 0xC3, 0x65, 0x61, 0x2E, 0x6D, 0x74, 0x6C, 0x73, 0x6E, 0x63, + 0x69, 0xFF, 0x7B, 0xFF, 0xB5, 0xFF, 0xBC, 0xFF, 0x24, 0xD3, 0xAA, 0xFF, 0xD2, 0xFF, 0xD5, 0xFF, 0xE0, 0xFF, 0xD5, + 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xFB, 0x41, 0x61, 0xFE, 0xFF, 0x43, 0x65, 0x6F, 0x61, 0xF0, 0x49, 0xF0, 0x49, 0xFB, + 0xBA, 0x43, 0x2E, 0x61, 0x65, 0xFD, 0x9B, 0xFD, 0xA1, 0xFE, 0xED, 0x43, 0x2E, 0x73, 0x72, 0xFD, 0x91, 0xFD, 0x94, + 0xFF, 0xF6, 0x48, 0x2E, 0x6D, 0x74, 0x6C, 0x6E, 0x6F, 0x61, 0x65, 0xD3, 0x63, 0xFC, 0xFE, 0xFC, 0xFE, 0xFF, 0xE2, + 0xFC, 0xE6, 0xFF, 0xF6, 0xFD, 0x8D, 0xE3, 0xDD, 0xA0, 0x05, 0x41, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, + 0xFD, 0x41, 0x6E, 0xFD, 0x65, 0x21, 0xB3, 0xFC, 0x41, 0x65, 0xFE, 0xAD, 0x21, 0x6E, 0xFC, 0x22, 0xC3, 0x6F, 0xF6, + 0xFD, 0x43, 0x74, 0x61, 0x69, 0xE4, 0xD8, 0xFF, 0xEA, 0xFF, 0xFB, 0x41, 0x72, 0xE4, 0xCE, 0x41, 0x64, 0xFE, 0xBA, + 0x21, 0x61, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x42, 0x72, 0x69, 0xE4, + 0xB7, 0xFF, 0xFD, 0x41, 0x69, 0xE2, 0xB7, 0x41, 0x74, 0xED, 0x7B, 0x43, 0x64, 0x73, 0x74, 0xEA, 0xF2, 0xFF, 0xFC, + 0xE4, 0xA8, 0x41, 0x73, 0xEC, 0xE8, 0x42, 0x2E, 0x65, 0xD2, 0xF0, 0xFF, 0xFC, 0xA0, 0x08, 0xF1, 0x21, 0x2E, 0xFD, + 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x73, 0xFD, 0x21, 0xAD, 0xFD, 0x53, 0x61, 0x69, 0x73, 0x2E, + 0x6D, 0x6E, 0x74, 0x72, 0x62, 0x64, 0x6F, 0x63, 0x65, 0x66, 0x67, 0x70, 0x75, 0x6C, 0xC3, 0xFE, 0x25, 0xFE, 0x38, + 0xFE, 0x59, 0xD2, 0xD2, 0xFE, 0x81, 0xFE, 0x8F, 0xFE, 0x9F, 0xFF, 0x28, 0xFF, 0x4D, 0xFF, 0x6F, 0xFB, 0x0B, 0xFF, + 0xA7, 0xFF, 0xB1, 0xFF, 0xC8, 0xFF, 0xB1, 0xFF, 0xCF, 0xFF, 0xD7, 0xFF, 0xE5, 0xFF, 0xFD, 0xA0, 0x01, 0xB2, 0x21, + 0xA1, 0xFD, 0x43, 0xC3, 0x65, 0x73, 0xFF, 0xFD, 0xD2, 0xF0, 0xE3, 0x8C, 0x41, 0x65, 0xEB, 0xDA, 0x21, 0x6C, 0xFC, + 0x41, 0x65, 0xE4, 0xF6, 0x21, 0x72, 0xFC, 0x21, 0x65, 0xFD, 0x43, 0x2E, 0x63, 0x74, 0xD2, 0x77, 0xFF, 0xF3, 0xFF, + 0xFD, 0x41, 0x61, 0xD2, 0x6D, 0x21, 0x63, 0xFC, 0x21, 0x6F, 0xFD, 0x41, 0x6F, 0xD5, 0x4C, 0x43, 0x6F, 0x62, 0x69, + 0xFD, 0xD5, 0xFF, 0xF9, 0xFF, 0xFC, 0xA0, 0x01, 0xB1, 0x21, 0x72, 0xFD, 0x21, 0x62, 0xFD, 0x21, 0x65, 0xFD, 0x43, + 0x65, 0x6F, 0x72, 0xEC, 0xC5, 0xD6, 0x64, 0xDE, 0x0C, 0x45, 0x2E, 0x68, 0x64, 0x65, 0x74, 0xD2, 0x3F, 0xFF, 0xF3, + 0xE3, 0xEC, 0xEB, 0xCF, 0xFF, 0xF6, 0xA0, 0x04, 0x13, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21, + 0x6D, 0xFD, 0x42, 0x2E, 0x65, 0xFC, 0x44, 0xFF, 0xFD, 0xA0, 0x03, 0xC2, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21, + 0x61, 0xED, 0x22, 0x61, 0x65, 0xEA, 0xEA, 0x46, 0x73, 0x2E, 0x6E, 0x69, 0x62, 0x72, 0xFF, 0xE8, 0xFC, 0x2C, 0xFC, + 0x2F, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFB, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xFC, 0x19, 0xFC, 0x1C, 0xFD, 0xCD, + 0xFD, 0x6B, 0xFC, 0x1C, 0x42, 0x73, 0x61, 0xFC, 0x0C, 0xFF, 0xF0, 0x43, 0xA9, 0xA1, 0xAD, 0xFD, 0xD5, 0xFF, 0xD6, + 0xFF, 0xF9, 0xA0, 0x02, 0xF3, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6D, 0xFD, 0x43, 0x65, + 0x61, 0x6F, 0xEE, 0x8D, 0xEE, 0x8D, 0xEE, 0x8D, 0x43, 0x2E, 0x73, 0x69, 0xFF, 0xA2, 0xFF, 0xA5, 0xFF, 0xA8, 0x21, + 0x65, 0xF6, 0xA0, 0x03, 0xE3, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x24, 0x2E, 0x73, 0x69, 0x6E, 0xF7, 0xFA, 0xFD, + 0xFA, 0x43, 0x2E, 0x74, 0x65, 0xFF, 0x83, 0xFF, 0xEB, 0xFF, 0xF7, 0x21, 0x6F, 0xEA, 0x41, 0x65, 0xFF, 0x7C, 0x21, + 0x6E, 0xE0, 0x21, 0x73, 0xDA, 0x25, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xD7, 0xDA, 0xF3, 0xFD, 0xDA, 0x22, 0x61, 0x73, + 0xF5, 0xCF, 0x23, 0x2E, 0x73, 0x69, 0xC7, 0xCA, 0xCD, 0x23, 0x73, 0x2E, 0x6E, 0xC3, 0xC0, 0xC3, 0x23, 0xAD, 0xA9, + 0xA1, 0xED, 0xF2, 0xF9, 0x45, 0x6D, 0x2E, 0x73, 0x69, 0x6E, 0xFF, 0xCE, 0xFF, 0xB2, 0xFF, 0xB5, 0xFF, 0xB8, 0xFF, + 0xB5, 0x44, 0x6F, 0xC3, 0x65, 0x61, 0xFF, 0xC5, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, 0xAB, 0xA0, 0x10, 0x54, 0x21, 0x2E, + 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73, 0x6D, 0xEE, 0xF1, + 0xFD, 0x21, 0x65, 0xF9, 0x42, 0x61, 0x6C, 0xFF, 0x82, 0xFF, 0xFD, 0x42, 0x2E, 0x73, 0xFF, 0x72, 0xFF, 0x75, 0x43, + 0x2E, 0x61, 0x65, 0xFF, 0x6B, 0xFF, 0xF9, 0xFF, 0x71, 0x43, 0x2E, 0x73, 0x72, 0xFF, 0x61, 0xFF, 0x64, 0xFF, 0xF6, + 0x43, 0x2E, 0x6F, 0x61, 0xFE, 0xEC, 0xFF, 0xF6, 0xFF, 0xE5, 0x47, 0x73, 0x6D, 0x6E, 0x74, 0x72, 0x62, 0x64, 0xFF, + 0x5F, 0xFF, 0x69, 0xFE, 0xE5, 0xFF, 0x6C, 0xFF, 0xAB, 0xFF, 0xD4, 0xFF, 0xF6, 0x42, 0x2E, 0x65, 0xFB, 0x09, 0xFC, + 0x5B, 0x21, 0x64, 0xF9, 0x21, 0x61, 0xFD, 0x21, 0x64, 0xFD, 0x45, 0x2E, 0x65, 0x61, 0x6D, 0x69, 0xFA, 0xF9, 0xFC, + 0x4B, 0xFA, 0xFF, 0xFB, 0x10, 0xFF, 0xFD, 0x21, 0x72, 0xF0, 0x21, 0x6F, 0xFD, 0x4B, 0xC3, 0x65, 0x2E, 0x6D, 0x74, + 0x6C, 0x73, 0x6E, 0x6F, 0x61, 0x69, 0xFE, 0xE1, 0xFE, 0xF7, 0xD0, 0xBF, 0xFA, 0x5A, 0xFA, 0x5A, 0xFE, 0xFA, 0xFA, + 0x5A, 0xFA, 0x42, 0xFC, 0x35, 0xFF, 0xC4, 0xFF, 0xFD, 0x46, 0x2E, 0x6D, 0x74, 0x6C, 0x6E, 0x72, 0xD0, 0x9D, 0xFA, + 0x38, 0xFA, 0x38, 0xFD, 0x1C, 0xFA, 0x20, 0xFA, 0xCC, 0x41, 0x61, 0xE1, 0x84, 0x21, 0x6C, 0xFC, 0x21, 0x64, 0xFD, + 0x41, 0x6F, 0xFB, 0x7E, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x2E, 0xE3, + 0x46, 0x42, 0x2E, 0x73, 0xE3, 0x42, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x21, 0x69, 0xFB, 0x23, 0x64, 0x6D, + 0x63, 0xD7, 0xEA, 0xFD, 0xA0, 0x00, 0x81, 0x21, 0x6F, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xA1, 0xFD, + 0x42, 0x6F, 0x72, 0xD4, 0x62, 0xDC, 0x11, 0xA0, 0x11, 0x92, 0x21, 0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, + 0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x61, 0xFD, 0x44, 0x61, 0x6F, 0x74, 0x75, 0xE0, 0xA2, + 0xE9, 0x8F, 0xFF, 0xE1, 0xFF, 0xFD, 0x41, 0x6E, 0xE1, 0xC8, 0x42, 0x63, 0x72, 0xE1, 0xC4, 0xE1, 0xC4, 0x42, 0x6D, + 0x72, 0xD9, 0x69, 0xD4, 0xC3, 0x41, 0x67, 0xD9, 0xBC, 0x42, 0x61, 0x6F, 0xD0, 0x9B, 0xD0, 0x9B, 0x43, 0x61, 0x65, + 0x6F, 0xD0, 0x94, 0xD0, 0x94, 0xD0, 0x94, 0x21, 0x69, 0xF6, 0x44, 0x61, 0x69, 0x65, 0x6F, 0xD0, 0x87, 0xD0, 0x87, + 0xD0, 0x87, 0xD0, 0x87, 0x44, 0x6A, 0x67, 0x6C, 0x6D, 0xFF, 0xDF, 0xD9, 0x97, 0xFF, 0xF0, 0xFF, 0xF3, 0x44, 0xA1, + 0xA9, 0xB3, 0xAD, 0xFF, 0xC7, 0xFF, 0xCE, 0xD8, 0x5C, 0xFF, 0xF3, 0x41, 0x72, 0xDC, 0x2A, 0x21, 0x65, 0xFC, 0x41, + 0x6D, 0xE2, 0x99, 0x21, 0x75, 0xFC, 0x41, 0x69, 0xD1, 0xE0, 0x44, 0x63, 0x67, 0x6C, 0x6D, 0xFF, 0xF2, 0xD9, 0x83, + 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xD2, 0x2C, 0x21, 0xA9, 0xFC, 0x21, 0xC3, 0xFD, 0x41, 0x69, 0xD7, 0xE1, 0x21, + 0x75, 0xFC, 0x43, 0x63, 0x67, 0x71, 0xD1, 0xB0, 0xFF, 0xF6, 0xFF, 0xFD, 0x43, 0x61, 0xC3, 0x6F, 0xD1, 0xA3, 0xD9, + 0x2F, 0xD1, 0xA3, 0x41, 0xAD, 0xD1, 0x99, 0x43, 0x65, 0x69, 0xC3, 0xD1, 0x95, 0xD1, 0x95, 0xFF, 0xFC, 0x42, 0x69, + 0x61, 0xD7, 0x0B, 0xD1, 0x8E, 0x44, 0xA1, 0xAD, 0xA9, 0xB3, 0xD1, 0x84, 0xD1, 0x84, 0xD1, 0x84, 0xD1, 0x84, 0x45, + 0x61, 0xC3, 0x69, 0x65, 0x6F, 0xD1, 0x77, 0xFF, 0xF3, 0xD1, 0x77, 0xD1, 0x77, 0xD1, 0x77, 0x41, 0x6F, 0xD1, 0xE6, + 0x25, 0x6A, 0x67, 0x6C, 0x6D, 0x74, 0xC0, 0xCE, 0xD8, 0xEC, 0xFC, 0x41, 0xB3, 0xD1, 0x58, 0x21, 0xC3, 0xFC, 0x21, + 0x69, 0xFD, 0x41, 0x72, 0xFF, 0x7F, 0x41, 0xA9, 0xD1, 0x4D, 0x43, 0x63, 0x71, 0x73, 0xD1, 0x46, 0xD1, 0x46, 0xD6, + 0x27, 0x22, 0xC3, 0x69, 0xF2, 0xF6, 0x41, 0x6D, 0xD1, 0xA5, 0x21, 0xA1, 0xFC, 0x22, 0x61, 0xC3, 0xF9, 0xFD, 0x41, + 0x71, 0xD1, 0x2B, 0x21, 0x73, 0xFC, 0x41, 0x61, 0xD1, 0x35, 0x21, 0x6C, 0xFC, 0x47, 0x62, 0x6E, 0x63, 0x74, 0x67, + 0x65, 0x70, 0xFF, 0xCC, 0xD8, 0xD5, 0xFF, 0xCF, 0xFF, 0xE1, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xFD, 0x43, 0x72, 0x74, + 0x63, 0xD1, 0x07, 0xD1, 0x07, 0xD1, 0x07, 0x21, 0x61, 0xF6, 0x42, 0x62, 0x64, 0xD8, 0xB2, 0xFF, 0xFD, 0x41, 0x72, + 0xD0, 0x12, 0xA0, 0x0D, 0xA1, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x6F, 0xFD, 0x48, 0xC3, 0x61, 0x65, 0x69, + 0x6F, 0x75, 0x74, 0x70, 0xFE, 0xF9, 0xFF, 0x18, 0xFF, 0x36, 0xFF, 0x80, 0xFF, 0xC6, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, + 0xFD, 0x41, 0x72, 0xFA, 0x54, 0x21, 0x74, 0xFC, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD, 0x22, 0x65, 0xC3, 0xFA, 0xFD, + 0x4F, 0x6F, 0x73, 0x2E, 0x6D, 0x6E, 0x72, 0x64, 0x65, 0x61, 0xC3, 0x63, 0x74, 0x75, 0x78, 0x6C, 0xFC, 0x13, 0xFC, + 0x2E, 0xCE, 0xA5, 0xFC, 0x46, 0xFC, 0x66, 0xFD, 0xE6, 0xFE, 0x08, 0xFE, 0x22, 0xFE, 0x48, 0xFE, 0x5B, 0xFE, 0x7D, + 0xFE, 0x8A, 0xFE, 0x8E, 0xFF, 0xD5, 0xFF, 0xFB, 0x43, 0x2E, 0x73, 0x6D, 0xF8, 0x9B, 0xF8, 0x9E, 0xFA, 0x4F, 0xA0, + 0x03, 0x53, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0xA0, 0x03, 0x84, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, + 0xFA, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xF0, 0xF0, 0xFB, 0x22, 0x2E, 0x6C, 0xE3, 0xF9, 0x21, 0x65, 0xFB, 0x23, 0x65, + 0x61, 0x6F, 0xE1, 0xE1, 0xE1, 0x23, 0x65, 0x6F, 0x61, 0xDA, 0xDA, 0xDA, 0x21, 0x6C, 0xF9, 0x24, 0x6D, 0x74, 0x6C, + 0x65, 0xEC, 0xEC, 0xEF, 0xFD, 0x22, 0x2E, 0x6C, 0xC1, 0xED, 0x21, 0x73, 0xFB, 0x21, 0x6F, 0xFD, 0x23, 0x73, 0x6E, + 0x6F, 0xEC, 0xFD, 0xFA, 0x21, 0x6F, 0xF9, 0x43, 0x73, 0x69, 0x6D, 0xF8, 0x40, 0xF9, 0x8F, 0xFF, 0xFD, 0x21, 0xA1, + 0xF6, 0x43, 0x6F, 0x61, 0xC3, 0xF8, 0x33, 0xFF, 0x95, 0xFF, 0xFD, 0x41, 0x69, 0xF9, 0x78, 0x21, 0x74, 0xFC, 0x41, + 0x73, 0xF8, 0xEC, 0x42, 0x2E, 0x65, 0xF8, 0xE5, 0xFF, 0xFC, 0x21, 0x6C, 0xF9, 0x43, 0x69, 0x61, 0x65, 0xFF, 0xEF, + 0xFF, 0xFD, 0xF8, 0x1C, 0x41, 0x61, 0xEA, 0xAB, 0x42, 0x6F, 0x69, 0xCE, 0x5D, 0xCE, 0xBE, 0x21, 0x6D, 0xF9, 0x41, + 0x69, 0xCE, 0xB4, 0x21, 0x6D, 0xFC, 0x21, 0xA1, 0xFD, 0x22, 0x61, 0xC3, 0xF3, 0xFD, 0x44, 0x6D, 0x74, 0x6C, 0x6F, + 0xF6, 0xB8, 0xFF, 0xE3, 0xFF, 0xFB, 0xE7, 0x2D, 0xC3, 0x02, 0x91, 0x61, 0xC3, 0x65, 0xCF, 0xCC, 0xD7, 0x58, 0xCF, + 0xCC, 0x21, 0x69, 0xF4, 0x21, 0x63, 0xFD, 0xC1, 0x05, 0x81, 0x61, 0xCE, 0x3D, 0x21, 0x69, 0xFA, 0x21, 0x63, 0xFD, + 0x21, 0xAD, 0xFD, 0xA0, 0x02, 0xB1, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x23, 0x62, 0x65, 0xC3, + 0xF4, 0xFA, 0xFD, 0x21, 0x62, 0xED, 0x21, 0x6C, 0xEA, 0x21, 0x6D, 0xE7, 0x21, 0x69, 0xE7, 0x21, 0x70, 0xFD, 0x21, + 0x73, 0xFD, 0x25, 0xAD, 0xA1, 0xA9, 0xBA, 0xB3, 0xEE, 0xF1, 0xE1, 0xF4, 0xFD, 0x22, 0x74, 0x69, 0xD0, 0xD0, 0x21, + 0x6E, 0xCE, 0x21, 0x65, 0xFD, 0x22, 0x73, 0x72, 0xF5, 0xFD, 0x25, 0x69, 0xC3, 0x61, 0x65, 0x75, 0xCC, 0xE5, 0xD6, + 0xFB, 0xD9, 0x41, 0x75, 0xEA, 0x4B, 0xA0, 0x0B, 0xE3, 0x22, 0x75, 0x74, 0xFD, 0xFD, 0x22, 0x73, 0x64, 0xFB, 0xF8, + 0xA0, 0x0C, 0x63, 0x21, 0x72, 0xFD, 0xA0, 0x0C, 0x93, 0x21, 0x2E, 0xFD, 0x23, 0x6E, 0x6F, 0x6D, 0xEF, 0xF7, 0xFD, + 0x41, 0x73, 0xEA, 0x44, 0x21, 0xA9, 0xFC, 0xA0, 0x0A, 0x12, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x73, 0xFD, + 0xA0, 0x0C, 0x42, 0x21, 0x6F, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x65, 0xFD, 0x24, 0x69, 0xC3, 0x65, + 0x72, 0xD7, 0xE2, 0xEE, 0xFD, 0x21, 0x72, 0xF7, 0x42, 0x65, 0x72, 0xFF, 0xFD, 0xCE, 0x2D, 0x41, 0x72, 0xFC, 0xB4, + 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x72, 0xCD, 0xC6, 0x21, 0x65, 0xFC, 0x21, 0x69, 0xFD, + 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x4A, 0x69, 0xC3, 0x68, 0x66, 0x6D, 0x74, 0x6F, 0x61, 0x64, 0x67, 0xFF, 0x2D, + 0xFF, 0x3C, 0xFF, 0x7F, 0xFD, 0xF7, 0xFF, 0x8A, 0xFF, 0xDC, 0xE9, 0x9F, 0xE9, 0x9F, 0xFF, 0xED, 0xFF, 0xFD, 0x41, + 0x65, 0xD8, 0x86, 0x43, 0x6E, 0x2E, 0x73, 0xD8, 0x7E, 0xF7, 0x21, 0xF7, 0x24, 0x42, 0x6F, 0x61, 0xFF, 0xF6, 0xF7, + 0x1D, 0x42, 0x2E, 0x73, 0xF8, 0xC5, 0xF8, 0xC8, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x41, 0x2E, 0xEE, 0x81, 0x21, 0x73, + 0xFC, 0x21, 0x65, 0xFD, 0x44, 0x6E, 0x72, 0x2E, 0x73, 0xFF, 0xF1, 0xFF, 0xFD, 0xF7, 0x72, 0xF7, 0x75, 0x41, 0x61, + 0xDE, 0x29, 0x41, 0x72, 0xF8, 0xA1, 0x42, 0x2E, 0x73, 0xF7, 0x5D, 0xF7, 0x60, 0x4A, 0x67, 0x64, 0x73, 0x6E, 0x62, + 0x63, 0x61, 0x74, 0x65, 0x6F, 0xFE, 0x65, 0xFE, 0x84, 0xFE, 0xAB, 0xFF, 0x9A, 0xFF, 0xB9, 0xFF, 0xC7, 0xFF, 0xE4, + 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF9, 0x41, 0x69, 0xFB, 0xFC, 0x21, 0x72, 0xFC, 0x21, 0x65, 0xFD, 0x41, 0x74, 0xFD, + 0x68, 0xA0, 0x11, 0xB2, 0x42, 0x64, 0x74, 0xFF, 0xFD, 0xFC, 0x01, 0x21, 0x69, 0xF9, 0x21, 0x73, 0xFD, 0x21, 0x72, + 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x76, 0xFD, 0x21, 0x69, 0xFD, 0x23, 0x74, 0x6C, 0x6E, 0xDD, 0xE0, 0xFD, 0x5C, 0x62, + 0x2E, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, + 0x79, 0x7A, 0xC3, 0x6F, 0x61, 0x65, 0x69, 0x75, 0xCD, 0x5C, 0xDB, 0x8C, 0xDD, 0xF1, 0xE3, 0xC6, 0xE4, 0x43, 0xE5, + 0xC4, 0xE7, 0x42, 0xE7, 0x94, 0xE7, 0xDD, 0xE8, 0x9B, 0xE9, 0xD6, 0xEA, 0x67, 0xED, 0x21, 0xED, 0x83, 0xEE, 0x2C, + 0xF0, 0x08, 0xF2, 0x7F, 0xF2, 0xDD, 0xF3, 0x29, 0xF3, 0x78, 0xF3, 0xC3, 0xF4, 0x0C, 0xF6, 0x24, 0xF7, 0x4C, 0xF9, + 0x4F, 0xFD, 0x7C, 0xFF, 0xB0, 0xFF, 0xF9, +}; + +constexpr SerializedHyphenationPatterns es_patterns = { + es_trie_data, + sizeof(es_trie_data), +}; diff --git a/test/hyphenation_eval/HyphenationEvaluationTest.cpp b/test/hyphenation_eval/HyphenationEvaluationTest.cpp index 90d17101..e01b647f 100644 --- a/test/hyphenation_eval/HyphenationEvaluationTest.cpp +++ b/test/hyphenation_eval/HyphenationEvaluationTest.cpp @@ -42,6 +42,7 @@ const std::vector kSupportedLanguages = { {"french", "test/hyphenation_eval/resources/french_hyphenation_tests.txt", "fr"}, {"german", "test/hyphenation_eval/resources/german_hyphenation_tests.txt", "de"}, {"russian", "test/hyphenation_eval/resources/russian_hyphenation_tests.txt", "ru"}, + {"spanish", "test/hyphenation_eval/resources/spanish_hyphenation_tests.txt", "es"}, }; std::vector expectedPositionsFromAnnotatedWord(const std::string& annotated) { diff --git a/test/hyphenation_eval/resources/spanish_hyphenation_tests.txt b/test/hyphenation_eval/resources/spanish_hyphenation_tests.txt new file mode 100644 index 00000000..f50fb062 --- /dev/null +++ b/test/hyphenation_eval/resources/spanish_hyphenation_tests.txt @@ -0,0 +1,5012 @@ +# Hyphenation Test Data +# Source: quijote.epub +# Language: es_ES +# Min prefix: 2 +# Min suffix: 2 +# Total words: 5000 +# Format: word | hyphenated_form | frequency_in_source +# +# Hyphenation points are marked with '=' +# Example: Silbentrennung -> Sil=ben=tren=nung +# + +Quijote|Qui=jo=te|2264 +Sancho|San=cho|2171 +porque|por=que|1333 +respondió|res=pon=dió|1053 +merced|mer=ced|900 +vuestra|vues=tra|813 +cuando|cuan=do|712 +caballero|ca=ba=lle=ro|584 +aunque|aun=que|525 +señora|se=ño=ra|504 +estaba|es=ta=ba|462 +verdad|ver=dad|418 +alguna|al=gu=na|384 +manera|ma=ne=ra|329 +aquella|aque=lla|328 +tiempo|tiem=po|327 +puesto|pues=to|305 +caballeros|ca=ba=lle=ros|292 +Dulcinea|Dul=ci=nea|284 +tierra|tie=rra|273 +historia|his=to=ria|259 +hombre|hom=bre|258 +quiero|quie=ro|253 +habían|ha=bían|251 +camino|ca=mino|246 +escudero|es=cu=de=ro|246 +parece|pa=re=ce|239 +muchas|mu=chas|236 +cuenta|cuen=ta|222 +cuanto|cuan=to|219 +cabeza|ca=be=za|216 +replicó|re=pli=có|207 +Rocinante|Ro=ci=nan=te|204 +nuestro|nues=tro|202 +parecer|pa=re=cer|202 +razones|ra=zo=nes|202 +también|tam=bién|199 +diciendo|di=cien=do|198 +grande|gran=de|198 +andante|an=dan=te|197 +muchos|mu=chos|197 +caballo|ca=ba=llo|196 +duquesa|du=que=sa|190 +después|des=pués|186 +primero|pri=me=ro|186 +nombre|nom=bre|181 +Mancha|Man=cha|178 +estaban|es=ta=ban|174 +barbero|bar=be=ro|171 +gobernador|go=ber=na=dor|171 +adelante|ade=lan=te|170 +Toboso|To=bo=so|163 +andantes|an=dan=tes|162 +aventura|aven=tu=ra|160 +voluntad|vo=lun=tad|160 +vuestro|vues=tro|158 +aquellos|aque=llos|156 +ventura|ven=tu=ra|155 +cuatro|cua=tro|153 +rostro|ros=tro|153 +entender|en=ten=der|151 +Camila|Ca=mi=la|148 +doncella|don=ce=lla|148 +libros|li=bros|148 +menester|me=nes=ter|147 +palabra|pa=la=bra|147 +tienen|tie=nen|147 +fueron|fue=ron|146 +siempre|siem=pre|145 +señores|se=ño=res|144 +caballería|ca=ba=lle=ría|143 +castillo|cas=ti=llo|143 +cuales|cua=les|143 +alguno|al=guno|142 +cuerpo|cuer=po|142 +hermosa|her=mo=sa|142 +Lotario|Lo=ta=rio|142 +corazón|co=ra=zón|141 +quiere|quie=re|141 +suerte|suer=te|141 +nuestra|nues=tra|140 +Anselmo|An=sel=mo|138 +muerte|muer=te|138 +ninguna|nin=gu=na|137 +persona|per=so=na|136 +Fernando|Fer=nan=do|135 +entonces|en=ton=ces|133 +comenzó|co=men=zó|132 +fuerza|fuer=za|128 +memoria|me=mo=ria|128 +Capítulo|Ca=pí=tu=lo|126 +preguntó|pre=gun=tó|126 +grandes|gran=des|125 +palabras|pa=la=bras|125 +delante|de=lan=te|124 +contra|contra|123 +hermosura|her=mo=su=ra|123 +posible|po=si=ble|123 +cierto|cier=to|121 +contento|con=ten=to|119 +ventero|ven=te=ro|118 +ínsula|ín=su=la|118 +algunos|al=gu=nos|117 +gobierno|go=bierno|117 +Gutenberg|Gu=ten=berg|117 +Project|Pro=ject|117 +viendo|vien=do|117 +Dorotea|Do=ro=tea|112 +bachiller|ba=chi=ller|111 +tantas|tan=tas|111 +habéis|ha=béis|110 +espada|es=pa=da|109 +volvió|vol=vió|109 +nosotros|no=so=tros|108 +intención|in=ten=ción|107 +muerto|muer=to|107 +volver|vol=ver|107 +buscar|bus=car|105 +aquello|aque=llo|104 +ocasión|oca=sión|104 +pueblo|pue=blo|104 +buenos|bue=nos|102 +hombres|hom=bres|102 +lengua|len=gua|102 +pensamientos|pen=sa=mien=tos|102 +siendo|sien=do|102 +Cardenio|Car=de=nio|101 +partes|par=tes|101 +ciudad|ciu=dad|100 +estado|es=ta=do|100 +llevar|lle=var|100 +pareció|pa=re=ció|100 +Luscinda|Lus=cin=da|99 +lágrimas|lá=gri=mas|99 +tantos|tan=tos|99 +aventuras|aven=tu=ras|98 +dellos|de=llos|98 +hermano|her=ma=no|98 +adonde|adon=de|97 +hablar|ha=blar|97 +aquellas|aque=llas|96 +pienso|pien=so|96 +quería|que=ría|96 +buenas|bue=nas|95 +famoso|fa=mo=so|95 +ninguno|nin=guno|94 +parecía|pa=re=cía|94 +aposento|apo=sen=to|93 +primera|pri=me=ra|93 +Caballero|Ca=ba=lle=ro|92 +brazos|bra=zos|91 +conmigo|con=mi=go|90 +entrar|en=trar|90 +podría|po=dría|90 +Teresa|Te=re=sa|90 +cuento|cuen=to|89 +pudiera|pu=die=ra|89 +pensar|pen=sar|88 +algunas|al=gu=nas|86 +dieron|die=ron|86 +trabajo|tra=ba=jo|86 +habiendo|ha=bien=do|85 +mañana|ma=ña=na|85 +puerta|puer=ta|85 +batalla|ba=ta=lla|84 +diablo|dia=blo|84 +pueden|pue=den|84 +juicio|jui=cio|83 +nuevas|nue=vas|83 +cuantos|cuan=tos|82 +entendimiento|en=ten=di=mien=to|82 +libertad|li=ber=tad|82 +sucesos|su=ce=sos|82 +vuestras|vues=tras|82 +apenas|ape=nas|80 +caballerías|ca=ba=lle=rías|80 +desgracia|des=gra=cia|80 +llaman|lla=man|80 +natural|na=tu=ral|80 +peligro|pe=li=gro|80 +quisiera|qui=sie=ra|80 +marido|ma=ri=do|78 +pensamiento|pen=sa=mien=to|78 +Zoraida|Zo=rai=da|78 +locura|lo=cu=ra|77 +Sansón|San=són|77 +debajo|de=ba=jo|76 +discreto|dis=cre=to|76 +mercedes|mer=ce=des|76 +España|Es=pa=ña|75 +Finalmente|Fi=nal=men=te|75 +hubiera|hu=bie=ra|75 +nuestros|nues=tros|75 +remedio|re=me=dio|75 +silencio|si=len=cio|75 +suceso|su=ce=so|75 +valeroso|va=le=ro=so|75 +criado|cria=do|74 +jumento|ju=men=to|74 +licencia|li=cen=cia|74 +presto|pres=to|74 +semejantes|se=me=jan=tes|74 +sucedió|su=ce=dió|74 +deseos|de=seos|73 +priesa|prie=sa|73 +condición|con=di=ción|72 +cristiano|cris=tia=no|72 +espacio|es=pa=cio|72 +hidalgo|hi=dal=go|72 +doncellas|don=ce=llas|71 +llegar|lle=gar|71 +estando|es=tan=do|70 +hicieron|hi=cie=ron|70 +labrador|la=bra=dor|70 +padres|pa=dres|70 +cristianos|cris=tia=nos|69 +quisiere|qui=sie=re|69 +venían|ve=nían|69 +vestido|ves=ti=do|69 +noticia|no=ti=cia|68 +pensaba|pen=sa=ba|68 +discurso|dis=cur=so|67 +hazañas|ha=za=ñas|67 +mayores|ma=yo=res|67 +pasado|pa=sa=do|67 +amigos|ami=gos|66 +espaldas|es=pal=das|66 +gracias|gra=cias|66 +tenido|te=ni=do|66 +tenían|te=nían|66 +Antonio|An=to=nio|65 +barbas|bar=bas|65 +escuderos|es=cu=de=ros|65 +esperar|es=pe=rar|65 +figura|fi=gu=ra|65 +grandeza|gran=de=za|65 +muestras|mues=tras|65 +sangre|san=gre|65 +virtud|vir=tud|65 +Altisidora|Alti=si=do=ra|64 +criados|cria=dos|64 +encantadores|en=can=ta=do=res|64 +llamaba|lla=ma=ba|64 +principio|prin=ci=pio|64 +querer|que=rer|64 +enamorado|ena=mo=ra=do|63 +enemigo|ene=mi=go|63 +Carrasco|Ca=rras=co|62 +dormir|dor=mir|62 +fortuna|for=tu=na|62 +Porque|Por=que|62 +valiente|va=lien=te|62 +azotes|azo=tes|61 +compañía|com=pa=ñía|61 +consigo|con=si=go|61 +querría|que=rría|61 +resolución|re=so=lu=ción|61 +haciendo|ha=cien=do|60 +ningún|nin=gún|60 +provecho|pro=ve=cho|60 +renegado|re=ne=ga=do|60 +sobrina|so=bri=na|60 +tuviese|tu=vie=se|60 +hambre|ham=bre|59 +hubiese|hu=bie=se|59 +triste|tris=te|59 +encantado|en=can=ta=do|58 +finalmente|fi=nal=men=te|58 +hallar|ha=llar|58 +necesidad|ne=ce=si=dad|58 +pasaba|pa=sa=ba|58 +pastor|pas=tor|58 +presente|pre=sen=te|58 +principal|prin=ci=pal|58 +suelen|sue=len|58 +vieron|vie=ron|58 +dijese|di=je=se|57 +dineros|di=ne=ros|57 +esposo|es=po=so|57 +estuvo|es=tu=vo|57 +hallado|ha=lla=do|57 +letras|le=tras|57 +licenciado|li=cen=cia=do|57 +destos|des=tos|56 +discreción|dis=cre=ción|56 +gigante|gi=gan=te|56 +plática|plá=ti=ca|56 +quitar|qui=tar|56 +tienes|tie=nes|56 +ejercicio|ejer=ci=cio|55 +encima|en=ci=ma|55 +llegaron|lle=ga=ron|55 +princesa|prin=ce=sa|55 +reales|rea=les|55 +veinte|vein=te|55 +dueñas|due=ñas|54 +esperando|es=pe=ran=do|54 +llamar|lla=mar|54 +mirando|mi=ran=do|54 +quedaron|que=da=ron|54 +vencido|ven=ci=do|54 +Basilio|Ba=si=lio|53 +cuidado|cui=da=do|53 +dejando|de=jan=do|53 +hacienda|ha=cien=da|53 +Montesinos|Mon=te=si=nos|53 +mujeres|mu=je=res|53 +oficio|ofi=cio|53 +servir|ser=vir|53 +verdadera|ver=da=de=ra|53 +versos|ver=sos|53 +acabar|aca=bar|52 +acuerdo|acuer=do|52 +asimismo|asi=mis=mo|52 +contar|con=tar|52 +dentro|den=tro|52 +diciéndole|di=cién=do=le|52 +Miguel|Mi=guel|52 +prosiguió|pro=si=guió|52 +quieres|quie=res|52 +responder|res=pon=der|52 +sucedido|su=ce=di=do|52 +verdadero|ver=da=de=ro|52 +destas|des=tas|51 +esposa|es=po=sa|51 +historias|his=to=rias|51 +negocio|ne=go=cio|51 +presencia|pre=sen=cia|51 +servido|ser=vi=do|51 +efecto|efec=to|50 +escrito|es=cri=to|50 +fuerzas|fuer=zas|50 +presentes|pre=sen=tes|50 +puesta|pues=ta|50 +bondad|bon=dad|49 +cabrero|ca=bre=ro|49 +dejado|de=ja=do|49 +disparates|dis=pa=ra=tes|49 +enemigos|ene=mi=gos|49 +huésped|huésped|49 +personas|per=so=nas|49 +respuesta|res=pues=ta|49 +señoras|se=ño=ras|49 +Cervantes|Cer=van=tes|48 +conocido|co=no=ci=do|48 +Figura|Fi=gu=ra|48 +género|gé=ne=ro|48 +nuestras|nues=tras|48 +pesadumbre|pe=sa=dum=bre|48 +Triste|Tris=te|48 +término|tér=mino|48 +vestidos|ves=ti=dos|48 +consejo|con=se=jo|47 +cólera|có=le=ra|47 +ingenio|in=ge=nio|47 +justicia|jus=ti=cia|47 +llegando|lle=gan=do|47 +muestra|mues=tra|47 +opinión|opi=nión|47 +perder|per=der|47 +propósito|pro=pó=si=to|47 +pusieron|pu=sie=ron|47 +vuelto|vuel=to|47 +albarda|al=bar=da|46 +amores|amo=res|46 +andaba|an=da=ba|46 +cabellos|ca=be=llos|46 +cautivo|cau=ti=vo|46 +conocer|co=no=cer|46 +cortesía|cor=tesía|46 +Cuando|Cuan=do|46 +entiendo|en=tien=do|46 +especialmente|es=pe=cial=men=te|46 +estremo|es=tre=mo|46 +fueran|fue=ran|46 +imaginación|ima=gi=na=ción|46 +venido|ve=ni=do|46 +viento|vien=to|46 +escudos|es=cu=dos|45 +esperanza|es=pe=ran=za|45 +instante|ins=tan=te|45 +pudiese|pu=die=se|45 +quieren|quie=ren|45 +Rodríguez|Ro=drí=guez|45 +todavía|to=da=vía|45 +tuvieron|tu=vie=ron|45 +Amadís|Ama=dís|44 +arriba|arri=ba|44 +belleza|be=lle=za|44 +blanco|blan=co|44 +emperador|em=pe=ra=dor|44 +fuesen|fuesen|44 +general|ge=ne=ral|44 +gloria|glo=ria|44 +guerra|gue=rra|44 +honestidad|ho=nes=ti=dad|44 +Leonela|Leo=ne=la|44 +pasaron|pa=sa=ron|44 +árboles|ár=bo=les|44 +Bosque|Bos=que|43 +buscando|bus=can=do|43 +contado|con=ta=do|43 +hechos|he=chos|43 +llegado|lle=ga=do|43 +llevaba|lle=va=ba|43 +primer|pri=mer|43 +quedar|que=dar|43 +Saavedra|Saave=dra|43 +fuerte|fuer=te|42 +imposible|im=po=si=ble|42 +linaje|li=na=je|42 +llamado|lla=ma=do|42 +mejores|me=jo=res|42 +tiempos|tiem=pos|42 +venganza|ven=gan=za|42 +verdaderamente|ver=da=de=ra=men=te|42 +vuestros|vues=tros|42 +agravio|agra=vio|41 +amistad|amis=tad|41 +armado|ar=ma=do|41 +duques|du=ques|41 +escribir|es=cri=bir|41 +jardín|jar=dín|41 +oyeron|oye=ron|41 +segunda|se=gun=da|41 +seguro|se=gu=ro|41 +admiración|ad=mi=ra=ción|40 +alcanzar|al=can=zar|40 +alegre|ale=gre|40 +canónigo|ca=nó=ni=go|40 +contrario|con=tra=rio|40 +costumbre|cos=tum=bre|40 +desdichado|des=di=cha=do|40 +hiciese|hi=cie=se|40 +pastores|pas=to=res|40 +Quiteria|Qui=te=ria|40 +siglos|si=glos|40 +conciencia|con=cien=cia|39 +dientes|dien=tes|39 +discreta|dis=cre=ta|39 +leguas|le=guas|39 +mentira|men=ti=ra|39 +podían|po=dían|39 +rodillas|ro=di=llas|39 +señales|se=ña=les|39 +vienen|vie=nen|39 +Vuestra|Vues=tra|39 +caminos|ca=mi=nos|38 +conozco|co=noz=co|38 +diligencia|di=li=gen=cia|38 +dinero|di=ne=ro|38 +donaire|do=nai=re|38 +hallaron|ha=lla=ron|38 +mayordomo|ma=yor=do=mo|38 +perlas|per=las|38 +salido|sali=do|38 +capitán|ca=pi=tán|37 +correr|co=rrer|37 +entrambos|en=tram=bos|37 +galeras|ga=le=ras|37 +gentil|gen=til|37 +hacían|ha=cían|37 +Hamete|Ha=me=te|37 +juntos|jun=tos|37 +levantó|le=van=tó|37 +molido|mo=li=do|37 +mostraba|mos=tra=ba|37 +paciencia|pa=cien=cia|37 +tuviera|tu=vie=ra|37 +Camacho|Ca=ma=cho|36 +experiencia|ex=pe=rien=cia|36 +gentes|gen=tes|36 +grandísimo|gran=dí=si=mo|36 +labradora|la=bra=do=ra|36 +obligado|obli=ga=do|36 +trabajos|tra=ba=jos|36 +título|tí=tu=lo|36 +viniese|vi=nie=se|36 +volvieron|vol=vie=ron|36 +celada|ce=la=da|35 +creyendo|cre=yen=do|35 +cristiana|cris=tia=na|35 +cumplir|cum=plir|35 +decían|de=cían|35 +Dolorida|Do=lo=ri=da|35 +encantador|en=can=ta=dor|35 +engaño|en=ga=ño|35 +entrañas|en=tra=ñas|35 +guardar|guar=dar|35 +imagino|ima=gino|35 +industria|in=dus=tria|35 +llevado|lle=va=do|35 +locuras|lo=cu=ras|35 +pasados|pa=sa=dos|35 +pedazos|pe=da=zos|35 +presteza|pres=te=za|35 +profesión|pro=fe=sión|35 +prosigue|pro=si=gue|35 +quedaba|que=da=ba|35 +querido|que=ri=do|35 +Ricote|Ri=co=te|35 +También|Tam=bién|35 +tenemos|te=ne=mos|35 +asimesmo|asi=mes=mo|34 +ausencia|au=sen=cia|34 +cualquiera|cual=quie=ra|34 +dellas|de=llas|34 +facilidad|fa=ci=li=dad|34 +hacerse|ha=cer=se|34 +llevaban|lle=va=ban|34 +oyendo|oyen=do|34 +parecen|pa=re=cen|34 +soldado|sol=da=do|34 +sombra|som=bra|34 +Viendo|Vien=do|34 +alforjas|al=for=jas|33 +andado|an=da=do|33 +blanca|blan=ca|33 +cualquier|cual=quier|33 +cuello|cue=llo|33 +decirse|de=cir=se|33 +dejaba|de=ja=ba|33 +encantamento|en=can=ta=men=to|33 +estamos|es=ta=mos|33 +naturaleza|na=tu=ra=le=za|33 +número|nú=me=ro|33 +ponerse|po=ner=se|33 +recibió|re=ci=bió|33 +refranes|re=fra=nes|33 +seguir|se=guir|33 +segundo|se=gun=do|33 +sepultura|se=pul=tu=ra|33 +temeroso|te=me=ro=so|33 +burlas|bur=las|32 +condesa|con=de=sa|32 +deseaba|de=sea=ba|32 +esperaba|es=pe=ra=ba|32 +hablando|ha=blan=do|32 +mancebo|man=ce=bo|32 +mandado|man=da=do|32 +merece|me=re=ce|32 +pláticas|plá=ti=cas|32 +principales|prin=ci=pa=les|32 +puedes|pue=des|32 +queréis|que=réis|32 +recebir|re=ce=bir|32 +riendas|rien=das|32 +siquiera|si=quie=ra|32 +adónde|adón=de|31 +caminar|ca=mi=nar|31 +crédito|cré=di=to|31 +dejaron|de=ja=ron|31 +dejase|de=ja=se|31 +derecho|de=re=cho|31 +famosos|fa=mo=sos|31 +Francia|Fran=cia|31 +ganado|ga=na=do|31 +gracia|gra=cia|31 +haberle|ha=ber=le|31 +Maritornes|Ma=ri=tor=nes|31 +momento|mo=men=to|31 +particular|par=ti=cu=lar|31 +príncipes|prín=ci=pes|31 +quisiese|qui=sie=se|31 +respeto|res=pe=to|31 +salieron|salie=ron|31 +sosiego|so=sie=go|31 +suspiros|sus=pi=ros|31 +valentía|va=len=tía|31 +ventana|ven=ta=na|31 +volviéndose|vol=vién=do=se|31 +vuelta|vuel=ta|31 +acometer|aco=me=ter|30 +acudir|acu=dir|30 +conoció|co=no=ció|30 +cuentan|cuen=tan|30 +descubierto|des=cu=bier=to|30 +determinado|de=ter=mi=na=do|30 +diferentes|di=fe=ren=tes|30 +doctor|doc=tor|30 +encantada|en=can=ta=da|30 +estraña|es=tra=ña|30 +forzoso|for=zo=so|30 +gigantes|gi=gan=tes|30 +guarda|guar=da|30 +hacerle|ha=cer=le|30 +hábito|há=bi=to|30 +llegaba|lle=ga=ba|30 +llorar|llo=rar|30 +Lorenzo|Lo=ren=zo|30 +prometido|pro=me=ti=do|30 +salida|sali=da|30 +servicio|ser=vi=cio|30 +sobresalto|so=bre=sal=to|30 +soldados|sol=da=dos|30 +Trifaldi|Tri=fal=di|30 +ventera|ven=te=ra|30 +verdaderas|ver=da=de=ras|30 +vizcaíno|viz=caí=no|30 +volviese|vol=vie=se|30 +artificio|ar=ti=fi=cio|29 +decirle|de=cir=le|29 +determinación|de=ter=mi=na=ción|29 +entraron|en=tra=ron|29 +Grisóstomo|Gri=sós=to=mo|29 +hombros|hom=bros|29 +levantar|le=van=tar|29 +mesmos|mes=mos|29 +parecido|pa=re=ci=do|29 +pendencia|pen=den=cia|29 +poetas|poe=tas|29 +puertas|puer=tas|29 +tomado|to=ma=do|29 +términos|tér=mi=nos|29 +villano|vi=lla=no|29 +volviendo|vol=vien=do|29 +acabado|aca=ba=do|28 +agradable|agra=da=ble|28 +calidad|ca=li=dad|28 +compadre|com=pa=dre|28 +confuso|con=fu=so|28 +derecha|de=re=cha|28 +Espejos|Es=pe=jos|28 +famosa|fa=mo=sa|28 +gobernar|go=ber=nar|28 +hablado|ha=bla=do|28 +infanta|in=fan=ta|28 +infierno|in=fierno|28 +Marcela|Mar=ce=la|28 +mostró|mos=tró|28 +muchacho|mu=cha=cho|28 +nacido|na=ci=do|28 +narices|na=ri=ces|28 +nombres|nom=bres|28 +nuevos|nue=vos|28 +Oyendo|Oyen=do|28 +pareciéndole|pa=re=cién=do=le|28 +partida|par=ti=da|28 +pequeño|pe=que=ño|28 +promesas|pro=me=sas|28 +quiera|quie=ra|28 +quisieres|qui=sie=res|28 +retablo|re=ta=blo|28 +rostros|ros=tros|28 +secreto|se=cre=to|28 +solamente|so=la=men=te|28 +suspenso|sus=pen=so|28 +Tosilos|To=si=los|28 +vosotros|vo=so=tros|28 +ínsulas|ín=su=las|28 +último|úl=ti=mo|28 +acerca|acer=ca|27 +aliento|alien=to|27 +andaban|an=da=ban|27 +capítulo|ca=pí=tu=lo|27 +carnes|car=nes|27 +cartas|car=tas|27 +comenzaron|co=men=za=ron|27 +conviene|con=vie=ne|27 +cuerdo|cuer=do|27 +despecho|des=pe=cho|27 +determinó|de=ter=mi=nó|27 +electronic|elec=tro=nic|27 +empresa|em=pre=sa|27 +escudo|es=cu=do|27 +esperanzas|es=pe=ran=zas|27 +estuviese|es=tu=vie=se|27 +estómago|es=tó=ma=go|27 +gracioso|gra=cio=so|27 +hallaba|ha=lla=ba|27 +hiciera|hi=cie=ra|27 +imaginar|ima=gi=nar|27 +lugares|lu=ga=res|27 +ofreció|ofre=ció|27 +pasada|pa=sa=da|27 +reinos|rei=nos|27 +respondía|res=pon=día|27 +semejante|se=me=jan=te|27 +simple|sim=ple|27 +tampoco|tam=po=co|27 +tratar|tra=tar|27 +traído|traí=do|27 +treinta|trein=ta|27 +admirados|ad=mi=ra=dos|26 +atrevido|atre=vi=do|26 +comodidad|co=mo=di=dad|26 +cuantas|cuan=tas|26 +desnudo|des=nu=do|26 +diablos|dia=blos|26 +estraño|es=tra=ño|26 +estudiante|es=tu=dian=te|26 +hacerme|ha=cer=me|26 +hermoso|her=mo=so|26 +honesta|ho=nes=ta|26 +impertinente|im=per=ti=nen=te|26 +lacayo|la=ca=yo|26 +ladrón|la=drón|26 +llevan|lle=van|26 +malicia|ma=li=cia|26 +Merlín|Mer=lín|26 +médico|mé=di=co|26 +penitencia|pe=ni=ten=cia|26 +pensativo|pen=sa=ti=vo|26 +pequeña|pe=que=ña|26 +piernas|pier=nas|26 +puestos|pues=tos|26 +quedan|que=dan|26 +suplico|su=pli=co|26 +valientes|va=lien=tes|26 +viéndose|vién=do=se|26 +bosque|bos=que|25 +caballos|ca=ba=llos|25 +castellano|cas=te=llano|25 +cierta|cier=ta|25 +conforme|con=for=me|25 +consejos|con=se=jos|25 +desventura|des=ven=tu=ra|25 +díjole|dí=jo=le|25 +entrada|en=tra=da|25 +entrado|en=tra=do|25 +Gregorio|Gre=go=rio|25 +Iglesia|Igle=sia|25 +ingenioso|in=ge=nio=so|25 +juramento|ju=ra=men=to|25 +limpio|lim=pio|25 +maestresala|ma=es=tre=sa=la|25 +Majestad|Ma=jes=tad|25 +milagro|mi=la=gro|25 +mostrar|mos=trar|25 +necesario|ne=ce=sa=rio|25 +pecado|pe=ca=do|25 +piedra|pie=dra|25 +pobres|po=bres|25 +quitado|qui=ta=do|25 +recebido|re=ce=bi=do|25 +sentido|sen=ti=do|25 +tendido|ten=di=do|25 +tendré|ten=dré|25 +tercera|ter=ce=ra|25 +turcos|tur=cos|25 +vuelva|vuel=va|25 +vuelve|vuel=ve|25 +acudió|acu=dió|24 +admirado|ad=mi=ra=do|24 +alabanzas|ala=ban=zas|24 +alcanza|al=can=za|24 +aparte|apar=te|24 +Apenas|Ape=nas|24 +apriesa|aprie=sa|24 +aquélla|aqué=lla|24 +atención|aten=ción|24 +bastante|bas=tan=te|24 +breves|bre=ves|24 +cielos|cie=los|24 +compañeros|com=pa=ñe=ros|24 +cuadrilleros|cua=dri=lle=ros|24 +desencanto|des=en=can=to|24 +desgracias|des=gra=cias|24 +dígame|dí=ga=me|24 +echado|echa=do|24 +encantados|en=can=ta=dos|24 +espero|es=pe=ro|24 +estancia|es=tan=cia|24 +felice|fe=li=ce|24 +Foundation|Foun=da=tion|24 +gobernadores|go=ber=na=do=res|24 +honrada|hon=ra=da|24 +honrado|hon=ra=do|24 +importancia|im=por=tan=cia|24 +infinitos|in=fi=ni=tos|24 +invención|in=ven=ción|24 +ligereza|li=ge=re=za|24 +llegándose|lle=gán=do=se|24 +mentecato|men=te=ca=to|24 +ordenó|or=de=nó|24 +patria|pa=tria|24 +pechos|pe=chos|24 +perdido|per=di=do|24 +ponerme|po=ner=me|24 +propio|pro=pio|24 +satisfecho|sa=tis=fe=cho|24 +sentado|sen=ta=do|24 +talante|ta=lan=te|24 +tenéis|te=néis|24 +tomando|to=man=do|24 +traigo|trai=go|24 +traían|traían|24 +trecho|tre=cho|24 +vecino|ve=cino|24 +acertado|acer=ta=do|23 +además|ade=más|23 +arremetió|arre=me=tió|23 +blancas|blan=cas|23 +cantidad|canti=dad|23 +conocimiento|co=no=ci=mien=to|23 +convenía|con=ve=nía|23 +corral|co=rral|23 +dijeron|di=je=ron|23 +discretos|dis=cre=tos|23 +entendió|en=ten=dió|23 +golpes|gol=pes|23 +habido|ha=bi=do|23 +importa|im=por=ta|23 +intento|in=ten=to|23 +juntamente|jun=ta=men=te|23 +liberal|li=be=ral|23 +parecían|pa=re=cían|23 +piensa|pien=sa|23 +pobreza|po=bre=za|23 +preguntar|pre=gun=tar|23 +romance|ro=man=ce|23 +saliese|salie=se|23 +señoría|se=ño=ría|23 +teniendo|te=nien=do|23 +venida|ve=ni=da|23 +vergüenza|ver=güen=za|23 +visorrey|vi=so=rrey|23 +viéndole|vién=do=le|23 +volvía|vol=vía|23 +alegría|ale=g=ría|22 +añadió|aña=dió|22 +cabras|ca=bras|22 +corona|co=ro=na|22 +desdichada|des=di=cha=da|22 +desenvoltura|des=en=vol=tu=ra|22 +diversas|di=ver=sas|22 +ejecución|eje=cu=ción|22 +ejemplo|ejem=plo|22 +escuchando|es=cu=chan=do|22 +Estando|Es=tan=do|22 +estáis|es=táis|22 +Excelencia|Ex=ce=len=cia|22 +faltar|fal=tar|22 +haberse|ha=ber=se|22 +humilde|hu=mil=de|22 +Hízolo|Hí=zo=lo|22 +lenguas|len=guas|22 +mostrado|mos=tra=do|22 +muerta|muer=ta|22 +ordinario|or=di=na=rio|22 +papeles|pa=pe=les|22 +pecador|pe=ca=dor|22 +pintado|pin=ta=do|22 +podéis|po=déis|22 +ponerle|po=ner=le|22 +pusiese|pu=sie=se|22 +quince|quin=ce|22 +refrán|re=frán|22 +riquezas|ri=que=zas|22 +Roldán|Rol=dán|22 +sacado|saca=do|22 +sintió|sin=tió|22 +vencimiento|ven=ci=mien=to|22 +vestida|ves=ti=da|22 +vinieron|vi=nie=ron|22 +yerbas|yer=bas|22 +atento|aten=to|21 +atrevimiento|atre=vi=mien=to|21 +bellaco|be=lla=co|21 +Blanca|Blan=ca|21 +calles|ca=lles|21 +confusión|con=fu=sión|21 +debían|de=bían|21 +defensa|de=fen=sa|21 +descuido|des=cui=do|21 +diferencia|di=fe=ren=cia|21 +dijera|di=je=ra|21 +discretas|dis=cre=tas|21 +dándole|dán=do=le|21 +entiende|en=tien=de|21 +escribió|es=cri=bió|21 +escritos|es=cri=tos|21 +estilo|es=ti=lo|21 +figuras|fi=gu=ras|21 +galeotes|ga=leo=tes|21 +Hermandad|Her=man=dad|21 +justas|jus=tas|21 +labios|la=bios|21 +leones|leo=nes|21 +llegue|lle=gue|21 +mandar|man=dar|21 +misericordia|mi=se=ri=cor=dia|21 +máquina|má=qui=na|21 +ofrecimientos|ofre=ci=mien=tos|21 +pagado|pa=ga=do|21 +pastora|pas=to=ra|21 +perjuicio|per=jui=cio|21 +prudente|pru=den=te|21 +prueba|prue=ba|21 +puestas|pues=tas|21 +Sanchica|San=chi=ca|21 +sentencias|sen=ten=cias|21 +sentimiento|sen=ti=mien=to|21 +Sierra|Sie=rra|21 +soledad|so=le=dad|21 +suspensos|sus=pen=sos|21 +temerosa|te=me=ro=sa|21 +temiendo|te=mien=do|21 +traidor|trai=dor|21 +vendrá|ven=drá|21 +Zaragoza|Za=ra=go=za|21 +arriero|arrie=ro|20 +arrojó|arro=jó|20 +batallas|ba=ta=llas|20 +brevedad|bre=ve=dad|20 +bronce|bron=ce|20 +caballeriza|ca=ba=lle=ri=za|20 +campos|cam=pos|20 +ciencia|cien=cia|20 +comedia|co=me=dia|20 +comido|co=mi=do|20 +contigo|con=ti=go|20 +costillas|cos=ti=llas|20 +cubierto|cu=bier=to|20 +descubrió|des=cu=brió|20 +detrás|de=trás|20 +diesen|die=sen|20 +enamorados|ena=mo=ra=dos|20 +encierra|en=cie=rra|20 +entendido|en=ten=di=do|20 +espanto|es=pan=to|20 +estuviera|es=tu=vie=ra|20 +faltaba|fal=ta=ba|20 +gallardo|ga=llar=do|20 +hierro|hie=rro|20 +ignorante|ig=no=ran=te|20 +lienzo|lien=zo|20 +lástima|lás=ti=ma|20 +maravilla|ma=ra=vi=lla|20 +maravillas|ma=ra=vi=llas|20 +miraba|mi=ra=ba|20 +miraban|mi=ra=ban|20 +montaña|mon=ta=ña|20 +movido|mo=vi=do|20 +música|mú=si=ca|20 +poderoso|po=de=ro=so|20 +precio|pre=cio|20 +pregunta|pre=gun=ta|20 +prudencia|pru=den=cia|20 +quedado|que=da=do|20 +recato|re=ca=to|20 +sabían|sa=bían|20 +segura|se=gu=ra|20 +suceder|su=ce=der|20 +Sucedió|Su=ce=dió|20 +sujeto|su=je=to|20 +trecientos|tre=cien=tos|20 +vencedor|ven=ce=dor|20 +verdaderos|ver=da=de=ros|20 +viniere|vi=nie=re|20 +Álvaro|Ál=va=ro|20 +acciones|ac=cio=nes|19 +adarga|adar=ga|19 +agravios|agra=vios|19 +Andrés|An=drés|19 +autores|au=to=res|19 +bendición|ben=di=ción|19 +Benengeli|Be=nen=ge=li|19 +bestias|bes=tias|19 +blanda|blan=da|19 +camisa|ca=mi=sa|19 +carrera|ca=rre=ra|19 +carreta|ca=rre=ta|19 +casado|ca=sa=do|19 +cautivos|cau=ti=vos|19 +ceremonias|ce=re=mo=nias|19 +circunstantes|cir=cuns=tan=tes|19 +Claudia|Clau=dia|19 +Clavileño|Cla=vi=le=ño|19 +comida|co=mi=da|19 +compañero|com=pa=ñe=ro|19 +copyright|co=p=y=ri=ght|19 +Cuanto|Cuan=to|19 +cueros|cue=ros|19 +defender|de=fen=der|19 +demonio|de=mo=nio|19 +desmayada|des=ma=ya=da|19 +desventuras|des=ven=tu=ras|19 +docientos|do=cien=tos|19 +durmiendo|dur=mien=do|19 +espadas|es=pa=das|19 +espíritu|es=píri=tu|19 +estrecheza|es=tre=che=za|19 +estrellas|es=tre=llas|19 +fortaleza|for=ta=le=za|19 +hechas|he=chas|19 +hubieran|hu=bie=ran|19 +humana|hu=ma=na|19 +humano|hu=ma=no|19 +imaginaba|ima=gi=na=ba|19 +improviso|im=pro=vi=so|19 +liberalidad|li=be=ra=li=dad|19 +llenas|lle=nas|19 +Melisendra|Me=li=sen=dra|19 +menudo|me=nu=do|19 +mientras|mien=tras|19 +mirado|mi=ra=do|19 +miserable|mi=se=ra=ble|19 +Morena|More=na|19 +muelas|mue=las|19 +nación|na=ción|19 +olvido|ol=vi=do|19 +peligros|pe=li=gros|19 +pensado|pen=sa=do|19 +persiguen|per=si=guen|19 +piedras|pie=dras|19 +poniendo|po=nien=do|19 +posesión|po=se=sión|19 +primeros|pri=me=ros|19 +pudieran|pu=die=ran|19 +pudiere|pu=die=re|19 +quedará|que=da=rá|19 +secretario|se=cre=ta=rio|19 +Sevilla|Se=vi=lla|19 +sufrir|su=frir|19 +tengan|ten=gan|19 +testamento|tes=ta=men=to|19 +tocaba|to=ca=ba|19 +trance|tran=ce|19 +veréis|ve=réis|19 +virtudes|vir=tu=des|19 +acabada|aca=ba=da|18 +alegres|ale=gres|18 +aquéllos|aqué=llos|18 +bienes|bienes|18 +bálsamo|bál=samo|18 +cabreros|ca=bre=ros|18 +campaña|cam=pa=ña|18 +contenta|con=ten=ta|18 +cortés|cor=tés|18 +cuerpos|cuer=pos|18 +desdichas|des=di=chas|18 +desear|de=sear|18 +despojos|des=po=jos|18 +ducados|du=ca=dos|18 +engañado|en=ga=ña=do|18 +escondido|es=con=di=do|18 +escuadrón|es=cua=drón|18 +herida|he=ri=da|18 +hermanos|her=ma=nos|18 +honesto|ho=nes=to|18 +hubiere|hu=bie=re|18 +huesos|hue=sos|18 +lanzón|lan=zón|18 +Leandra|Lean=dra|18 +levantándose|le=van=tán=do=se|18 +llamada|lla=ma=da|18 +Llegóse|Lle=gó=se|18 +llenos|lle=nos|18 +Malambruno|Ma=lam=bruno|18 +manteles|man=te=les|18 +montañas|mon=ta=ñas|18 +necedades|ne=ce=da=des|18 +Nicolás|Ni=co=lás|18 +Nuestro|Nues=tro|18 +parientes|pa=rien=tes|18 +Paréceme|Pa=ré=ce=me|18 +pecados|pe=ca=dos|18 +perdón|per=dón|18 +persuadir|per=sua=dir|18 +premio|pre=mio|18 +probar|pro=bar|18 +profundo|pro=fun=do|18 +promesa|pro=me=sa|18 +prometida|pro=me=ti=da|18 +prometió|pro=me=tió|18 +proseguir|pro=se=guir|18 +querían|que=rían|18 +reposo|re=po=so|18 +ruegos|rue=gos|18 +sabido|sa=bi=do|18 +salario|sa=la=rio|18 +servicios|ser=vi=cios|18 +Verdad|Ver=dad|18 +Válame|Vá=la=me|18 +abundancia|abun=dan=cia|17 +acordó|acor=dó|17 +agreement|agree=ment|17 +amantes|aman=tes|17 +antigua|an=ti=gua|17 +arzobispo|ar=zo=bis=po|17 +ausente|au=sen=te|17 +Barcelona|Bar=ce=lo=na|17 +Berbería|Ber=be=ría|17 +bueyes|bue=yes|17 +cadena|ca=de=na|17 +castigo|cas=ti=go|17 +comenzado|co=men=za=do|17 +compasión|com=pa=sión|17 +compuesto|com=pues=to|17 +consideración|con=si=de=ra=ción|17 +continente|con=ti=nen=te|17 +corría|co=rría|17 +curiosidad|cu=rio=si=dad|17 +cuándo|cuán=do|17 +cuántas|cuán=tas|17 +descubrieron|des=cu=brie=ron|17 +deshora|des=ho=ra|17 +desmayo|des=ma=yo|17 +despertó|des=per=tó|17 +discursos|dis=cur=sos|17 +enamorada|ena=mo=ra=da|17 +encantamentos|en=can=ta=men=tos|17 +encina|en=ci=na|17 +enfermedad|en=fer=me=dad|17 +entera|en=te=ra|17 +escribano|es=cri=bano|17 +espejo|es=pe=jo|17 +estruendo|es=truen=do|17 +estuvieron|es=tu=vie=ron|17 +favorecer|fa=vo=re=cer|17 +flores|flo=res|17 +fuente|fuen=te|17 +garganta|gar=gan=ta|17 +hallarse|ha=llar=se|17 +heridas|he=ri=das|17 +huyendo|hu=yen=do|17 +iguales|igua=les|17 +llegase|lle=ga=se|17 +llevaron|lle=va=ron|17 +malandrines|ma=lan=dri=nes|17 +Mambrino|Mam=brino|17 +mandamiento|man=da=mien=to|17 +maravedís|ma=ra=ve=dís|17 +menesterosos|me=nes=te=ro=sos|17 +Micomicona|Mi=co=mi=co=na|17 +mortal|mor=tal|17 +mármol|már=mol|17 +ofrecía|ofre=cía|17 +parecerle|pa=re=cer=le|17 +pasase|pa=sa=se|17 +podido|po=di=do|17 +poesía|poesía|17 +prometo|pro=me=to|17 +puerto|puer=to|17 +puntualidad|pun=tua=li=dad|17 +quedase|que=da=se|17 +recado|re=ca=do|17 +regalo|re=ga=lo|17 +responde|res=pon=de|17 +santos|san=tos|17 +seguridad|se=gu=ri=dad|17 +seiscientos|seis=cien=tos|17 +sentidos|sen=ti=dos|17 +siguiente|si=guien=te|17 +Soneto|So=ne=to|17 +sospecha|sos=pe=cha|17 +supiese|su=pie=se|17 +tendría|ten=dría|17 +terrible|te=rri=ble|17 +usanza|usan=za|17 +Vicente|Vi=cen=te|17 +vuelvo|vuel=vo|17 +adorno|adorno|16 +Ambrosio|Am=bro=sio|16 +ansimesmo|an=si=mes=mo|16 +antiguos|an=ti=guos|16 +atentamente|aten=ta=men=te|16 +cantar|can=tar|16 +ciento|cien=to|16 +cincuenta|cin=cuen=ta|16 +colores|co=lo=res|16 +comedias|co=me=dias|16 +concierto|con=cier=to|16 +conocía|co=no=cía|16 +contando|con=tan=do|16 +contentos|con=ten=tos|16 +costumbres|cos=tum=bres|16 +cuesta|cues=ta|16 +dejaré|de=ja=ré|16 +demasiadamente|de=ma=sia=da=men=te|16 +detuvo|de=tu=vo|16 +dijere|di=je=re|16 +disparate|dis=pa=ra=te|16 +dondequiera|don=de=quie=ra|16 +ejercicios|ejer=ci=cios|16 +entero|en=te=ro|16 +entrambas|en=tram=bas|16 +envidia|en=vi=dia|16 +escribe|es=cri=be|16 +espuelas|es=pue=las|16 +Estaba|Es=ta=ba|16 +estacas|es=ta=cas|16 +Gaiferos|Gai=fe=ros|16 +habilidad|ha=bi=li=dad|16 +hablaba|ha=bla=ba|16 +habrán|ha=brán|16 +imprimir|im=pri=mir|16 +labradores|la=bra=do=res|16 +llevando|lle=van=do|16 +límites|lí=mi=tes|16 +maleta|ma=le=ta|16 +mandaba|man=da=ba|16 +Marién|Ma=rién|16 +marqués|mar=qués|16 +muchachos|mu=cha=chos|16 +naturales|na=tu=ra=les|16 +novela|no=ve=la|16 +parezca|pa=rez=ca|16 +pasaban|pa=sa=ban|16 +placer|pla=cer|16 +pondré|pon=dré|16 +príncipe|prín=ci=pe|16 +pudieron|pu=die=ron|16 +quejas|que=jas|16 +quisieren|qui=sie=ren|16 +quisiéredes|qui=sié=re=des|16 +respondido|res=pon=di=do|16 +respondiese|res=pon=die=se|16 +sabéis|sa=béis|16 +sosegado|so=se=ga=do|16 +tesoro|te=so=ro|16 +tiento|tien=to|16 +tristeza|tris=te=za|16 +ventaja|ven=ta=ja|16 +verano|ve=rano|16 +verdades|ver=da=des|16 +volverse|vol=ver=se|16 +abrazó|abra=zó|15 +ajenas|aje=nas|15 +alabanza|ala=ban=za|15 +alteza|al=te=za|15 +amante|aman=te|15 +amparo|am=pa=ro|15 +asiento|asien=to|15 +asiéndole|asién=do=le|15 +autoridad|au=to=ri=dad|15 +averiguar|ave=ri=guar|15 +bellotas|be=llo=tas|15 +buscarle|bus=car=le|15 +cabestro|ca=bes=tro|15 +castigar|cas=ti=gar|15 +cerrada|ce=rra=da|15 +cerrar|ce=rrar|15 +claridad|cla=ri=dad|15 +cobrar|co=brar|15 +condado|con=da=do|15 +confesar|con=fe=sar|15 +consintió|con=sin=tió|15 +consuelo|con=sue=lo|15 +contornos|con=tor=nos|15 +cuadrillero|cua=dri=lle=ro|15 +cuchilladas|cu=chi=lla=das|15 +decoro|de=co=ro|15 +dejará|de=ja=rá|15 +dejasen|de=ja=sen|15 +descubrir|des=cu=brir|15 +deshonra|des=hon=ra|15 +dignas|dig=nas|15 +duerme|duer=me|15 +dádivas|dádi=vas|15 +entrando|en=tran=do|15 +entretenimiento|en=tre=te=ni=mien=to|15 +escrúpulo|es=crú=pu=lo|15 +escuchaba|es=cu=cha=ba|15 +estima|es=ti=ma|15 +fermosa|fer=mo=sa|15 +fermosura|fer=mo=su=ra|15 +frente|fren=te|15 +gentileza|gen=ti=le=za|15 +grandezas|gran=de=zas|15 +hermosas|her=mo=sas|15 +humildad|hu=mil=dad|15 +huéspedes|huéspe=des|15 +imagen|ima=gen|15 +infinitas|in=fi=ni=tas|15 +ingrata|in=gra=ta|15 +jornada|jor=na=da|15 +leonero|leo=ne=ro|15 +libres|li=bres|15 +llegan|lle=gan|15 +Llegaron|Lle=ga=ron|15 +mentir|men=tir|15 +mentiras|men=ti=ras|15 +mirase|mi=ra=se|15 +montes|mon=tes|15 +negros|ne=gros|15 +niñerías|ni=ñe=rías|15 +noches|no=ches|15 +original|ori=gi=nal|15 +palacios|pa=la=cios|15 +pedido|pe=di=do|15 +pensando|pen=san=do|15 +perdición|per=di=ción|15 +prendas|pren=das|15 +procura|pro=cu=ra|15 +propuso|pro=pu=so|15 +puntas|pun=tas|15 +puntos|pun=tos|15 +redonda|re=don=da|15 +remediar|re=me=diar|15 +rescate|res=ca=te|15 +sabiendo|sa=bien=do|15 +sacaron|sa=ca=ron|15 +Salamanca|Sa=la=man=ca|15 +satisfación|sa=tis=fa=ción|15 +serían|se=rían|15 +sierra|sie=rra|15 +simplicidad|sim=pli=ci=dad|15 +sirven|sir=ven|15 +States|Sta=tes|15 +sustentar|sus=ten=tar|15 +tuertos|tuer=tos|15 +United|United|15 +vasallos|va=sa=llos|15 +viesen|vie=sen|15 +virrey|vi=rrey|15 +vitoria|vi=to=ria|15 +vuelven|vuel=ven|15 +acomodó|aco=mo=dó|14 +acudieron|acu=die=ron|14 +afrenta|afren=ta|14 +ahínco|ahín=co|14 +alcanzó|al=can=zó|14 +alzando|al=zan=do|14 +animal|ani=mal|14 +antiguo|an=ti=guo|14 +apartó|apar=tó|14 +apearse|apear=se|14 +arrogante|arro=gan=te|14 +arroyo|arro=yo|14 +añadidura|aña=di=du=ra|14 +báculo|bá=cu=lo|14 +caminante|ca=mi=nan=te|14 +castellana|cas=te=lla=na|14 +Cecial|Ce=cial|14 +ciertos|cier=tos|14 +cobarde|co=bar=de|14 +coloquio|co=lo=quio|14 +comedido|co=me=di=do|14 +confieso|con=fie=so|14 +conocidos|co=no=ci=dos|14 +Consejo|Con=se=jo|14 +contiene|con=tie=ne|14 +corrido|co=rri=do|14 +cuánto|cuán=to|14 +cuántos|cuán=tos|14 +cárcel|cár=cel|14 +decirte|de=cir=te|14 +dejaban|de=ja=ban|14 +docena|do=ce=na|14 +encanto|en=can=to|14 +enfermo|en=fer=mo|14 +entretener|en=tre=te=ner|14 +estrella|es=tre=lla|14 +faltan|fal=tan|14 +fiesta|fies=ta|14 +fiestas|fies=tas|14 +fuentes|fuen=tes|14 +graves|gra=ves|14 +guarde|guar=de|14 +hallará|ha=lla=rá|14 +izquierdo|iz=quier=do|14 +juzgar|juz=gar|14 +levantado|le=van=ta=do|14 +levantaron|le=van=ta=ron|14 +ligero|li=ge=ro|14 +Madrid|Ma=drid|14 +maestro|ma=es=tro|14 +medias|me=dias|14 +milagros|mi=la=gros|14 +mismas|mis=mas|14 +mismos|mis=mos|14 +muchacha|mu=cha=cha|14 +músico|mú=si=co|14 +olvidado|ol=vi=da=do|14 +ovejas|ove=jas|14 +palacio|pa=la=cio|14 +paraba|pa=ra=ba|14 +pasadas|pa=sa=das|14 +Pasamonte|Pa=samon=te|14 +pelear|pe=lear|14 +pendencias|pen=den=cias|14 +pidiese|pi=die=se|14 +pierna|pier=na|14 +principalmente|prin=ci=pal=men=te|14 +prisión|pri=sión|14 +procesión|pro=ce=sión|14 +pudiesen|pu=die=sen|14 +puñadas|pu=ña=das|14 +quedaré|que=da=ré|14 +quienquiera|quien=quie=ra|14 +razonable|ra=zo=na=ble|14 +referido|re=fe=ri=do|14 +república|re=pú=bli=ca|14 +rienda|rien=da|14 +riqueza|ri=que=za|14 +sentencia|sen=ten=cia|14 +sentir|sen=tir|14 +sesenta|se=s=en=ta|14 +siguieron|si=guie=ron|14 +siguió|si=guió|14 +testigo|tes=ti=go|14 +testigos|tes=ti=gos|14 +tomase|to=ma=se|14 +tristes|tris=tes|14 +tuviere|tu=vie=re|14 +vender|ven=der|14 +viniesen|vi=nie=sen|14 +vuesas|vue=sas|14 +zapatos|za=pa=tos|14 +abierto|abier=to|13 +abismo|abis=mo|13 +acabando|aca=ban=do|13 +acomodado|aco=mo=da=do|13 +agradecido|agra=de=ci=do|13 +alcornoque|al=cor=no=que|13 +amenazas|ame=na=zas|13 +anduvo|an=du=vo|13 +apeándose|apeán=do=se|13 +Archive|Ar=chi=ve|13 +atónito|ató=ni=to|13 +añadir|aña=dir|13 +blando|blan=do|13 +buscaba|bus=ca=ba|13 +canalla|ca=na=lla|13 +cansado|can=sa=do|13 +cantando|can=tan=do|13 +capellán|ca=pe=llán|13 +católico|ca=tó=li=co|13 +cebada|ce=ba=da|13 +claras|cla=ras|13 +compuso|com=pu=so|13 +condiciones|con=di=cio=nes|13 +conocida|co=no=ci=da|13 +considerando|con=si=de=ran=do|13 +conversación|con=ver=sación|13 +corales|co=ra=les|13 +corriendo|co=rrien=do|13 +criadas|cria=das|13 +cumplido|cum=pli=do|13 +curioso|cu=rio=so|13 +darles|dar=les|13 +decirme|de=cir=me|13 +denuedo|de=nue=do|13 +descubre|des=cu=bre|13 +desesperado|des=es=pe=ra=do|13 +dichoso|di=cho=so|13 +dignos|dig=nos|13 +diligencias|di=li=gen=cias|13 +dormía|dor=mía|13 +Díjole|Dí=jo=le|13 +echando|echan=do|13 +emperadores|em=pe=ra=do=res|13 +encerrado|en=ce=rra=do|13 +entereza|en=te=re=za|13 +escritas|es=cri=tas|13 +escuchar|es=cu=char|13 +escusar|es=cu=sar|13 +estará|es=ta=rá|13 +estimar|es=ti=mar|13 +estraños|es=tra=ños|13 +fantasmas|fan=tas=mas|13 +fatiga|fa=ti=ga|13 +Fortuna|For=tu=na|13 +frailes|frai=les|13 +fuertemente|fuer=te=men=te|13 +grandísima|gran=dí=si=ma|13 +gravedad|gra=ve=dad|13 +gritos|gri=tos|13 +habemos|ha=be=mos|13 +habría|ha=bría|13 +herido|he=ri=do|13 +historiador|his=to=ria=dor|13 +imitación|imi=ta=ción|13 +imitar|imi=tar|13 +instrumentos|ins=tru=men=tos|13 +juntas|jun=tas|13 +lanzas|lan=zas|13 +Leones|Leo=nes|13 +leyendo|le=yen=do|13 +leyese|le=ye=se|13 +ligera|li=ge=ra|13 +limpia|lim=pia|13 +Literary|Li=te=ra=ry|13 +llamando|lla=man=do|13 +llevase|lle=va=se|13 +maldiciones|mal=di=cio=nes|13 +manifiesto|ma=ni=fies=to|13 +miembros|miem=bros|13 +miente|mien=te|13 +molinos|mo=li=nos|13 +Muerte|Muer=te|13 +nombrar|nom=brar|13 +partió|par=tió|13 +piense|pien=se|13 +pierda|pier=da|13 +pliego|plie=go|13 +podrán|po=drán|13 +prados|pra=dos|13 +preguntado|pre=gun=ta=do|13 +Preguntó|Pre=gun=tó|13 +principios|prin=ci=pios|13 +procuraba|pro=cu=ra=ba|13 +propia|pro=pia|13 +puedan|pue=dan|13 +público|pú=bli=co|13 +quedara|que=da=ra|13 +QUIJOTE|QUI=JO=TE|13 +quisieron|qui=sie=ron|13 +rebuzno|re=buzno|13 +región|re=gión|13 +relación|re=la=ción|13 +respondí|res=pon=dí|13 +reverencia|re=ve=ren=cia|13 +romper|rom=per|13 +sacando|sacan=do|13 +sandeces|san=de=ces|13 +Segunda|Se=gun=da|13 +sentía|sen=tía|13 +Señora|Se=ño=ra|13 +siento|sien=to|13 +simples|sim=ples|13 +subieron|su=bie=ron|13 +suspiro|sus=pi=ro|13 +tardanza|tar=dan=za|13 +trademark|tra=de=ma=rk|13 +venideros|ve=ni=de=ros|13 +verdes|ver=des|13 +visera|vi=se=ra|13 +abierta|abier=ta|12 +abrazar|abra=zar|12 +acabase|aca=ba=se|12 +acertó|acer=tó|12 +acullá|acu=llá|12 +advertir|ad=ver=tir|12 +Adónde|Adón=de|12 +agradezco|agra=dez=co|12 +aguardar|aguar=dar|12 +agujero|agu=je=ro|12 +alcalde|al=cal=de|12 +almohada|al=moha=da|12 +amorosa|amo=ro=sa|12 +andando|an=dan=do|12 +apacible|apa=ci=ble|12 +apartado|apar=ta=do|12 +apostaré|apos=ta=ré|12 +armada|ar=ma=da|12 +armados|ar=ma=dos|12 +ayudar|ayu=dar|12 +Barataria|Ba=ra=ta=ria|12 +bardas|bar=das|12 +callando|ca=llan=do|12 +callar|ca=llar|12 +casamiento|ca=sa=mien=to|12 +casarse|ca=sar=se|12 +ciudades|ciu=da=des|12 +comenzar|co=men=zar|12 +concertado|con=cer=ta=do|12 +conocían|co=no=cían|12 +corteses|cor=te=ses|12 +costal|cos=tal|12 +cubierta|cu=bier=ta|12 +cuentos|cuen=tos|12 +cuáles|cuá=les|12 +cédula|cé=du=la|12 +dejarse|de=jar=se|12 +dejemos|de=je=mos|12 +demanda|de=man=da|12 +descubriese|des=cu=brie=se|12 +desdeñado|des=de=ña=do|12 +desdicha|des=di=cha|12 +desengaño|des=en=ga=ño|12 +despacio|des=pa=cio|12 +donations|do=na=tions|12 +Dígolo|Dí=go=lo|12 +encantos|en=can=tos|12 +encubrir|en=cu=brir|12 +enemiga|ene=mi=ga|12 +entienda|en=tien=da|12 +Entonces|En=ton=ces|12 +enviado|en=via=do|12 +escura|es=cu=ra|12 +español|es=pa=ñol|12 +españoles|es=pa=ño=les|12 +estrañas|es=tra=ñas|12 +estrecha|es=tre=cha|12 +faldas|fal=das|12 +faltas|fal=tas|12 +flaqueza|fla=que=za|12 +gobiernos|go=bier=nos|12 +Goleta|Go=le=ta|12 +graciosa|gra=cio=sa|12 +guardado|guar=da=do|12 +haberme|ha=ber=me|12 +habiéndose|ha=bién=do=se|12 +hiciere|hi=cie=re|12 +hidalgos|hi=dal=gos|12 +hombro|hom=bro|12 +honrados|hon=ra=dos|12 +iglesia|igle=sia|12 +ignorancia|ig=no=ran=cia|12 +imaginaciones|ima=gi=na=cio=nes|12 +imaginarse|ima=gi=nar=se|12 +indicio|in=di=cio|12 +instrumento|ins=tru=men=to|12 +jueces|jue=ces|12 +largas|lar=gas|12 +lastimada|las=ti=ma=da|12 +linajes|li=na=jes|12 +llanto|llan=to|12 +luengos|luen=gos|12 +materia|ma=te=ria|12 +mezcla|mez=cla|12 +mostrarse|mos=trar=se|12 +muriendo|mu=rien=do|12 +obligados|obli=ga=dos|12 +paredes|pa=re=des|12 +pasara|pa=sa=ra|12 +pedían|pe=dían|12 +perdió|per=dió|12 +perros|pe=rros|12 +pintada|pin=ta=da|12 +posada|po=sa=da|12 +preguntas|pre=gun=tas|12 +procurar|pro=cu=rar|12 +quedos|que=dos|12 +quédese|qué=de=se|12 +recibieron|re=ci=bie=ron|12 +referidas|re=fe=ri=das|12 +religión|re=li=gión|12 +repuesto|re=pues=to|12 +respondieron|res=pon=die=ron|12 +saliendo|salien=do|12 +salían|salían|12 +selvas|se=l=vas|12 +serviros|ser=vi=ros|12 +sierras|sie=rras|12 +sirvió|sir=vió|12 +socarrón|so=ca=rrón|12 +socorro|so=co=rro|12 +soneto|so=ne=to|12 +suceden|su=ce=den|12 +sucediese|su=ce=die=se|12 +sujetos|su=je=tos|12 +supiera|su=pie=ra|12 +tablas|ta=blas|12 +testimonio|tes=ti=mo=nio|12 +tratan|tra=tan|12 +valerosos|va=le=ro=sos|12 +vecinos|ve=ci=nos|12 +venturoso|ven=tu=ro=so|12 +Volvió|Vol=vió|12 +abiertos|abier=tos|11 +acababa|aca=ba=ba|11 +acogimiento|aco=gi=mien=to|11 +acompañar|acom=pa=ñar|11 +acontecido|acon=te=ci=do|11 +ademán|ade=mán|11 +admirar|ad=mi=rar|11 +admiró|ad=mi=ró|11 +advertido|ad=ver=ti=do|11 +agraviado|agra=via=do|11 +alcázar|al=cá=zar|11 +Alejandro|Ale=jan=dro|11 +alivio|ali=vio|11 +animales|ani=ma=les|11 +artillería|ar=ti=lle=ría|11 +arábigo|ará=bi=go|11 +aventurero|aven=tu=re=ro|11 +batanes|ba=ta=nes|11 +bestia|bes=tia|11 +cabecera|ca=be=ce=ra|11 +cabezas|ca=be=zas|11 +camaradas|ca=ma=ra=das|11 +camisas|ca=mi=sas|11 +cansancio|can=s=an=cio|11 +capitanes|ca=pi=ta=nes|11 +caridad|ca=ri=dad|11 +cascos|cas=cos|11 +caterva|ca=ter=va|11 +chusma|chus=ma|11 +ciertas|cier=tas|11 +claramente|cla=ra=men=te|11 +clavija|cla=vi=ja|11 +colgado|col=ga=do|11 +cometido|co=me=ti=do|11 +comisario|co=mi=sa=rio|11 +comparación|com=pa=ra=ción|11 +componer|com=po=ner|11 +compuesta|com=pues=ta|11 +conoce|co=no=ce|11 +conocieron|co=no=cie=ron|11 +consiste|con=sis=te|11 +contraria|con=tra=ria|11 +correo|co=rreo|11 +corriente|co=rrien=te|11 +cortesanos|cor=te=sanos|11 +criatura|cria=tu=ra|11 +crueldad|cruel=dad|11 +cuestas|cues=tas|11 +dejara|de=ja=ra|11 +dejarle|de=jar=le|11 +derribado|de=rri=ba=do|11 +desafío|de=sa=fío|11 +desaguisado|des=agui=sa=do|11 +desdenes|des=de=nes|11 +deshacer|des=ha=cer|11 +despidió|des=pi=dió|11 +detener|de=te=ner|11 +determinaron|de=ter=mi=na=ron|11 +dieren|die=ren|11 +dificultades|di=fi=cul=ta=des|11 +dormido|dor=mi=do|11 +Durandarte|Du=ran=dar=te|11 +Dígame|Dí=ga=me|11 +ejemplos|ejem=plos|11 +ejército|ejérci=to|11 +encuentro|en=cuen=tro|11 +escoger|es=co=ger|11 +escuridad|es=cu=ri=dad|11 +espera|es=pe=ra|11 +esperaban|es=pe=ra=ban|11 +estimación|es=ti=ma=ción|11 +estrecho|es=tre=cho|11 +gallarda|ga=llar=da|11 +guardas|guar=das|11 +Guinart|Gui=nart|11 +habilidades|ha=bi=li=da=des|11 +hablase|ha=bla=se|11 +hacerla|ha=cer=la|11 +hideputa|hi=de=pu=ta|11 +humanas|hu=ma=nas|11 +imaginó|ima=gi=nó|11 +imposibles|im=po=si=bles|11 +inconveniente|in=con=ve=nien=te|11 +ingenios|in=ge=nios|11 +invidia|in=vi=dia|11 +invierno|in=vierno|11 +laberinto|la=be=rin=to|11 +ladrones|la=dro=nes|11 +levantarse|le=van=tar=se|11 +limpieza|lim=pie=za|11 +llamarse|lla=mar=se|11 +llegaban|lle=ga=ban|11 +llegasen|lle=ga=sen|11 +llover|llo=ver|11 +matrimonio|ma=tri=mo=nio|11 +merecen|me=re=cen|11 +merecía|me=re=cía|11 +mesmas|mes=mas|11 +metido|me=ti=do|11 +molino|mo=lino|11 +moneda|mo=ne=da|11 +monesterio|mo=nes=te=rio|11 +morisca|mo=ris=ca|11 +muertos|muer=tos|11 +nacidos|na=ci=dos|11 +necedad|ne=ce=dad|11 +negocios|ne=go=cios|11 +obligación|obli=ga=ción|11 +ocasiones|oca=sio=nes|11 +oficios|ofi=cios|11 +ordenado|or=de=na=do|11 +parado|pa=ra=do|11 +pareciese|pa=re=cie=se|11 +pasatiempo|pa=sa=tiem=po|11 +peligrosa|pe=li=gro=sa|11 +pesada|pe=sa=da|11 +podemos|po=de=mos|11 +preguntóle|pre=gun=tó=le|11 +promete|pro=me=te|11 +prometer|pro=me=ter|11 +propias|pro=pias|11 +pudiendo|pu=dien=do|11 +puente|puen=te|11 +puercos|puer=cos|11 +pusiera|pu=sie=ra|11 +pérdida|pér=di=da|11 +quedamos|que=da=mos|11 +quejarse|que=jar=se|11 +querida|que=ri=da|11 +queriendo|que=rien=do|11 +quieras|quie=ras|11 +quitarme|qui=tar=me|11 +razonamiento|ra=zo=na=mien=to|11 +recibe|re=ci=be|11 +rendido|ren=di=do|11 +reposar|re=po=sar|11 +respuestas|res=pues=tas|11 +rodela|ro=de=la|11 +saldrá|sal=drá|11 +sentaron|sen=ta=ron|11 +servida|ser=vi=da|11 +servían|ser=vían|11 +solicitud|so=li=ci=tud|11 +sustento|sus=ten=to|11 +tendrá|ten=drá|11 +terciopelo|ter=cio=pe=lo|11 +tierna|tier=na|11 +tierras|tie=rras|11 +tocantes|to=can=tes|11 +Toledo|To=le=do|11 +traición|trai=ción|11 +través|tra=vés|11 +trueco|true=co|11 +trujeron|tru=je=ron|11 +vendría|ven=dría|11 +viejos|vie=jos|11 +vistas|vis=tas|11 +Vivaldo|Vi=val=do|11 +volverme|vol=ver=me|11 +órdenes|ór=de=nes|11 +última|úl=ti=ma|11 +access|ac=ce=ss|10 +accidente|ac=ci=den=te|10 +aceite|acei=te|10 +acertar|acer=tar|10 +Acudió|Acu=dió|10 +advierta|ad=vier=ta|10 +aficionado|afi=cio=na=do|10 +ajenos|aje=nos|10 +alabado|ala=ba=do|10 +alcaide|al=cai=de|10 +Alonso|Alon=so|10 +amorosos|amo=ro=sos|10 +Andalucía|An=da=lu=cía|10 +anoche|ano=che|10 +Antonomasia|An=to=no=ma=sia|10 +apartar|apar=tar|10 +Aragón|Ara=gón|10 +arrobas|arro=bas|10 +asperezas|as=pe=re=zas|10 +bastantes|bas=tan=tes|10 +Belerma|Be=ler=ma|10 +beneficio|be=ne=fi=cio|10 +caminando|ca=mi=nan=do|10 +caminantes|ca=mi=nan=tes|10 +Candaya|Can=da=ya|10 +carretero|ca=rre=te=ro|10 +catorce|ca=tor=ce|10 +causado|cau=sa=do|10 +causas|cau=sas|10 +cautiva|cau=ti=va|10 +cayeron|ca=ye=ron|10 +comiendo|co=mien=do|10 +componen|com=po=nen|10 +comúnmente|co=mún=men=te|10 +consentir|con=sen=tir|10 +considerar|con=si=de=rar|10 +continua|con=ti=nua|10 +Corchuelo|Cor=chue=lo|10 +corredores|co=rre=do=res|10 +corren|co=rren|10 +coyuntura|co=yun=tu=ra|10 +creído|creí=do|10 +criada|cria=da|10 +cuarta|cuar=ta|10 +cuarto|cuar=to|10 +cubrir|cu=brir|10 +cuentas|cuen=tas|10 +cuerno|cuerno|10 +cumplimiento|cum=pli=mien=to|10 +cántaro|cán=ta=ro|10 +debida|de=bi=da|10 +dejarme|de=jar=me|10 +descomunal|des=co=mu=nal|10 +deseosos|de=seo=sos|10 +desgraciado|des=gra=cia=do|10 +despedirse|des=pe=dir=se|10 +despierto|des=pier=to|10 +Después|Des=pués|10 +difunto|di=fun=to|10 +digáis|di=gáis|10 +disculpa|dis=cul=pa|10 +disposición|dis=po=si=ción|10 +donaires|do=nai=res|10 +echaron|echa=ron|10 +ejércitos|ejérci=tos|10 +embajada|em=ba=ja=da|10 +encomendándose|en=co=men=dán=do=se|10 +enviar|en=viar|10 +escritura|es=cri=tu=ra|10 +escusado|es=cu=sa=do|10 +espíritus|es=píri=tus|10 +estribos|es=tri=bos|10 +eterna|eter=na|10 +faltado|fal=ta=do|10 +felicísimo|fe=li=cí=si=mo|10 +ferido|fe=ri=do|10 +galera|ga=le=ra|10 +gallardía|ga=llar=día|10 +ganancia|ga=nan=cia|10 +gentilhombre|gen=tilhom=bre|10 +habiéndole|ha=bién=do=le|10 +hachas|ha=chas|10 +hallase|ha=lla=se|10 +hallazgo|ha=llaz=go|10 +hermosos|her=mo=sos|10 +ilustre|ilus=tre|10 +imágines|imá=gi=nes|10 +infinita|in=fi=ni=ta|10 +lealtad|leal=tad|10 +lenguaje|len=gua=je|10 +Levantóse|Le=van=tó=se|10 +License|Li=cen=se|10 +llevarle|lle=var=le|10 +llevas|lle=vas|10 +manchego|man=che=go|10 +menoscabo|me=nos=ca=bo|10 +mientes|mien=tes|10 +miserables|mi=se=ra=bles|10 +miseria|mi=se=ria|10 +mohíno|mohí=no|10 +morisco|mo=ris=co|10 +movimientos|mo=vi=mien=tos|10 +muestre|mues=tre|10 +nobleza|no=ble=za|10 +nombrado|nom=bra=do|10 +nosotras|no=so=tras|10 +ofrece|ofre=ce|10 +ofrezco|ofrez=co|10 +paragraph|pa=ra=gra=ph|10 +pedazo|pe=da=zo|10 +pelota|pe=lo=ta|10 +pendiente|pen=dien=te|10 +peregrino|pe=re=grino|10 +perfeción|per=fe=ción|10 +pierde|pier=de|10 +Pierres|Pie=rres|10 +pintor|pin=tor|10 +poderosa|po=de=ro=sa|10 +podrás|po=drás|10 +pondría|pon=dría|10 +poniéndose|po=nién=do=se|10 +primeras|pri=me=ras|10 +profeso|pro=fe=so|10 +profunda|pro=fun=da|10 +prosiguiendo|pro=si=guien=do|10 +quietud|quie=tud|10 +quitan|qui=tan|10 +quitaron|qui=ta=ron|10 +realmente|real=men=te|10 +referida|re=fe=ri=da|10 +reinas|rei=nas|10 +reliquias|re=li=quias|10 +requesones|re=que=so=nes|10 +responderé|res=pon=de=ré|10 +Respondió|Res=pon=dió|10 +Rodrigo|Ro=dri=go|10 +sandez|san=dez|10 +Satanás|Sata=nás|10 +satisfacer|sa=tis=fa=cer|10 +sentar|sen=tar|10 +singular|sin=gu=lar|10 +soberbia|so=ber=bia|10 +soledades|so=le=da=des|10 +soltar|sol=tar|10 +sonetos|so=ne=tos|10 +sospechas|sos=pe=chas|10 +temeridad|te=me=ri=dad|10 +tendrás|ten=drás|10 +tengas|ten=gas|10 +tormento|tor=men=to|10 +torres|to=rres|10 +tronco|tron=co|10 +tropel|tro=pel|10 +turbada|tur=ba=da|10 +vencer|ven=cer|10 +viernes|vier=nes|10 +viniendo|vi=nien=do|10 +volveré|vol=ve=ré|10 +éramos|éra=mos|10 +abriendo|abrien=do|9 +acabaron|aca=ba=ron|9 +achaque|acha=que|9 +acompañaban|acom=pa=ña=ban|9 +acompañe|acom=pa=ñe|9 +acostumbrada|acos=tum=bra=da|9 +acostumbrado|acos=tum=bra=do|9 +admiraba|ad=mi=ra=ba|9 +admiraron|ad=mi=ra=ron|9 +afición|afi=ción|9 +agradecimiento|agra=de=ci=mien=to|9 +alcanzado|al=can=za=do|9 +alcanzan|al=can=zan|9 +alrededor|al=re=de=dor|9 +amigas|ami=gas|9 +amorosas|amo=ro=sas|9 +Angélica|An=gé=li=ca|9 +ardite|ar=di=te|9 +atrevidos|atre=vi=dos|9 +bendito|ben=di=to|9 +bergantín|ber=gan=tín|9 +bocado|bo=ca=do|9 +cadenas|ca=de=nas|9 +callaba|ca=lla=ba|9 +calzas|cal=zas|9 +camina|ca=mi=na|9 +capitana|ca=pi=ta=na|9 +caritativo|ca=ri=ta=ti=vo|9 +Carlomagno|Car=lo=mag=no|9 +casarme|ca=sar=me|9 +Casildea|Ca=sil=dea|9 +castillos|cas=ti=llos|9 +cazadores|ca=za=do=res|9 +cerrado|ce=rra=do|9 +chirimías|chi=ri=mías|9 +cintura|cin=tu=ra|9 +circunstancias|cir=cuns=tan=cias|9 +comedimiento|co=me=di=mien=to|9 +comenzaba|co=men=za=ba|9 +comprar|com=prar|9 +compás|com=pás|9 +confirmación|con=fir=ma=ción|9 +conocemos|co=no=ce=mos|9 +conocen|co=no=cen|9 +contenía|con=te=nía|9 +contino|con=tino|9 +cordel|cor=del|9 +cortésmente|cor=tés=men=te|9 +Cuenta|Cuen=ta|9 +cumbre|cum=bre|9 +Córdoba|Cór=do=ba|9 +deciros|de=ci=ros|9 +dejaremos|de=ja=re=mos|9 +demasiado|de=ma=sia=do|9 +demonios|de=mo=nios|9 +desatino|des=a=tino|9 +descanso|des=can=so|9 +desventurado|des=ven=tu=ra=do|9 +Diablo|Dia=blo|9 +dichas|di=chas|9 +dificultoso|di=fi=cul=to=so|9 +docenas|do=ce=nas|9 +doliente|do=lien=te|9 +duelen|due=len|9 +déjame|dé=ja=me|9 +déjeme|dé=je=me|9 +déstos|dés=tos|9 +enamoró|ena=mo=ró|9 +encontrar|en=con=trar|9 +entendía|en=ten=día|9 +entraba|en=tra=ba|9 +entretenía|en=tre=te=nía|9 +ermita|er=mi=ta|9 +escrita|es=cri=ta|9 +esfuerzo|es=fuer=zo|9 +espacioso|es=pa=cio=so|9 +espuma|es=pu=ma|9 +estampa|es=tam=pa|9 +estremos|es=tre=mos|9 +firmeza|fir=me=za|9 +franceses|fran=ce=ses|9 +fresca|fres=ca|9 +frontero|fron=te=ro|9 +fácilmente|fá=cil=men=te|9 +gallinas|ga=lli=nas|9 +Grecia|Gre=cia|9 +guardaba|guar=da=ba|9 +guardando|guar=dan=do|9 +gutenberg|gu=ten=berg|9 +géneros|gé=ne=ros|9 +haberla|ha=ber=la|9 +hacerlo|ha=cer=lo|9 +hallara|ha=lla=ra|9 +hermana|her=ma=na|9 +hermosísima|her=mo=sí=si=ma|9 +hubieron|hu=bie=ron|9 +hubiesen|hu=bie=sen|9 +humildes|hu=mil=des|9 +iguala|igua=la|9 +imperio|im=pe=rio|9 +imposibilidad|im=po=si=bi=li=dad|9 +impresión|im=pre=sión|9 +impreso|im=pre=so|9 +incomparable|in=com=pa=ra=ble|9 +Indias|In=dias|9 +interés|in=te=rés|9 +Interés|In=te=rés|9 +intrépido|in=trépi=do|9 +inútil|inú=til|9 +juramentos|ju=ra=men=tos|9 +Lanzarote|Lan=za=ro=te|9 +lastimado|las=ti=ma=do|9 +lector|lec=tor|9 +letrados|le=tra=dos|9 +levantando|le=van=tan=do|9 +limpias|lim=pias|9 +lleven|lle=ven|9 +malezas|ma=le=zas|9 +maltrecho|mal=tre=cho|9 +MANCHA|MAN=CHA|9 +maneras|ma=ne=ras|9 +Mantua|Man=tua|9 +medios|me=dios|9 +medroso|me=dro=so|9 +melancolía|me=lan=co=lía|9 +merecido|me=re=ci=do|9 +moverse|mo=ver=se|9 +mudado|mu=da=do|9 +nacida|na=ci=da|9 +necesarias|ne=ce=sa=rias|9 +notable|no=ta=ble|9 +notado|no=ta=do|9 +obligaciones|obli=ga=cio=nes|9 +ojeriza|oje=ri=za|9 +olvide|ol=vi=de|9 +ordinaria|or=di=na=ria|9 +padece|pa=de=ce|9 +palafrén|pa=la=frén|9 +parecerme|pa=re=cer=me|9 +partido|par=ti=do|9 +partir|par=tir|9 +partirse|par=tir=se|9 +pastoral|pas=to=ral|9 +pedirle|pe=dir=le|9 +peores|peo=res|9 +peregrinos|pe=re=gri=nos|9 +pintar|pin=tar|9 +plazas|pla=zas|9 +podrían|po=drían|9 +pongan|pon=gan|9 +poniéndole|po=nién=do=le|9 +ponían|po=nían|9 +porfía|por=fía|9 +preguntase|pre=gun=ta=se|9 +prenda|pren=da|9 +quedaban|que=da=ban|9 +quedándose|que=dán=do=se|9 +Quisiera|Qui=sie=ra|9 +Reinaldos|Rei=nal=dos|9 +reprehensión|re=prehen=sión|9 +resucitar|re=su=ci=tar|9 +riberas|ri=be=ras|9 +riguroso|ri=gu=ro=so|9 +rincón|rin=cón|9 +Roncesvalles|Ron=ces=va=lles|9 +sabemos|sa=be=mos|9 +sabios|sa=bios|9 +sacarle|sa=car=le|9 +saltando|sal=tan=do|9 +Sancha|San=cha|9 +sentimientos|sen=ti=mien=tos|9 +sepáis|se=páis|9 +siguiendo|si=guien=do|9 +siéndolo|sién=do=lo|9 +sollozos|so=llo=zos|9 +subido|su=bi=do|9 +suceda|su=ce=da|9 +suelta|suel=ta|9 +tendió|ten=dió|9 +tocado|to=ca=do|9 +trasluce|tras=lu=ce|9 +trataba|tra=ta=ba|9 +tratado|tra=ta=do|9 +trompetas|trom=pe=tas|9 +turbado|tur=ba=do|9 +tuviesen|tu=vie=sen|9 +venerable|ve=ne=ra=ble|9 +venimos|ve=ni=mos|9 +veremos|ve=re=mos|9 +vestir|ves=tir|9 +villana|vi=lla=na|9 +Virgilio|Vir=gi=lio|9 +vituperios|vi=tu=pe=rios|9 +viudas|viu=das|9 +volverá|vol=ve=rá|9 +volviesen|vol=vie=sen|9 +aceñas|ace=ñas|8 +acompañamiento|acom=pa=ña=mien=to|8 +aconteció|acon=te=ció|8 +Admirado|Ad=mi=ra=do|8 +advierte|ad=vier=te|8 +alboroto|al=bo=ro=to|8 +alcuza|al=cu=za|8 +alfanje|al=fan=je|8 +amarillo|ama=ri=llo|8 +amenaza|ame=na=za|8 +anochecer|ano=che=cer|8 +apartaron|apar=ta=ron|8 +apartándose|apar=tán=do=se|8 +aprieta|aprie=ta|8 +aprovecharse|apro=ve=char=se|8 +arrieros|arrie=ros|8 +arráez|arráez|8 +asiendo|asien=do|8 +atadas|ata=das|8 +atentos|aten=tos|8 +barruntos|ba=rrun=tos|8 +bastaba|bas=ta=ba|8 +blancos|blan=cos|8 +bonitamente|bo=ni=ta=men=te|8 +brevemente|bre=ve=men=te|8 +caldero|cal=de=ro|8 +caliente|ca=lien=te|8 +caminaban|ca=mi=na=ban|8 +cantaba|can=ta=ba|8 +carneros|car=ne=ros|8 +Castilla|Cas=ti=lla|8 +ciencias|cien=cias|8 +cinchas|cin=chas|8 +comoquiera|co=mo=quie=ra|8 +concertadas|con=cer=ta=das|8 +confianza|con=fian=za|8 +conocerle|co=no=cer=le|8 +conoces|co=no=ces|8 +conocí|co=no=cí|8 +conozca|co=noz=ca|8 +consiente|con=sien=te|8 +contaba|con=ta=ba|8 +contentó|con=ten=tó|8 +cordura|cor=du=ra|8 +cortesano|cor=te=sano|8 +cortesías|cor=tesías|8 +cristal|cris=tal|8 +cuarenta|cua=ren=ta|8 +cuatrocientos|cua=tro=cien=tos|8 +cuchillada|cu=chi=lla=da|8 +Cuerpo|Cuer=po|8 +cumpla|cum=pla|8 +Curioso|Cu=rio=so|8 +debieron|de=bie=ron|8 +debéis|de=béis|8 +declaraba|de=cla=ra=ba|8 +dejamos|de=ja=mos|8 +dejaría|de=ja=ría|8 +demasía|de=ma=sía|8 +derribó|de=rri=bó|8 +derrota|de=rro=ta|8 +desafíos|de=sa=fíos|8 +descubriendo|des=cu=brien=do|8 +descubría|des=cu=bría|8 +desdén|des=dén|8 +desigual|de=si=gual|8 +despoblados|des=po=bla=dos|8 +diciplinantes|di=ci=pli=nan=tes|8 +diciéndoles|di=cién=do=les|8 +diferente|di=fe=ren=te|8 +dificultad|di=fi=cul=tad|8 +dijesen|di=je=sen|8 +discreciones|dis=cre=cio=nes|8 +distintamente|dis=tin=ta=men=te|8 +diversos|di=ver=sos|8 +divino|di=vino|8 +duermen|duer=men|8 +dándome|dán=do=me|8 +edificio|edi=fi=cio|8 +embestir|em=bes=tir|8 +embuste|em=bus=te|8 +embustes|em=bus=tes|8 +encaje|en=ca=je|8 +encerramiento|en=ce=rra=mien=to|8 +encomendarse|en=co=men=dar=se|8 +engañar|en=ga=ñar|8 +entierro|en=tie=rro|8 +entretanto|en=tre=tan=to|8 +escogido|es=co=gi=do|8 +escondida|es=con=di=da|8 +escuchado|es=cu=cha=do|8 +espantable|es=pan=ta=ble|8 +espantado|es=pan=ta=do|8 +esperiencia|es=pe=rien=cia|8 +estarse|es=tar=se|8 +estaría|es=ta=ría|8 +estuviere|es=tu=vie=re|8 +estábamos|es=tá=ba=mos|8 +eterno|eterno|8 +excelencia|ex=ce=len=cia|8 +falsas|fal=sas|8 +faltarán|fal=ta=rán|8 +famosas|fa=mo=sas|8 +fantasma|fan=tas=ma|8 +fatigaba|fa=ti=ga=ba|8 +feridas|fe=ri=das|8 +fidelidad|fi=de=li=dad|8 +finísimo|fi=ní=si=mo|8 +firmes|fir=mes|8 +follones|fo=llo=nes|8 +fueren|fue=ren|8 +fuertes|fuer=tes|8 +furioso|fu=rio=so|8 +galeote|ga=leo=te|8 +ganadero|ga=na=de=ro|8 +gemidos|ge=mi=dos|8 +generoso|ge=ne=ro=so|8 +graciosos|gra=cio=sos|8 +greguescos|gre=gues=cos|8 +guardada|guar=da=da|8 +hablador|ha=bla=dor|8 +habrás|ha=brás|8 +habréis|ha=bréis|8 +habíamos|ha=bía=mos|8 +hacerte|ha=cer=te|8 +hallan|ha=llan|8 +Hermano|Her=ma=no|8 +historiadores|his=to=ria=do=res|8 +holgara|hol=ga=ra|8 +honroso|hon=ro=so|8 +huevos|hue=vos|8 +ignorantes|ig=no=ran=tes|8 +impresa|im=pre=sa|8 +including|in=clu=ding|8 +infame|in=fa=me|8 +infinito|in=fi=ni=to|8 +intencionado|in=ten=cio=na=do|8 +intenciones|in=ten=cio=nes|8 +invencible|in=ven=ci=ble|8 +jurado|ju=ra=do|8 +juzgado|juz=ga=do|8 +labradoras|la=bra=do=ras|8 +legítima|le=gí=ti=ma|8 +levantada|le=van=ta=da|8 +leyenda|le=yen=da|8 +libreas|li=breas|8 +liebre|lie=bre|8 +llamaban|lla=ma=ban|8 +llegamos|lle=ga=mos|8 +llegué|lle=gué|8 +lloraba|llo=ra=ba|8 +lícito|lí=ci=to|8 +mandase|man=da=se|8 +manjares|man=ja=res|8 +margen|mar=gen|8 +medicina|me=di=ci=na|8 +memorias|me=mo=rias|8 +mercader|mer=ca=der|8 +Micomicón|Mi=co=mi=cón|8 +Miranda|Mi=ran=da|8 +molineros|mo=li=ne=ros|8 +momentos|mo=men=tos|8 +Montalbán|Mon=tal=bán|8 +Moreno|Mo=reno|8 +mostraron|mos=tra=ron|8 +Muchas|Mu=chas|8 +médicos|mé=di=cos|8 +nacimiento|na=ci=mien=to|8 +naciones|na=cio=nes|8 +narración|na=rra=ción|8 +necesaria|ne=ce=sa=ria|8 +Nápoles|Ná=po=les|8 +obligada|obli=ga=da|8 +ociosidad|ocio=si=dad|8 +ofrecido|ofre=ci=do|8 +ordena|or=de=na|8 +ordenanzas|or=de=nan=zas|8 +orejas|ore=jas|8 +paradero|pa=ra=de=ro|8 +particulares|par=ti=cu=la=res|8 +pasamos|pa=sa=mos|8 +pensaban|pen=sa=ban|8 +pensase|pen=sa=se|8 +perdonar|per=do=nar|8 +perdone|per=do=ne|8 +perezoso|pe=re=zo=so|8 +personaje|per=so=na=je|8 +pesaba|pe=sa=ba|8 +piadoso|pia=do=so|8 +pidiendo|pi=dien=do|8 +piensas|pien=sas|8 +poblado|po=bla=do|8 +pollinos|po=lli=nos|8 +pregunté|pre=gun=té|8 +Preguntóle|Pre=gun=tó=le|8 +proceder|pro=ce=der|8 +procurando|pro=cu=ran=do|8 +prosiguen|pro=si=guen|8 +prosupuesto|pro=su=pues=to|8 +pusiesen|pu=sie=sen|8 +Púsose|Pú=so=se|8 +quebrada|que=bra=da|8 +quebrantado|que=bran=ta=do|8 +quedarse|que=dar=se|8 +quedóse|que=dó=se|8 +quemar|que=mar|8 +Quiero|Quie=ro|8 +quitarle|qui=tar=le|8 +rebuznar|re=buz=nar|8 +recebida|re=ce=bi=da|8 +recebirle|re=ce=bir=le|8 +recién|re=cién|8 +refund|re=fund|8 +regocijo|re=go=ci=jo|8 +reposada|re=po=sa=da|8 +repúblicas|re=pú=bli=cas|8 +riesgo|ries=go|8 +riscos|ris=cos|8 +rogando|ro=gan=do|8 +ruedas|rue=das|8 +Ruidera|Rui=de=ra|8 +rústico|rús=ti=co|8 +saberse|sa=ber=se|8 +sacaba|sa=ca=ba|8 +sacaré|sa=ca=ré|8 +sagacidad|saga=ci=dad|8 +salimos|sali=mos|8 +sastre|sas=tre|8 +servidos|ser=vi=dos|8 +servirle|ser=vir=le|8 +servía|ser=vía|8 +Siendo|Sien=do|8 +siente|sien=te|8 +soberbio|so=ber=bio|8 +socorrer|so=co=rrer|8 +sucedidas|su=ce=di=das|8 +sábanas|sá=ba=nas|8 +tendida|ten=di=da|8 +tenerla|te=ner=la|8 +tenerle|te=ner=le|8 +tercero|ter=ce=ro|8 +tienda|tien=da|8 +tinieblas|ti=nie=blas|8 +Tirteafuera|Tir=tea=fue=ra|8 +tocadores|to=ca=do=res|8 +tomaba|to=ma=ba|8 +tomaron|to=ma=ron|8 +traerle|traer=le|8 +trajes|tra=jes|8 +trataban|tra=ta=ban|8 +Trifaldín|Tri=fal=dín|8 +tripas|tri=pas|8 +tuerto|tuer=to|8 +títulos|tí=tu=los|8 +túmulo|tú=mu=lo|8 +veamos|vea=mos|8 +vencida|ven=ci=da|8 +vencidos|ven=ci=dos|8 +vengan|ven=gan|8 +vengar|ven=gar|8 +vengarse|ven=gar=se|8 +ventanas|ven=ta=nas|8 +visión|vi=sión|8 +viéndola|vién=do=la|8 +váyase|vá=ya=se|8 +without|wi=thout|8 +yeguas|ye=guas|8 +zapato|za=pa=to|8 +áspero|ás=pe=ro|8 +acomodados|aco=mo=da=dos|7 +acomodarse|aco=mo=dar=se|7 +acontecimientos|acon=te=ci=mien=tos|7 +acordaba|acor=da=ba|7 +acuerda|acuer=da|7 +acémila|acé=mi=la|7 +aderezada|ade=re=za=da|7 +adivino|adi=vino|7 +admirables|ad=mi=ra=bles|7 +adornada|ador=na=da|7 +advertid|ad=ver=tid|7 +afirmar|afir=mar|7 +agradecida|agra=de=ci=da|7 +aguarda|aguar=da|7 +agüero|agüe=ro|7 +albedrío|al=be=drío|7 +alborotado|al=bo=ro=ta=do|7 +aldeana|al=dea=na|7 +Aldonza|Al=don=za|7 +alegrar|ale=grar|7 +alerta|aler=ta|7 +alzaron|al=za=ron|7 +amoroso|amo=ro=so|7 +antojadiza|an=to=ja=di=za|7 +aparejos|apa=re=jos|7 +aplauso|aplau=so|7 +aprobación|apro=ba=ción|7 +aprovechar|apro=ve=char|7 +arcabuces|ar=ca=bu=ces|7 +arrimado|arri=ma=do|7 +asalto|asal=to|7 +aspereza|as=pe=re=za|7 +associated|as=so=ciated|7 +asturiana|as=tu=ria=na|7 +asumpto|asump=to|7 +atados|ata=dos|7 +atrevió|atre=vió|7 +aurora|au=ro=ra|7 +aventaja|aven=ta=ja|7 +averiguada|ave=ri=gua=da|7 +ballesta|ba=lles=ta|7 +banderas|ban=de=ras|7 +bastan|bas=tan|7 +bellaquería|be=lla=que=ría|7 +Bendito|Ben=di=to|7 +beneplácito|be=ne=plá=ci=to|7 +Bernardo|Ber=nar=do|7 +billete|bi=lle=te|7 +bizarría|bi=za=rría|7 +blandas|blan=das|7 +borrasca|bo=rras=ca|7 +calzones|cal=zo=nes|7 +campanas|cam=pa=nas|7 +candil|can=dil|7 +cansados|can=sa=dos|7 +caperuzas|ca=pe=ru=zas|7 +Capitán|Ca=pi=tán|7 +carreras|ca=rre=ras|7 +causar|cau=sar|7 +celebro|ce=le=bro|7 +celoso|ce=lo=so|7 +cenaron|ce=na=ron|7 +cerrados|ce=rra=dos|7 +codicioso|co=di=cio=so|7 +cogido|co=gi=do|7 +colérico|co=lé=ri=co|7 +comenzando|co=men=zan=do|7 +comienzan|co=mien=zan|7 +compasivo|com=pa=si=vo|7 +compostura|com=pos=tu=ra|7 +compuestas|com=pues=tas|7 +confesó|con=fe=só|7 +confirmar|con=fir=mar|7 +confirmó|con=fir=mó|7 +confusa|con=fu=sa|7 +considera|con=si=de=ra|7 +consolar|con=so=lar|7 +Constantinopla|Cons=tan=ti=no=pla|7 +contarse|con=tar=se|7 +contase|con=ta=se|7 +contrarios|con=tra=rios|7 +contravenir|con=tra=ve=nir|7 +conveniente|con=ve=nien=te|7 +copies|co=pies|7 +coplas|co=plas|7 +corazones|co=ra=zo=nes|7 +coronas|co=ro=nas|7 +creció|cre=ció|7 +creyese|cre=ye=se|7 +crianza|crian=za|7 +cruces|cru=ces|7 +créame|créa=me|7 +Cuatro|Cua=tro|7 +cubiertos|cu=bier=tos|7 +cubrió|cu=brió|7 +cuitas|cui=tas|7 +curado|cu=ra=do|7 +cuánta|cuán=ta|7 +Cámara|Cá=ma=ra|7 +daremos|da=re=mos|7 +darían|da=rían|7 +decencia|de=cen=cia|7 +Decidme|De=cid=me|7 +declara|de=cla=ra|7 +declarado|de=cla=ra=do|7 +defenderse|de=fen=der=se|7 +dejándole|de=ján=do=le|7 +demostraciones|de=mos=tra=cio=nes|7 +desdichados|des=di=cha=dos|7 +deseaban|de=sea=ban|7 +deseado|de=sea=do|7 +desean|de=sean|7 +desencantada|des=en=can=ta=da|7 +desiertos|de=sier=tos|7 +desmán|des=mán|7 +despechado|des=pe=cha=do|7 +destreza|des=tre=za|7 +detenía|de=te=nía|7 +diamante|dia=man=te|7 +diamantes|dia=man=tes|7 +diciplina|di=ci=pli=na|7 +diente|dien=te|7 +dijésemos|di=jé=se=mos|7 +disparatados|dis=pa=ra=ta=dos|7 +distributing|dis=tri=bu=ting|7 +doloroso|do=lo=ro=so|7 +dueños|due=ños|7 +durmió|dur=mió|7 +dádiva|dádi=va|7 +eBooks|eBooks|7 +eclesiástico|ecle=siás=ti=co|7 +empero|em=pe=ro|7 +encaminaban|en=ca=mi=na=ban|7 +encaminó|en=ca=mi=nó|7 +encarecidamente|en=ca=re=ci=da=men=te|7 +encendidas|en=cen=di=das|7 +encierran|en=cie=rran|7 +encontrado|en=contra=do|7 +engendró|en=gen=dró|7 +entrase|en=tra=se|7 +entregó|en=tre=gó|7 +enviase|en=via=se|7 +envuelto|en=vuel=to|7 +ermitaño|er=mi=ta=ño|7 +escopetas|es=co=pe=tas|7 +Escritura|Es=cri=tu=ra|7 +escuela|es=cue=la|7 +espantosa|es=pan=to=sa|7 +esperan|es=pe=ran|7 +estados|es=ta=dos|7 +estaré|es=ta=ré|7 +estimaba|es=ti=ma=ba|7 +estranjeros|es=tran=je=ros|7 +estrechamente|es=tre=cha=men=te|7 +estudiar|es=tu=diar|7 +estudio|es=tu=dio|7 +estuve|es=tu=ve|7 +estuviesen|es=tu=vie=sen|7 +faltaban|fal=ta=ban|7 +fantasía|fan=ta=sía|7 +favorable|fa=vo=ra=ble|7 +favorezca|fa=vo=rez=ca|7 +fazañas|fa=za=ñas|7 +Felixmarte|Fe=lix=mar=te|7 +fingidas|fin=gi=das|7 +galope|ga=lo=pe|7 +garras|ga=rras|7 +gastar|gas=tar|7 +gentiles|gen=ti=les|7 +Guadiana|Gua=dia=na|7 +guardan|guar=dan|7 +guardarse|guar=dar=se|7 +guiaba|guia=ba|7 +hacerlas|ha=cer=las|7 +haciéndole|ha=cién=do=le|7 +hagáis|ha=gáis|7 +hallarle|ha=llar=le|7 +haremos|ha=re=mos|7 +hazaña|ha=za=ña|7 +hembra|hem=bra|7 +heredera|he=re=de=ra|7 +herreruelo|he=rre=rue=lo|7 +hiciesen|hi=cie=sen|7 +hincar|hin=car|7 +Hircania|Hir=ca=nia|7 +Homero|Ho=me=ro|7 +huérfanos|huér=fa=nos|7 +Hízose|Hí=zo=se|7 +igualar|igua=lar|7 +imaginando|ima=gi=nan=do|7 +imposibilitado|im=po=si=bi=li=ta=do|7 +imprimiere|im=pri=mie=re|7 +inclinado|in=cli=na=do|7 +inconvenientes|in=con=ve=nien=tes|7 +indicios|in=di=cios|7 +innumerables|in=nu=me=ra=bles|7 +insulanos|in=su=la=nos|7 +invenciones|in=ven=cio=nes|7 +jornadas|jor=na=das|7 +juridición|ju=ri=di=ción|7 +justos|jus=tos|7 +lagunas|la=gu=nas|7 +levanta|le=van=ta|7 +levantase|le=van=ta=se|7 +leyere|le=ye=re|7 +libremente|li=bre=men=te|7 +librillo|li=bri=llo|7 +limosna|li=mos=na|7 +llamas|lla=mas|7 +llegará|lle=ga=rá|7 +llevasen|lle=va=sen|7 +lleváis|lle=váis|7 +llorando|llo=ran=do|7 +located|lo=ca=ted|7 +malicias|ma=li=cias|7 +mamonas|ma=mo=nas|7 +manadas|ma=na=das|7 +manjar|man=jar|7 +maravillo|ma=ra=vi=llo|7 +maravilloso|ma=ra=vi=llo=so|7 +Marsilio|Mar=si=lio|7 +mediano|me=diano|7 +medicinas|me=di=ci=nas|7 +medida|me=di=da|7 +memorable|me=mo=ra=ble|7 +menear|me=near|7 +menesterosa|me=nes=te=ro=sa|7 +menguado|men=gua=do|7 +mentirosos|men=ti=ro=sos|7 +menudencias|me=nu=den=cias|7 +merecían|me=re=cían|7 +ministros|mi=nis=tros|7 +mirara|mi=ra=ra|7 +mirándole|mi=rán=do=le|7 +mochachos|mo=cha=chos|7 +montera|mon=te=ra|7 +Morato|Mo=ra=to|7 +movimiento|mo=vi=mien=to|7 +mudanza|mu=dan=za|7 +mínima|mí=ni=ma|7 +naturalmente|na=tu=ral=men=te|7 +ningunas|nin=gu=nas|7 +ocupado|ocu=pa=do|7 +ofender|ofen=der|7 +ofrecen|ofre=cen|7 +ofrecer|ofre=cer|7 +ofrecimiento|ofre=ci=mien=to|7 +oración|ora=ción|7 +Pardiez|Par=diez|7 +parecieron|pa=re=cie=ron|7 +parezcan|pa=rez=can|7 +pasando|pa=san=do|7 +pastoras|pas=to=ras|7 +pensáis|pen=sáis|7 +perdiese|per=die=se|7 +pereza|pe=re=za|7 +perpetuo|per=pe=tuo|7 +pleito|plei=to|7 +plumas|plu=mas|7 +podremos|po=dre=mos|7 +podréis|po=dréis|7 +predicar|pre=di=car|7 +pregunto|pre=gun=to|7 +preguntándole|pre=gun=tán=do=le|7 +presos|pre=sos|7 +pretende|pre=ten=de|7 +prevenciones|pre=ven=cio=nes|7 +procuraré|pro=cu=ra=ré|7 +profesa|pro=fe=sa|7 +profundos|pro=fun=dos|7 +prometen|pro=me=ten|7 +proprio|pro=prio|7 +provide|pro=vi=de|7 +prudentes|pru=den=tes|7 +purgatorio|pur=ga=to=rio|7 +pública|pú=bli=ca|7 +púsose|pú=so=se|7 +quejar|que=jar|7 +quinto|quin=to|7 +quisiesen|qui=sie=sen|7 +quitaba|qui=ta=ba|7 +razonamientos|ra=zo=na=mien=tos|7 +reciben|re=ci=ben|7 +recibo|re=ci=bo|7 +recogiendo|re=co=gien=do|7 +recogimiento|re=co=gi=mien=to|7 +recogió|re=co=gió|7 +reducir|re=du=cir|7 +referidos|re=fe=ri=dos|7 +regalos|re=ga=los|7 +regidor|re=gi=dor|7 +religioso|re=li=gio=so|7 +representan|re=pre=sen=tan|7 +representó|re=pre=sen=tó|7 +requiebros|re=quie=bros|7 +requieren|re=quie=ren|7 +responda|res=pon=da|7 +revuelto|re=vuel=to|7 +rigurosa|ri=gu=ro=sa|7 +romano|ro=ma=no|7 +rompió|rom=pió|7 +rosario|ro=sa=rio|7 +rubios|ru=bios|7 +rústica|rús=ti=ca|7 +sabrosa|sa=bro=sa|7 +sacase|saca=se|7 +secretos|se=cre=tos|7 +Section|Sec=tion|7 +Segovia|Se=go=via|7 +seguramente|se=gu=ra=men=te|7 +seguía|se=guía|7 +sentada|sen=ta=da|7 +sepultado|se=pul=ta=do|7 +setenta|se=ten=ta|7 +señalado|se=ña=la=do|7 +Señores|Se=ño=res|7 +siguiese|si=guie=se|7 +sobremanera|so=bre=ma=ne=ra|7 +sosegada|so=se=ga=da|7 +sostenía|sos=te=nía|7 +sucedieron|su=ce=die=ron|7 +suelto|suel=to|7 +suspender|sus=pen=der|7 +suspensión|sus=pen=sión|7 +sustancia|sus=tan=cia|7 +sustenta|sus=ten=ta|7 +tamaño|ta=ma=ño|7 +tambores|tam=bo=res|7 +temerario|te=me=ra=rio|7 +tendrán|ten=drán|7 +tendrían|ten=drían|7 +ternera|ter=ne=ra|7 +tierno|tierno|7 +tiernos|tier=nos|7 +tiraba|ti=ra=ba|7 +titerero|ti=te=re=ro|7 +tontos|ton=tos|7 +tornaron|tor=na=ron|7 +Torralba|To=rral=ba|7 +tragedia|tra=ge=dia|7 +trujese|tru=je=se|7 +tuvieran|tu=vie=ran|7 +universal|uni=ver=sal|7 +Valencia|Va=len=cia|7 +valles|va=lles|7 +ventajas|ven=ta=jas|7 +venturas|ven=tu=ras|7 +vestiglos|ves=ti=glos|7 +vientre|vien=tre|7 +vieres|vie=res|7 +viéndome|vién=do=me|7 +volando|vo=lan=do|7 +voluntades|vo=lun=ta=des|7 +yangüeses|yan=güe=ses|7 +ánimos|áni=mos|7 +áspera|ás=pe=ra|7 +abismos|abis=mos|6 +abrazada|abra=za=da|6 +acaban|aca=ban|6 +ACADÉMICO|ACA=DÉ=MI=CO|6 +acierto|acier=to|6 +acomodar|aco=mo=dar|6 +acompañado|acom=pa=ña=do|6 +acompañados|acom=pa=ña=dos|6 +acontecimiento|acon=te=ci=mien=to|6 +acosado|aco=sa=do|6 +acrecentar|acre=cen=tar|6 +Acudieron|Acu=die=ron|6 +Adelante|Ade=lan=te|6 +adelantó|ade=lan=tó|6 +admirada|ad=mi=ra=da|6 +adornan|ador=nan|6 +adulación|adu=la=ción|6 +advertida|ad=ver=ti=da|6 +advirtiese|ad=vir=tie=se|6 +Agramante|Agra=man=te|6 +agüeros|agüe=ros|6 +ahorrar|aho=rrar|6 +alborotada|al=bo=ro=ta=da|6 +albricias|al=bri=cias|6 +alcaldes|al=cal=des|6 +alcance|al=can=ce|6 +alcanzaba|al=can=za=ba|6 +alcurnia|al=cur=nia|6 +aldeas|al=deas|6 +algodón|al=go=dón|6 +amanecer|ama=ne=cer|6 +amarga|amar=ga|6 +andamos|an=da=mos|6 +andanza|an=dan=za|6 +andurriales|an=du=rria=les|6 +anejas|ane=jas|6 +ansimismo|an=si=mis=mo|6 +antoja|an=to=ja|6 +antojos|an=to=jos|6 +apaleado|apa=lea=do|6 +apariencia|apa=rien=cia|6 +apartarse|apar=tar=se|6 +Apeáronse|Apeá=ron=se|6 +aprieto|aprie=to|6 +aprovechó|apro=ve=chó|6 +archivo|ar=chi=vo|6 +ardiendo|ar=dien=do|6 +ARGAMASILLA|AR=GA=MA=SI=LLA|6 +arroyos|arro=yos|6 +asidos|asi=dos|6 +atendiendo|aten=dien=do|6 +atengo|aten=go|6 +atenta|aten=ta|6 +atravesado|atra=ve=sa=do|6 +Aunque|Aun=que|6 +aventureros|aven=tu=re=ros|6 +averiguado|ave=ri=gua=do|6 +barato|ba=ra=to|6 +Barrabás|Ba=rra=bás|6 +Belianís|Be=lia=nís|6 +bellacos|be=lla=cos|6 +bigotes|bi=go=tes|6 +blandura|blan=du=ra|6 +borracho|bo=rra=cho|6 +borrica|bo=rri=ca|6 +boticario|bo=ti=ca=rio|6 +bravos|bra=vos|6 +brocado|bro=ca=do|6 +buenamente|bue=na=men=te|6 +buscalle|bus=ca=lle|6 +buscase|bus=ca=se|6 +cabalgadura|ca=bal=ga=du=ra|6 +caletre|ca=le=tre|6 +casada|ca=sa=da|6 +cautiverio|cau=ti=ve=rio|6 +cesaba|ce=sa=ba|6 +charge|char=ge|6 +chozas|cho=zas|6 +cierra|cie=rra|6 +Clavijo|Cla=vi=jo|6 +cogiendo|co=gien=do|6 +coloquios|co=lo=quios|6 +comedimientos|co=me=di=mien=tos|6 +comply|com=ply|6 +compra|com=pra|6 +comunicar|co=mu=ni=car|6 +conceptos|con=cep=tos|6 +concluir|con=cluir|6 +confusos|con=fu=sos|6 +conociera|co=no=cie=ra|6 +conseguir|con=se=guir|6 +consentimiento|con=sen=ti=mien=to|6 +consentiré|con=sen=ti=ré|6 +consoló|con=so=ló|6 +contienen|con=tie=nen|6 +continuas|con=ti=nuas|6 +contrahecha|contra=he=cha|6 +convertida|con=ver=ti=da|6 +corrió|co=rrió|6 +cortar|cor=tar|6 +creyeron|cre=ye=ron|6 +cristianas|cris=tia=nas|6 +cristiandad|cris=tian=dad|6 +cuadrilla|cua=dri=lla|6 +cuartillo|cuar=ti=llo|6 +cuartos|cuar=tos|6 +cubiertas|cu=bier=tas|6 +cubrían|cu=brían|6 +cuchillo|cu=chi=llo|6 +cuente|cuen=te|6 +cuerda|cuer=da|6 +culebras|cu=le=bras|6 +cumple|cum=ple|6 +cumplirá|cum=pli=rá|6 +darnos|dar=nos|6 +debemos|de=be=mos|6 +decidme|de=cid=me|6 +decille|de=ci=lle|6 +declaración|de=cla=ra=ción|6 +declarar|de=cla=rar|6 +dejadme|de=jad=me|6 +demasiada|de=ma=sia=da|6 +derechamente|de=re=cha=men=te|6 +derechas|de=re=chas|6 +derechos|de=re=chos|6 +desaforado|des=afo=ra=do|6 +desagradecido|des=agra=de=ci=do|6 +desalmados|des=al=ma=dos|6 +descargar|des=car=gar|6 +descubierta|des=cu=bier=ta|6 +descubrirse|des=cu=brir=se|6 +descuidado|des=cui=da=do|6 +descuidos|des=cui=dos|6 +deseada|de=sea=da|6 +desenvuelta|des=en=vuel=ta|6 +desesperaba|des=es=pe=ra=ba|6 +deshecho|des=he=cho|6 +deshizo|des=hi=zo|6 +desnuda|des=nu=da|6 +desnudez|des=nu=dez|6 +despertar|des=per=tar|6 +despidieron|des=pi=die=ron|6 +despierta|des=pier=ta|6 +destruir|des=truir|6 +Deteneos|De=te=neos|6 +determiné|de=ter=mi=né|6 +dichos|di=chos|6 +dieran|die=ran|6 +dignidad|dig=ni=dad|6 +diremos|di=re=mos|6 +disparatado|dis=pa=ra=ta=do|6 +distribution|dis=tri=bu=tion|6 +divina|di=vi=na|6 +divinas|di=vi=nas|6 +doctrina|doc=tri=na|6 +dudoso|du=do=so|6 +dársela|dár=se=la|6 +Díjome|Dí=jo=me|6 +echaba|echa=ba|6 +echándole|echán=do=le|6 +edades|eda=des|6 +elegante|ele=gan=te|6 +embrazó|em=bra=zó|6 +embustero|em=bus=te=ro|6 +empacho|em=pa=cho|6 +empleada|em=plea=da|6 +encamisados|en=ca=mi=sa=dos|6 +encantadas|en=can=ta=das|6 +encarecer|en=ca=re=cer|6 +encendida|en=cen=di=da|6 +encerrar|en=ce=rrar|6 +encerró|en=ce=rró|6 +entienden|en=tien=den|6 +entremeto|en=tre=me=to|6 +escopeta|es=co=pe=ta|6 +escucha|es=cu=cha|6 +escuchan|es=cu=chan|6 +espesas|es=pe=sas|6 +espías|es=pías|6 +estancias|es=tan=cias|6 +estandarte|es=tan=dar=te|6 +estender|es=ten=der|6 +estiende|es=tien=de|6 +estorbar|es=tor=bar|6 +estrañeza|es=tra=ñe=za|6 +estremado|es=tre=ma=do|6 +estribo|es=tri=bo|6 +estudiantes|es=tu=dian=tes|6 +estuvimos|es=tu=vi=mos|6 +faltará|fal=ta=rá|6 +faltase|fal=ta=se|6 +fealdad|feal=dad|6 +fechos|fe=chos|6 +fineza|fi=ne=za|6 +fingido|fin=gi=do|6 +fingidos|fin=gi=dos|6 +fingiendo|fin=gien=do|6 +firmar|fir=mar|6 +Flandes|Flan=des|6 +follón|fo=llón|6 +formar|for=mar|6 +fresco|fres=co|6 +frutas|fru=tas|6 +fuimos|fui=mos|6 +fábula|fá=bu=la|6 +galgos|gal=gos|6 +gallegos|ga=lle=gos|6 +García|Gar=cía|6 +gobiernan|go=bier=nan|6 +graduado|gra=dua=do|6 +gremio|gre=mio|6 +griego|grie=go|6 +guarden|guar=den|6 +gustos|gus=tos|6 +gustoso|gus=to=so|6 +habedes|ha=be=des|6 +haberos|ha=be=ros|6 +hablaban|ha=bla=ban|6 +habérsela|ha=bér=se=la|6 +hacernos|ha=cer=nos|6 +hacéis|ha=céis|6 +hallaban|ha=lla=ban|6 +hallamos|ha=lla=mos|6 +hallas|ha=llas|6 +hallóle|ha=lló=le|6 +haréis|ha=réis|6 +harían|ha=rían|6 +hechicero|he=chi=ce=ro|6 +hechura|he=chu=ra|6 +heredero|he=re=de=ro|6 +Hiciéronlo|Hi=cié=ron=lo|6 +hinojos|hi=no=jos|6 +honrosa|hon=ro=sa|6 +Hércules|Hércu=les|6 +igualmente|igual=men=te|6 +imitando|imi=tan=do|6 +impertinentes|im=per=ti=nen=tes|6 +importaba|im=por=ta=ba|6 +impresas|im=pre=sas|6 +inaudito|inau=di=to|6 +inclemencias|in=cle=men=cias|6 +inclinación|in=cli=na=ción|6 +Ingalaterra|In=ga=la=te=rra|6 +Italia|Ita=lia|6 +izquierda|iz=quier=da|6 +Jerónimo|Je=ró=ni=mo|6 +jineta|ji=ne=ta|6 +justamente|jus=ta=men=te|6 +largos|lar=gos|6 +leerla|leer=la|6 +legítimo|le=gí=ti=mo|6 +letrado|le=tra=do|6 +letura|le=tu=ra|6 +levantadas|le=van=ta=das|6 +license|li=cen=se|6 +llamaron|lla=ma=ron|6 +llegara|lle=ga=ra|6 +llevaría|lle=va=ría|6 +luciente|lu=cien=te|6 +lumbre|lum=bre|6 +madera|ma=de=ra|6 +Magalona|Ma=ga=lo=na|6 +magnificencia|mag=ni=fi=cen=cia|6 +malandrín|ma=lan=drín|6 +maldades|mal=da=des|6 +maldito|mal=di=to|6 +malferido|mal=fe=ri=do|6 +manifiesta|ma=ni=fies=ta|6 +manteado|man=tea=do|6 +maravillosa|ma=ra=vi=llo=sa|6 +mentiroso|men=ti=ro=so|6 +miembro|miem=bro|6 +millas|mi=llas|6 +Miróle|Mi=ró=le|6 +misterio|mis=te=rio|6 +mocedad|mo=ce=dad|6 +mochacho|mo=cha=cho|6 +moderno|mo=derno|6 +molimiento|mo=li=mien=to|6 +monarca|mo=nar=ca|6 +morrión|mo=rrión|6 +mostrase|mos=tra=se|6 +movieron|mo=vie=ron|6 +muchedumbre|mu=che=dum=bre|6 +muertas|muer=tas|6 +murmurando|mur=mu=ran=do|6 +muñeca|mu=ñe=ca|6 +músicas|mú=si=cas|6 +necesarios|ne=ce=sa=rios|6 +necesidades|ne=ce=si=da=des|6 +negras|ne=gras|6 +ninfas|nin=fas|6 +Ninguno|Nin=guno|6 +obligó|obli=gó|6 +ochenta|ochen=ta|6 +ociosas|ocio=sas|6 +ociosos|ocio=sos|6 +ocupada|ocu=pa=da|6 +ocupar|ocu=par|6 +ordinarios|or=di=na=rios|6 +orientales|orien=ta=les|6 +Orlando|Or=lan=do|6 +Ovidio|Ovi=dio|6 +pacífico|pa=cí=fi=co|6 +pajarillos|pa=ja=ri=llos|6 +Panzas|Pan=zas|6 +Parece|Pa=re=ce|6 +parecerles|pa=re=cer=les|6 +Parecióle|Pa=re=ció=le|6 +parecióle|pa=re=ció=le|6 +pasatiempos|pa=sa=tiem=pos|6 +pasmado|pas=ma=do|6 +peligroso|pe=li=gro=so|6 +pequeños|pe=que=ños|6 +perdices|per=di=ces|6 +perdida|per=di=da|6 +pergamino|per=ga=mino|6 +permission|per=mis=sion|6 +permita|per=mi=ta|6 +permite|per=mi=te|6 +personajes|per=so=na=jes|6 +pesaroso|pe=sa=ro=so|6 +pescadores|pes=ca=do=res|6 +piensan|pien=san|6 +pienses|pien=ses|6 +pierdo|pier=do|6 +plegarias|ple=ga=rias|6 +pollina|po=lli=na|6 +pondrá|pon=drá|6 +porfiar|por=fiar|6 +Portugal|Por=tu=gal|6 +Preguntáronle|Pre=gun=tá=ron=le|6 +Preguntéle|Pre=gun=té=le|6 +prestado|pres=ta=do|6 +princesas|prin=ce=sas|6 +procurase|pro=cu=ra=se|6 +profesamos|pro=fe=sa=mos|6 +profesan|pro=fe=san|6 +propiedad|pro=pie=dad|6 +propios|pro=pios|6 +prosiga|pro=si=ga|6 +provechoso|pro=ve=cho=so|6 +provincia|pro=vin=cia|6 +puntualmente|pun=tual=men=te|6 +pájaros|pá=ja=ros|6 +queden|que=den|6 +Querría|Que=rría|6 +quinientos|qui=nien=tos|6 +quisieran|qui=sie=ran|6 +quitasen|qui=ta=sen|6 +quitársela|qui=tár=se=la|6 +Radamanto|Ra=da=man=to|6 +rebuznos|re=buz=nos|6 +recebí|re=ce=bí|6 +recebían|re=ce=bían|6 +redondo|re=don=do|6 +redundar|re=dun=dar|6 +reglas|re=glas|6 +remate|re=ma=te|6 +renombre|re=nom=bre|6 +representa|re=pre=sen=ta|6 +representar|re=pre=sen=tar|6 +requiere|re=quie=re|6 +respetos|res=pe=tos|6 +Respondióle|Res=pon=dió=le|6 +retirada|re=ti=ra=da|6 +retiró|re=ti=ró|6 +retrato|re=tra=to|6 +Ricardo|Ri=car=do|6 +rodearon|ro=dea=ron|6 +rodeos|ro=deos|6 +rogaron|ro=ga=ron|6 +sabiduría|sa=bi=du=ría|6 +sacerdote|sacer=do=te|6 +saetas|sae=tas|6 +saldrán|sal=drán|6 +salgan|sal=gan|6 +saliera|salie=ra|6 +salteador|sal=tea=dor|6 +satisfaré|sa=tis=fa=ré|6 +satisfechos|sa=tis=fe=chos|6 +seamos|sea=mos|6 +secreta|se=cre=ta|6 +seguido|se=gui=do|6 +sendas|sen=das|6 +serpiente|ser=pien=te|6 +siesta|sies=ta|6 +simplicidades|sim=pli=ci=da=des|6 +sirvieron|sir=vie=ron|6 +soberbios|so=ber=bios|6 +sobras|so=bras|6 +sombrero|som=bre=ro|6 +sonaba|so=na=ba|6 +sonaban|so=na=ban|6 +subiendo|su=bien=do|6 +sueltos|suel=tos|6 +suertes|suer=tes|6 +sufrimiento|su=fri=mien=to|6 +suplicándole|su=pli=cán=do=le|6 +temerosos|te=me=ro=sos|6 +templo|tem=plo|6 +tenerme|te=ner=me|6 +tengamos|ten=ga=mos|6 +tengáis|ten=gáis|6 +tentar|ten=tar|6 +tercia|ter=cia|6 +tiendas|tien=das|6 +tomara|to=ma=ra|6 +tortas|tor=tas|6 +traerme|traer=me|6 +tragos|tra=gos|6 +transformada|trans=for=ma=da|6 +transformado|trans=for=ma=do|6 +trasladar|tras=la=dar|6 +tratando|tra=tan=do|6 +triunfo|triun=fo|6 +trocara|tro=ca=ra|6 +trofeo|tro=feo|6 +trompeta|trom=pe=ta|6 +Uchalí|Ucha=lí|6 +usaban|usa=ban|6 +usando|usan=do|6 +Vandalia|Van=da=lia|6 +vejigas|ve=ji=gas|6 +venció|ven=ció|6 +vengado|ven=ga=do|6 +venganzas|ven=gan=zas|6 +ventas|ven=tas|6 +venturosa|ven=tu=ro=sa|6 +Verdaderamente|Ver=da=de=ra=men=te|6 +verdugo|ver=du=go|6 +verdugos|ver=du=gos|6 +verlas|ver=las|6 +vestidas|ves=ti=das|6 +vicios|vi=cios|6 +vienes|vie=nes|6 +viniera|vi=nie=ra|6 +vinieren|vi=nie=ren|6 +virtuosos|vir=tuo=sos|6 +visitar|vi=si=tar|6 +Viéndose|Vién=do=se|6 +volvamos|vol=va=mos|6 +Volvióse|Vol=vió=se|6 +vomitar|vo=mi=tar|6 +vueltas|vuel=tas|6 +Válate|Vá=la=te|6 +zagalas|za=ga=las|6 +ángeles|án=ge=les|6 +aborrecido|abo=rre=ci=do|5 +abrasó|abra=só|5 +abrazándole|abra=zán=do=le|5 +abundante|abun=dan=te|5 +acabóse|aca=bó=se|5 +acaecimientos|acae=ci=mien=tos|5 +acertara|acer=ta=ra|5 +acertare|acer=ta=re|5 +acomete|aco=me=te|5 +acomodaron|aco=mo=da=ron|5 +acompañada|acom=pa=ña=da|5 +acontecer|acon=te=cer|5 +acordado|acor=da=do|5 +acordarse|acor=dar=se|5 +acorrer|aco=rrer|5 +acostumbradas|acos=tum=bra=das|5 +acostumbrados|acos=tum=bra=dos|5 +acreditar|acre=di=tar|5 +ademanes|ade=ma=nes|5 +adentro|aden=tro|5 +aderezado|ade=re=za=do|5 +admirarse|ad=mi=rar=se|5 +adondequiera|adon=de=quie=ra|5 +advertimiento|ad=ver=ti=mien=to|5 +Advierte|Ad=vier=te|5 +afligida|afli=gi=da|5 +afligidos|afli=gi=dos|5 +afrentado|afren=ta=do|5 +Afuera|Afue=ra|5 +agradables|agra=da=bles|5 +agradeció|agra=de=ció|5 +aguardando|aguar=dan=do|5 +agudeza|agu=de=za|5 +aguijón|agui=jón|5 +ahechando|ahe=chan=do|5 +ahorcado|ahor=ca=do|5 +ahorcar|ahor=car|5 +alabardas|ala=bar=das|5 +alameda|ala=me=da|5 +albogues|al=bo=gues|5 +alcahuete|al=cahue=te|5 +alcanzara|al=can=za=ra|5 +alcanzaron|al=can=za=ron|5 +alcázares|al=cá=za=res|5 +alegraron|ale=gra=ron|5 +alegró|ale=gró|5 +Alemania|Ale=ma=nia|5 +alfilerazos|al=fi=le=ra=zos|5 +alguaciles|al=gua=ci=les|5 +aljófar|al=jó=far|5 +altamente|al=ta=men=te|5 +alzaba|al=za=ba|5 +Amadises|Ama=di=ses|5 +amargo|amar=go|5 +ambición|am=bi=ción|5 +andará|an=da=rá|5 +anduviesen|an=du=vie=sen|5 +angustia|an=gus=tia|5 +anillo|ani=llo|5 +ansias|an=sias|5 +anyone|an=yo=ne|5 +aparato|apa=ra=to|5 +apellido|ape=lli=do|5 +apetito|ape=ti=to|5 +Apeóse|Apeó=se|5 +aporreado|apo=rrea=do|5 +apostura|apos=tu=ra|5 +aprendido|apren=di=do|5 +apretar|apre=tar|5 +apuesta|apues=ta|5 +Aquella|Aque=lla|5 +aquéllas|aqué=llas|5 +arenga|aren=ga|5 +Aristóteles|Aris=tó=te=les|5 +arrepentido|arre=pen=ti=do|5 +arrepentimiento|arre=pen=ti=mien=to|5 +arrimada|arri=ma=da|5 +arrimo|arri=mo|5 +arrimó|arri=mó|5 +asaltos|asal=tos|5 +ascuras|as=cu=ras|5 +asendereado|asen=de=rea=do|5 +atabales|ata=ba=les|5 +atambores|atam=bo=res|5 +atender|aten=der|5 +atendía|aten=día|5 +atentado|aten=ta=do|5 +atreva|atre=va|5 +atrevida|atre=vi=da|5 +atrevo|atre=vo|5 +atónitos|ató=ni=tos|5 +aumento|au=men=to|5 +avellanas|ave=lla=nas|5 +avemarías|ave=ma=rías|5 +averiguación|ave=ri=gua=ción|5 +avisado|avi=sa=do|5 +avisar|avi=sar|5 +avínole|aví=no=le|5 +ayudase|ayu=da=se|5 +bajaba|ba=ja=ba|5 +balcón|bal=cón|5 +barbado|bar=ba=do|5 +barriga|ba=rri=ga|5 +bastón|bas=tón|5 +bocací|bo=ca=cí|5 +bonísima|bo=ní=si=ma|5 +brebaje|bre=ba=je|5 +Bretaña|Bre=ta=ña|5 +brinco|brin=co|5 +buitre|bui=tre|5 +burlado|bur=la=do|5 +callaré|ca=lla=ré|5 +camarada|ca=ma=ra=da|5 +caminaba|ca=mi=na=ba|5 +caminado|ca=mi=na=do|5 +campea|cam=pea|5 +canséis|can=séis|5 +cantor|can=tor|5 +cariño|ca=ri=ño|5 +Carpio|Car=pio|5 +castigados|cas=ti=ga=dos|5 +catadura|ca=ta=du=ra|5 +católica|ca=tó=li=ca|5 +cayera|ca=ye=ra|5 +cayese|ca=ye=se|5 +caídos|caí=dos|5 +cebolla|ce=bo=lla|5 +celebrar|ce=le=brar|5 +Cirongilio|Ci=ron=gi=lio|5 +cocinero|co=ci=ne=ro|5 +codicia|co=di=cia|5 +cohecho|cohe=cho|5 +colcha|col=cha|5 +comencé|co=men=cé|5 +comieron|co=mie=ron|5 +comisión|co=mi=sión|5 +compañera|com=pa=ñe=ra|5 +competir|com=pe=tir|5 +comunique|co=mu=ni=que|5 +concedido|con=ce=di=do|5 +concertada|con=cer=ta=da|5 +concertó|con=cer=tó|5 +conducido|con=du=ci=do|5 +confío|con=fío|5 +conservar|con=ser=var|5 +considerado|con=si=de=ra=do|5 +consiguiente|con=si=guien=te|5 +contarle|con=tar=le|5 +contaré|con=ta=ré|5 +contentar|con=ten=tar|5 +contienda|con=tien=da|5 +convertido|con=ver=ti=do|5 +coronista|co=ro=nis=ta|5 +coroza|co=ro=za|5 +correspondencia|co=rres=pon=den=cia|5 +corresponder|co=rres=pon=der|5 +cortada|cor=ta=da|5 +cortado|cor=ta=do|5 +cortes|cor=tes|5 +costas|cos=tas|5 +crecer|cre=cer|5 +creciendo|cre=cien=do|5 +creyera|cre=ye=ra|5 +crujía|cru=jía|5 +cuadra|cua=dra|5 +cualesquiera|cua=les=quie=ra|5 +cubría|cu=bría|5 +cuenten|cuen=ten|5 +cuerdos|cuer=dos|5 +cumplan|cum=plan|5 +Cupido|Cu=pi=do|5 +curiosos|cu=rio=sos|5 +danzas|dan=zas|5 +debiera|de=bie=ra|5 +decirlo|de=cir=lo|5 +declarase|de=cla=ra=se|5 +declaró|de=cla=ró|5 +dejalle|de=ja=lle|5 +dejarte|de=jar=te|5 +dejéis|de=jéis|5 +deleitar|de=lei=tar|5 +delgado|del=ga=do|5 +delicado|de=li=ca=do|5 +delitos|de=li=tos|5 +denantes|de=nan=tes|5 +derramando|de=rra=man=do|5 +derramar|de=rra=mar|5 +derribar|de=rri=bar|5 +desaforados|des=afo=ra=dos|5 +desagradecida|des=agra=de=ci=da|5 +desalmado|des=al=ma=do|5 +descalzos|des=cal=zos|5 +descargó|des=car=gó|5 +desconocida|des=co=no=ci=da|5 +descontenta|des=con=ten=ta|5 +descubrirme|des=cu=brir=me|5 +desembarazado|des=em=ba=ra=za=do|5 +desencantar|des=en=can=tar|5 +desesperados|des=es=pe=ra=dos|5 +deshaciendo|des=ha=cien=do|5 +deshechas|des=he=chas|5 +desierta|de=sier=ta|5 +desierto|de=sier=to|5 +desmayado|des=ma=ya=do|5 +desmesurado|des=me=su=ra=do|5 +desnudar|des=nu=dar|5 +despachó|des=pa=chó|5 +desposado|des=po=sa=do|5 +desposorios|des=po=so=rios|5 +destierro|des=tie=rro|5 +desviados|des=via=dos|5 +detenerme|de=te=ner=me|5 +detenerse|de=te=ner=se|5 +detenga|de=ten=ga|5 +determinaba|de=ter=mi=na=ba|5 +detuvieron|de=tu=vie=ron|5 +detuviese|de=tu=vie=se|5 +devoción|de=vo=ción|5 +dichosa|di=cho=sa|5 +dificultosas|di=fi=cul=to=sas|5 +difícil|di=fí=cil|5 +dijeren|di=je=ren|5 +dilatado|di=la=ta=do|5 +disculpas|dis=cul=pas|5 +disgusto|dis=gus=to|5 +disignio|di=sig=nio|5 +disimular|di=si=mu=lar|5 +distribute|dis=tri=bu=te|5 +diversidad|di=ver=si=dad|5 +donate|do=na=te|5 +doradas|do=ra=das|5 +dudosos|du=do=sos|5 +duelos|due=los|5 +Duerme|Duer=me|5 +dulces|dul=ces|5 +dándose|dán=do=se|5 +Dígote|Dí=go=te|5 +embajador|em=ba=ja=dor|5 +embrazando|em=bra=zan=do|5 +emperatrices|em=pe=ra=tri=ces|5 +empresas|em=pre=sas|5 +encaja|en=ca=ja|5 +encaminadas|en=ca=mi=na=das|5 +encaminando|en=ca=mi=nan=do|5 +encaminar|en=ca=mi=nar|5 +encender|en=cen=der|5 +encerraba|en=ce=rra=ba|5 +encerrada|en=ce=rra=da|5 +encerrados|en=ce=rra=dos|5 +encoger|en=co=ger|5 +encontró|en=contró|5 +encrucijadas|en=cru=ci=ja=das|5 +encubre|en=cu=bre|5 +encuentros|en=cuen=tros|5 +enderezar|en=de=re=zar|5 +enfado|en=fa=do|5 +enferma|en=fer=ma|5 +engañaba|en=ga=ña=ba|5 +enhoramala|enho=ra=ma=la|5 +enjaulado|en=jau=la=do|5 +enmienda|en=mien=da|5 +enramada|en=ra=ma=da|5 +enseñado|en=se=ña=do|5 +enseñan|en=se=ñan|5 +enseñar|en=se=ñar|5 +entena|en=te=na|5 +entendiendo|en=ten=dien=do|5 +entendieron|en=ten=die=ron|5 +entendí|en=ten=dí|5 +enterado|en=te=ra=do|5 +enteros|en=te=ros|5 +enterrar|en=te=rrar|5 +Entraron|En=tra=ron|5 +entretenidos|en=tre=te=ni=dos|5 +entretenimientos|en=tre=te=ni=mien=tos|5 +entretiene|en=tre=tie=ne|5 +enviaba|en=via=ba|5 +enviarle|en=viar=le|5 +erratas|erra=tas|5 +escaparse|es=ca=par=se|5 +escogió|es=co=gió|5 +escrebir|es=cre=bir|5 +escribía|es=cri=bía|5 +escrutinio|es=cru=ti=nio|5 +escuchó|es=cu=chó|5 +escuderil|es=cu=de=ril|5 +espaciosa|es=pa=cio=sa|5 +espantoso|es=pan=to=so|5 +esperad|es=pe=rad|5 +estacada|es=ta=ca=da|5 +estatua|es=ta=tua|5 +estendió|es=ten=dió|5 +estimada|es=ti=ma=da|5 +estrado|es=tra=do|5 +estrechas|es=tre=chas|5 +estrema|es=tre=ma|5 +Estremadura|Es=tre=ma=du=ra|5 +estudios|es=tu=dios|5 +estéril|es=té=ril|5 +eternamente|eter=na=men=te|5 +excepto|ex=cep=to|5 +experiencias|ex=pe=rien=cias|5 +extraordinario|ex=tra=or=di=na=rio|5 +fabricada|fa=bri=ca=da|5 +facilitar|fa=ci=li=tar|5 +facultad|fa=cul=tad|5 +faltando|fal=tan=do|5 +faltare|fal=ta=re|5 +fantástico|fan=tás=ti=co|5 +fatigaban|fa=ti=ga=ban|5 +fatigado|fa=ti=ga=do|5 +felicemente|fe=li=ce=men=te|5 +felicidad|fe=li=ci=dad|5 +felicísima|fe=li=cí=si=ma|5 +fingida|fin=gi=da|5 +fingir|fin=gir|5 +flechas|fle=chas|5 +florestas|flo=res=tas|5 +formaban|for=ma=ban|5 +forzada|for=za=da|5 +forzado|for=za=do|5 +forzosamente|for=zo=sa=men=te|5 +fraile|frai=le|5 +franco|fran=co|5 +fueras|fue=ras|5 +fugitivo|fu=gi=ti=vo|5 +fulano|fu=lano|5 +fuésemos|fué=se=mos|5 +fábulas|fá=bu=las|5 +Galaor|Ga=la=or|5 +gallina|ga=lli=na|5 +gansos|gan=sos|5 +Gaspar|Gas=par|5 +generosos|ge=ne=ro=sos|5 +Ginebra|Gi=ne=bra|5 +Ginesillo|Gi=ne=si=llo|5 +gobierna|go=bier=na|5 +Gonzalo|Gon=za=lo|5 +Granada|Gra=na=da|5 +Grande|Gran=de|5 +guirnaldas|guir=nal=das|5 +gustosos|gus=to=sos|5 +hablara|ha=bla=ra|5 +hablas|ha=blas|5 +habremos|ha=bre=mos|5 +haceros|ha=ce=ros|5 +haciéndose|ha=cién=do=se|5 +hallando|ha=llan=do|5 +hallarme|ha=llar=me|5 +hallándose|ha=llán=do=se|5 +heridos|he=ri=dos|5 +hicieran|hi=cie=ran|5 +hierros|hie=rros|5 +holanda|ho=lan=da|5 +honestas|ho=nes=tas|5 +honestos|ho=nes=tos|5 +hábitos|há=bi=tos|5 +igualarse|igua=lar=se|5 +ilustres|ilus=tres|5 +imaginada|ima=gi=na=da|5 +impertinencias|im=per=ti=nen=cias|5 +impiden|im=pi=den|5 +impresos|im=pre=sos|5 +inclinada|in=cli=na=da|5 +inclinó|in=cli=nó|5 +incomodidades|in=co=mo=di=da=des|5 +indigno|in=dig=no|5 +infantería|in=fan=te=ría|5 +inferir|in=fe=rir|5 +infinidad|in=fi=ni=dad|5 +informado|in=for=ma=do|5 +Information|In=for=ma=tion|5 +ingratitud|in=gra=ti=tud|5 +intitulado|in=ti=tu=la=do|5 +intérprete|in=tér=pre=te|5 +invidiosos|in=vi=dio=sos|5 +jaulas|jau=las|5 +juntarse|jun=tar=se|5 +Júpiter|Jú=pi=ter|5 +lamentable|la=men=ta=ble|5 +lascivo|las=ci=vo|5 +leonado|leo=na=do|5 +levantados|le=van=ta=dos|5 +levante|le=van=te|5 +libertador|li=ber=ta=dor|5 +libras|li=bras|5 +librea|li=brea|5 +ligeras|li=ge=ras|5 +litera|li=te=ra|5 +llamarla|lla=mar=la|5 +llamándose|lla=mán=do=se|5 +llaneza|lla=ne=za|5 +llaves|lla=ves|5 +llevamos|lle=va=mos|5 +madura|ma=du=ra|5 +Mahoma|Maho=ma|5 +majadero|ma=ja=de=ro|5 +majestad|ma=jes=tad|5 +maldad|mal=dad|5 +maldecía|mal=de=cía|5 +maldita|mal=di=ta|5 +malparado|mal=pa=ra=do|5 +mangas|man=gas|5 +maravillado|ma=ra=vi=lla=do|5 +marina|ma=ri=na|5 +marras|ma=rras|5 +martirios|mar=ti=rios|5 +mascar|mas=car|5 +matarme|ma=tar=me|5 +mayorazgo|ma=yo=raz=go|5 +medianamente|me=dia=na=men=te|5 +medium|me=dium|5 +melindre|me=lin=dre|5 +melindrosa|me=lin=dro=sa|5 +melindroso|me=lin=dro=so|5 +mención|men=ción|5 +menguada|men=gua=da|5 +mentirosas|men=ti=ro=sas|5 +mirarle|mi=rar=le|5 +miraron|mi=ra=ron|5 +miserias|mi=se=rias|5 +molidos|mo=li=dos|5 +monstruo|mons=truo|5 +moreno|mo=reno|5 +mortales|mor=ta=les|5 +mostrara|mos=tra=ra|5 +mudando|mu=dan=do|5 +mudanzas|mu=dan=zas|5 +muestran|mues=tran|5 +Málaga|Má=la=ga|5 +Narváez|Nar=váez|5 +necios|ne=cios|5 +nombró|nom=bró|5 +notorio|no=to=rio|5 +novedad|no=ve=dad|5 +novelas|no=ve=las|5 +nueces|nue=ces|5 +Nuestra|Nues=tra|5 +obediencia|obe=dien=cia|5 +obliga|obli=ga|5 +ofreciere|ofre=cie=re|5 +ofrecieron|ofre=cie=ron|5 +ofreciese|ofre=cie=se|5 +ofreciéndole|ofre=cién=do=le|5 +ofrecían|ofre=cían|5 +olvidada|ol=vi=da=da|5 +oraciones|ora=cio=nes|5 +ordenaba|or=de=na=ba|5 +oyentes|oyen=tes|5 +oyéndole|oyén=do=le|5 +pacífica|pa=cí=fi=ca|5 +pagador|pa=ga=dor|5 +pagados|pa=ga=dos|5 +pagano|pa=gano|5 +pagaría|pa=ga=ría|5 +pareciere|pa=re=cie=re|5 +pareciéndoles|pa=re=cién=do=les|5 +parentela|pa=ren=te=la|5 +pariente|pa=rien=te|5 +pasear|pa=sear|5 +pañuelo|pa=ñue=lo|5 +pecadores|pe=ca=do=res|5 +pellizcos|pe=lliz=cos|5 +pendientes|pen=dien=tes|5 +pensarlo|pen=sar=lo|5 +pensaron|pen=sa=ron|5 +perderse|per=der=se|5 +perdidos|per=di=dos|5 +perdono|per=dono|5 +perdóneme|per=dó=ne=me|5 +perezosos|pe=re=zo=sos|5 +perseguida|per=se=gui=da|5 +persuadió|per=sua=dió|5 +persuasión|per=sua=sión|5 +pertrechos|per=tre=chos|5 +pesado|pe=sa=do|5 +picando|pi=can=do|5 +pidieron|pi=die=ron|5 +pintaba|pin=ta=ba|5 +pintadas|pin=ta=das|5 +pisando|pi=san=do|5 +platos|pla=tos|5 +poderlo|po=der=lo|5 +podridas|po=dri=das|5 +podrido|po=dri=do|5 +pongáis|pon=gáis|5 +porfiaba|por=fia=ba|5 +porqué|por=qué|5 +posaderas|po=sade=ras|5 +posibles|po=si=bles|5 +precia|pre=cia|5 +preguntan|pre=gun=tan|5 +preguntare|pre=gun=ta=re|5 +preguntarle|pre=gun=tar=le|5 +premiar|pre=miar|5 +prender|pren=der|5 +presentados|pre=sen=ta=dos|5 +presunción|pre=sun=ción|5 +privilegio|pri=vi=le=gio|5 +procure|pro=cu=re|5 +procuro|pro=cu=ro|5 +progreso|pro=gre=so|5 +prometía|pro=me=tía|5 +propria|pro=pria|5 +propuesto|pro=pues=to|5 +protected|pro=tec=ted|5 +proveídas|pro=veí=das|5 +proveído|pro=veí=do|5 +providencia|pro=vi=den=cia|5 +provincias|pro=vin=cias|5 +prólogo|pró=lo=go|5 +pudieres|pu=die=res|5 +pueblos|pue=blos|5 +pugnaba|pug=na=ba|5 +pupilos|pu=pi=los|5 +puñada|pu=ña=da|5 +pífaro|pí=fa=ro|5 +púlpito|púl=pi=to|5 +quedando|que=dan=do|5 +Quedaron|Que=da=ron|5 +quejaba|que=ja=ba|5 +quijadas|qui=ja=das|5 +Quijano|Qui=jano|5 +quimeras|qui=me=ras|5 +Quintañona|Quin=ta=ño=na|5 +quitarles|qui=tar=les|5 +quitarse|qui=tar=se|5 +quitarte|qui=tar=te|5 +quitándole|qui=tán=do=le|5 +quitármela|qui=tár=me=la|5 +quiénes|quié=nes|5 +randas|ran=das|5 +raíces|raíces|5 +recatada|re=ca=ta=da|5 +recebidos|re=ce=bi=dos|5 +recebía|re=ce=bía|5 +received|re=cei=ved|5 +recelo|re=ce=lo|5 +recogida|re=co=gi=da|5 +recompensa|re=com=pen=sa|5 +recordación|re=cor=da=ción|5 +recuesto|re=cues=to|5 +recámara|re=cá=ma=ra|5 +redoma|re=do=ma|5 +redondez|re=don=dez|5 +regidores|re=gi=do=res|5 +religiosos|re=li=gio=sos|5 +remedie|re=me=die|5 +remedios|re=me=dios|5 +rendida|ren=di=da|5 +renovar|re=no=var|5 +renovaron|re=no=va=ron|5 +renovó|re=no=vó|5 +replicar|re=pli=car|5 +reposado|re=po=sa=do|5 +repostería|re=pos=te=ría|5 +reprehendido|re=prehen=di=do|5 +rescatado|res=ca=ta=do|5 +resplandecientes|res=plan=de=cien=tes|5 +respondelle|res=pon=de=lle|5 +resquicios|res=qui=cios|5 +reventar|re=ven=tar|5 +reírse|reír=se|5 +rogaba|ro=ga=ba|5 +rogado|ro=ga=do|5 +romero|ro=me=ro|5 +ropilla|ro=pi=lla|5 +rústicos|rús=ti=cos|5 +saberlo|sa=ber=lo|5 +sabrosas|sa=bro=sas|5 +sacarán|sa=ca=rán|5 +sacristán|sa=cris=tán|5 +saldré|sal=dré|5 +saldría|sal=dría|5 +saliesen|salie=sen|5 +sandio|san=dio|5 +santas|san=tas|5 +Santiago|San=tia=go|5 +santiguada|san=ti=gua=da|5 +seguros|se=gu=ros|5 +seguían|se=guían|5 +semana|se=ma=na|5 +sentarse|sen=tar=se|5 +sentándose|sen=tán=do=se|5 +sentóse|sen=tó=se|5 +señalando|se=ña=lan=do|5 +Señoría|Se=ño=ría|5 +señorío|se=ño=río|5 +Siempre|Siem=pre|5 +sienta|sien=ta|5 +siguen|si=guen|5 +sinrazones|sin=ra=zo=nes|5 +sinrazón|sin=ra=zón|5 +sintieron|sin=tie=ron|5 +sirviendo|sir=vien=do|5 +sobrenombre|so=bre=nom=bre|5 +socorriese|so=co=rrie=se|5 +sosegadamente|so=se=ga=da=men=te|5 +sosegados|so=se=ga=dos|5 +sosegar|so=se=gar|5 +suaves|sua=ves|5 +subiese|su=bie=se|5 +sucediere|su=ce=die=re|5 +sucedióle|su=ce=dió=le|5 +suplicaba|su=pli=ca=ba|5 +suplir|su=plir|5 +suspenden|sus=pen=den|5 +suspirar|sus=pi=rar|5 +sustentado|sus=ten=ta=do|5 +sutiles|su=ti=les|5 +Sábete|Sá=be=te|5 +tafetán|ta=fe=tán|5 +tapices|ta=pi=ces|5 +teatro|tea=tro|5 +tendidas|ten=di=das|5 +teniéndole|te=nién=do=le|5 +tercio|ter=cio|5 +tesoros|te=so=ros|5 +tinajas|ti=na=jas|5 +Tirante|Ti=ran=te|5 +tocando|to=can=do|5 +tocare|to=ca=re|5 +tocino|to=cino|5 +tocinos|to=ci=nos|5 +tomarla|to=mar=la|5 +torpes|tor=pes|5 +toscano|tos=cano|5 +Tracia|Tra=cia|5 +traducir|tra=du=cir|5 +trances|tran=ces|5 +transformaciones|trans=for=ma=cio=nes|5 +transformación|trans=for=ma=ción|5 +trayendo|tra=yen=do|5 +trazas|tra=zas|5 +tristezas|tris=te=zas|5 +trujesen|tru=je=sen|5 +turbar|tur=bar|5 +tuvimos|tu=vi=mos|5 +universo|uni=ver=so|5 +Valdovinos|Val=do=vi=nos|5 +valerosa|va=le=ro=sa|5 +valían|va=lían|5 +varilla|va=ri=lla|5 +vencedora|ven=ce=do=ra|5 +vendrán|ven=drán|5 +vengamos|ven=ga=mos|5 +vengas|ven=gas|5 +vicario|vi=ca=rio|5 +vicioso|vi=cio=so|5 +vientos|vien=tos|5 +virtuoso|vir=tuo=so|5 +visiones|vi=sio=nes|5 +visita|vi=si=ta|5 +vistió|vis=tió|5 +vocablos|vo=ca=blos|5 +volvería|vol=ve=ría|5 +volváis|vol=váis|5 +volvían|vol=vían|5 +vuelvan|vuel=van|5 +vuelvas|vuel=vas|5 +vámonos|vá=mo=nos|5 +zarandajas|za=ran=da=jas|5 +íbamos|íba=mos|5 +últimamente|úl=ti=ma=men=te|5 +abrasar|abra=sar|4 +abrazado|abra=za=do|4 +abrazando|abra=zan=do|4 +abrazaron|abra=za=ron|4 +abreviar|abre=viar|4 +abriese|abrie=se|4 +abrían|abrían|4 +abundantes|abun=dan=tes|4 +acabadas|aca=ba=das|4 +acabarás|aca=ba=rás|4 +acabasen|aca=ba=sen|4 +Acabóse|Aca=bó=se|4 +Acaeció|Acae=ció|4 +accidentes|ac=ci=den=tes|4 +acentos|acen=tos|4 +acompañaron|acom=pa=ña=ron|4 +acompañase|acom=pa=ña=se|4 +aconsejaba|acon=se=ja=ba|4 +aconsejado|acon=se=ja=do|4 +acontece|acon=te=ce|4 +acreciente|acre=cien=te|4 +acuerde|acuer=de|4 +adelantado|ade=lan=ta=do|4 +aderezo|ade=re=zo|4 +adherentes|adhe=ren=tes|4 +adivinanzas|adi=vi=nan=zas|4 +admira|ad=mi=ra|4 +admirable|ad=mi=ra=ble|4 +Admiráronse|Ad=mi=rá=ron=se|4 +adornadas|ador=na=das|4 +adornado|ador=na=do|4 +advertencia|ad=ver=ten=cia|4 +Advertid|Ad=ver=tid|4 +advertimientos|ad=ver=ti=mien=tos|4 +Advierta|Ad=vier=ta|4 +advirtiendo|ad=vir=tien=do|4 +advirtieron|ad=vir=tie=ron|4 +afirma|afir=ma|4 +afligido|afli=gi=do|4 +afrentados|afren=ta=dos|4 +agradecer|agra=de=cer|4 +agradecidos|agra=de=ci=dos|4 +agradecía|agra=de=cía|4 +aguamanil|agua=ma=nil|4 +aguardaba|aguar=da=ba|4 +aguardaban|aguar=da=ban|4 +aguardase|aguar=da=se|4 +agujeros|agu=je=ros|4 +alabar|ala=bar|4 +alcanzase|al=can=za=se|4 +alcanzo|al=can=zo|4 +aldeanas|al=dea=nas|4 +alegra|ale=gra|4 +alegraba|ale=gra=ba|4 +alegran|ale=gran|4 +alevosía|ale=vo=sía|4 +alfiler|al=fi=ler|4 +Algunos|Al=gu=nos|4 +alhajas|alha=jas|4 +Almodóvar|Al=mo=dó=var|4 +alquiler|al=qui=ler|4 +altura|al=tu=ra|4 +alumbra|alum=bra|4 +amador|ama=dor|4 +amaneciese|ama=ne=cie=se|4 +amargamente|amar=ga=men=te|4 +amenazaba|ame=na=za=ba|4 +anciano|an=ciano|4 +ancianos|an=cia=nos|4 +andarse|an=dar=se|4 +andemos|an=de=mos|4 +anochecía|ano=che=cía|4 +anotación|ano=ta=ción|4 +antaño|an=ta=ño|4 +antifaces|anti=fa=ces|4 +antiguas|an=ti=guas|4 +apaciguó|apa=ci=guó|4 +apariencias|apa=rien=cias|4 +aparta|apar=ta|4 +Apartóse|Apar=tó=se|4 +Apostaré|Apos=ta=ré|4 +aprendí|apren=dí|4 +apretaba|apre=ta=ba|4 +apretó|apre=tó|4 +aprovecha|apro=ve=cha|4 +apócrifo|apó=cri=fo|4 +Arabia|Ara=bia|4 +aragonés|ara=go=nés|4 +Arcadia|Ar=ca=dia|4 +archivos|ar=chi=vos|4 +arenas|are=nas|4 +arguye|ar=gu=ye|4 +arminio|ar=mi=nio|4 +arrancaba|arran=ca=ba|4 +arrojando|arro=jan=do|4 +arrojarse|arro=jar=se|4 +asegurar|ase=gu=rar|4 +aseguro|ase=gu=ro|4 +asentar|asen=tar|4 +asistir|asis=tir|4 +atalaya|ata=la=ya|4 +atañen|ata=ñen|4 +atinar|ati=nar|4 +atrevimientos|atre=vi=mien=tos|4 +avengas|aven=gas|4 +avisase|avi=sa=se|4 +ayudan|ayu=dan|4 +ayudarme|ayu=dar=me|4 +ayudasen|ayu=da=sen|4 +azotar|azo=tar|4 +azotarme|azo=tar=me|4 +azotarse|azo=tar=se|4 +azules|azu=les|4 +añadiduras|aña=di=du=ras|4 +bajeles|ba=je=les|4 +bajeza|ba=je=za|4 +balcones|bal=co=nes|4 +bancos|ban=cos|4 +bandoleros|ban=do=le=ros|4 +banquete|ban=que=te|4 +barata|ba=ra=ta|4 +barras|ba=rras|4 +bautismo|bau=tis=mo|4 +bayeta|ba=ye=ta|4 +bebida|be=bi=da|4 +bebido|be=bi=do|4 +bebiendo|be=bien=do|4 +bellaquerías|be=lla=que=rías|4 +Beltenebros|Bel=te=ne=bros|4 +beneficiado|be=ne=fi=cia=do|4 +beneficios|be=ne=fi=cios|4 +Blanco|Blan=co|4 +blancura|blan=cu=ra|4 +blasfemia|blas=fe=mia|4 +blasfemias|blas=fe=mias|4 +bocados|bo=ca=dos|4 +bogaban|bo=ga=ban|4 +bosques|bos=ques|4 +boyero|bo=ye=ro|4 +bronces|bron=ces|4 +burladores|bur=la=do=res|4 +burlados|bur=la=dos|4 +buscan|bus=can|4 +buscándole|bus=cán=do=le|4 +bárbaros|bár=ba=ros|4 +bástame|bás=ta=me|4 +caballeresca|ca=ba=lle=res=ca|4 +caballerescas|ca=ba=lle=res=cas|4 +cabello|ca=be=llo|4 +calabazadas|ca=la=ba=za=das|4 +calamidades|ca=la=mi=da=des|4 +callado|ca=lla=do|4 +callejuelas|ca=lle=jue=las|4 +campana|cam=pa=na|4 +camuza|ca=mu=za|4 +cannot|can=not|4 +cansada|can=sa=da|4 +cansarse|can=sar=se|4 +capilla|ca=pi=lla|4 +captivo|cap=ti=vo|4 +capítulos|ca=pí=tu=los|4 +carecen|ca=re=cen|4 +cargado|car=ga=do|4 +cargaron|car=ga=ron|4 +carrillos|ca=rri=llos|4 +cartapacios|car=ta=pa=cios|4 +casadas|ca=sa=das|4 +cascabeles|cas=ca=be=les|4 +Cascajo|Cas=ca=jo|4 +castigado|cas=ti=ga=do|4 +Católica|Ca=tó=li=ca|4 +católicas|ca=tó=li=cas|4 +causaba|cau=sa=ba|4 +celosa|ce=lo=sa|4 +cencerros|cen=ce=rros|4 +centinelas|cen=ti=ne=las|4 +centro|cen=tro|4 +cerdas|cer=das|4 +ceremonia|ce=re=mo=nia|4 +cerradas|ce=rra=das|4 +cinchado|cin=cha=do|4 +circunvecinas|cir=cun=ve=ci=nas|4 +circustantes|cir=cus=tan=tes|4 +clarines|cla=ri=nes|4 +clérigos|clé=ri=gos|4 +cobardía|co=bar=día|4 +cobrado|co=bra=do|4 +colada|co=la=da|4 +colchones|col=cho=nes|4 +colgada|col=ga=da|4 +coligió|co=li=gió|4 +collar|co=llar|4 +collection|co=llec=tion|4 +comamos|co=ma=mos|4 +combatientes|com=ba=tien=tes|4 +comedidamente|co=me=di=da=men=te|4 +comenzamos|co=men=za=mos|4 +comienza|co=mien=za|4 +comiese|co=mie=se|4 +compaña|com=pa=ña|4 +compañeras|com=pa=ñe=ras|4 +complacer|com=pla=cer|4 +complexión|com=ple=xión|4 +compliance|com=plian=ce|4 +compone|com=po=ne|4 +comían|co=mían|4 +concavidad|con=ca=vi=dad|4 +concede|con=ce=de|4 +conceder|con=ce=der|4 +concluye|con=clu=ye|4 +condena|con=de=na|4 +confesión|con=fe=sión|4 +confiado|con=fia=do|4 +confirmo|con=fir=mo|4 +confusiones|con=fu=sio=nes|4 +congoja|con=go=ja|4 +conjeturas|con=je=tu=ras|4 +consejero|con=se=je=ro|4 +conserva|con=ser=va|4 +conservan|con=ser=van|4 +consigue|con=si=gue|4 +consintiera|con=sin=tie=ra|4 +consistía|con=sis=tía|4 +contarla|con=tar=la|4 +contentado|con=ten=ta=do|4 +contente|con=ten=te|4 +contentísimo|con=ten=tí=si=mo|4 +contenían|con=te=nían|4 +continencia|con=ti=nen=cia|4 +continuos|con=ti=nuos|4 +Contra|Contra|4 +convidó|con=vi=dó|4 +copying|co=p=ying|4 +cordobán|cor=do=bán|4 +coronada|co=ro=na=da|4 +coronado|co=ro=na=do|4 +corran|co=rran|4 +correrse|co=rrer=se|4 +corresponda|co=rres=pon=da|4 +corridos|co=rri=dos|4 +cortesana|cor=te=sa=na|4 +cortezas|cor=te=zas|4 +cosarios|co=sa=rios|4 +cosmógrafo|cos=mó=gra=fo|4 +Costantinopla|Cos=tan=ti=no=pla|4 +costilla|cos=ti=lla|4 +country|coun=try|4 +cristales|cris=ta=les|4 +crueles|crue=les|4 +cubrirse|cu=brir=se|4 +cuellos|cue=llos|4 +cuernos|cuer=nos|4 +cuervos|cuer=vos|4 +cueste|cues=te|4 +cuidados|cui=da=dos|4 +culpas|cul=pas|4 +cumpliese|cum=plie=se|4 +cumpliría|cum=pli=ría|4 +cumplió|cum=plió|4 +curarle|cu=rar=le|4 +curarse|cu=rar=se|4 +cásese|cá=se=se|4 +cómodo|có=mo=do|4 +debíamos|de=bía=mos|4 +decirlas|de=cir=las|4 +decirles|de=cir=les|4 +decírselo|de=cír=se=lo|4 +defenderme|de=fen=der=me|4 +defendía|de=fen=día|4 +dejaran|de=ja=ran|4 +dejémonos|de=jé=mo=nos|4 +delicada|de=li=ca=da|4 +delicadas|de=li=ca=das|4 +delicados|de=li=ca=dos|4 +delincuente|de=lin=cuen=te|4 +delito|de=li=to|4 +demandas|de=man=das|4 +deparase|de=pa=ra=se|4 +desaforadas|des=afo=ra=das|4 +desatinada|des=ati=na=da|4 +desatinos|des=a=ti=nos|4 +desayunado|des=ayu=na=do|4 +descomedidos|des=co=me=di=dos|4 +descortés|des=cor=tés|4 +describe|des=cri=be|4 +descubiertos|des=cu=bier=tos|4 +descubra|des=cu=bra|4 +descubrimos|des=cu=bri=mos|4 +descubro|des=cu=bro|4 +descubrí|des=cu=brí|4 +Desdichado|Des=di=cha=do|4 +deseamos|de=sea=mos|4 +desearse|de=sear=se|4 +desechar|de=se=char|4 +desengaños|des=en=ga=ños|4 +deseoso|de=seo=so|4 +desesperada|des=es=pe=ra=da|4 +deseáis|de=seáis|4 +desfacedor|des=fa=ce=dor|4 +deshonesta|des=ho=nes=ta|4 +deshonrado|des=hon=ra=do|4 +designio|de=sig=nio|4 +desiguales|de=si=gua=les|4 +desinteresada|de=sin=te=re=sa=da|4 +despacho|des=pa=cho|4 +despedir|des=pe=dir|4 +despensa|des=pen=sa|4 +despertaba|des=per=ta=ba|4 +despertado|des=per=ta=do|4 +despertase|des=per=ta=se|4 +despoblado|des=po=bla=do|4 +despojaron|des=po=ja=ron|4 +desposorio|des=po=so=rio|4 +desviado|des=via=do|4 +detenerle|de=te=ner=le|4 +devota|de=vo=ta|4 +diciplinas|di=ci=pli=nas|4 +diestro|dies=tro|4 +dificultosa|di=fi=cul=to=sa|4 +dijeres|di=je=res|4 +dijiste|di=jis=te|4 +dilatar|di=la=tar|4 +discordia|dis=cor=dia|4 +disparaba|dis=pa=ra=ba|4 +disparatadas|dis=pa=ra=ta=das|4 +displaying|dis=pla=ying|4 +dispone|dis=po=ne|4 +distributed|dis=tri=buted|4 +divide|di=vi=de|4 +dividía|di=vi=día|4 +Divina|Di=vi=na|4 +docientas|do=cien=tas|4 +documentos|do=cu=men=tos|4 +dolencia|do=len=cia|4 +doloridas|do=lo=ri=das|4 +doquiera|do=quie=ra|4 +dorada|do=ra=da|4 +dorado|do=ra=do|4 +duermo|duer=mo|4 +dulcísima|dul=cí=si=ma|4 +dureza|du=re=za|4 +Déjeme|Dé=je=me|4 +déstas|dés=tas|4 +dígalo|dí=ga=lo|4 +díganme|dí=gan=me|4 +dígolo|dí=go=lo|4 +Díjele|Dí=je=le|4 +díjome|dí=jo=me|4 +echarle|echar=le|4 +echará|echa=rá|4 +echase|echa=se|4 +editions|edi=tions|4 +Egipto|Egip=to|4 +ejecutar|eje=cu=tar|4 +ejercitan|ejer=ci=tan|4 +ejercitar|ejer=ci=tar|4 +elección|elec=ción|4 +elegantemente|ele=gante=men=te|4 +Elisabat|Eli=sa=bat|4 +embozo|em=bo=zo|4 +eminente|emi=nen=te|4 +Emperador|Em=pe=ra=dor|4 +enamorar|ena=mo=rar|4 +encaminase|en=ca=mi=na=se|4 +encamine|en=ca=mi=ne|4 +encarecimiento|en=ca=re=ci=mien=to|4 +encendido|en=cen=di=do|4 +encinas|en=ci=nas|4 +encomendarme|en=co=men=dar=me|4 +encubierta|en=cu=bier=ta|4 +encubría|en=cu=bría|4 +endereza|en=de=re=za|4 +enderezando|en=de=re=zan=do|4 +endiablada|en=dia=bla=da|4 +endriagos|en=dria=gos|4 +engañados|en=ga=ña=dos|4 +engañan|en=ga=ñan|4 +engañarme|en=ga=ñar=me|4 +enjalmas|en=jal=mas|4 +enjuto|en=ju=to|4 +enojado|eno=ja=do|4 +enriquecer|en=ri=que=cer|4 +ensalada|en=sa=la=da|4 +ensartando|en=sar=tan=do|4 +entendidos|en=ten=di=dos|4 +entendiese|en=ten=die=se|4 +entendían|en=ten=dían|4 +entiendan|en=tien=dan|4 +entonada|en=to=na=da|4 +entrega|en=tre=ga|4 +entretenerse|en=tre=te=ner=se|4 +enviaron|en=via=ron|4 +equivalente|equi=va=len=te|4 +erudición|eru=di=ción|4 +escarlata|es=car=la=ta|4 +esconderse|es=con=der=se|4 +escondidas|es=con=di=das|4 +escondidos|es=con=di=dos|4 +escote|es=co=te|4 +escriba|es=cri=ba|4 +escriban|es=cri=ban|4 +escriben|es=cri=ben|4 +escribiendo|es=cri=bien=do|4 +escribiese|es=cri=bie=se|4 +escribí|es=cri=bí|4 +escuadras|es=cua=dras|4 +Escucha|Es=cu=cha|4 +escuderiles|es=cu=de=ri=les|4 +escuelas|es=cue=las|4 +esotro|eso=tro|4 +espanta|es=pan=ta|4 +esparto|es=par=to|4 +especial|es=pe=cial|4 +esperado|es=pe=ra=do|4 +esperamos|es=pe=ra=mos|4 +espere|es=pe=re|4 +espinazo|es=pi=na=zo|4 +esquife|es=qui=fe|4 +estemos|es=te=mos|4 +estera|es=te=ra|4 +estimadas|es=ti=ma=das|4 +estimado|es=ti=ma=do|4 +estimo|es=ti=mo|4 +estoque|es=to=que|4 +estorbarlo|es=tor=bar=lo|4 +estorbase|es=tor=ba=se|4 +estorbó|es=tor=bó|4 +estotro|es=to=tro|4 +estratagemas|es=tra=ta=ge=mas|4 +Estraño|Es=tra=ño|4 +estremada|es=tre=ma=da|4 +estudiado|es=tu=dia=do|4 +estupenda|es=tu=pen=da|4 +etcétera|etcé=te=ra|4 +excede|ex=ce=de|4 +faldellín|fal=de=llín|4 +falsos|fal=sos|4 +faltaron|fal=ta=ron|4 +favorece|fa=vo=re=ce|4 +favores|fa=vo=res|4 +fementido|fe=men=ti=do|4 +fermosas|fer=mo=sas|4 +ficción|fic=ción|4 +fieros|fie=ros|4 +filósofo|fi=ló=so=fo|4 +filósofos|fi=ló=so=fos|4 +fingió|fin=gió|4 +fingía|fin=gía|4 +finísima|fi=ní=si=ma|4 +floresta|flo=res=ta|4 +following|fo=llo=wing|4 +formado|for=ma=do|4 +format|for=mat|4 +forzar|for=zar|4 +Francisco|Fran=cis=co|4 +francés|fran=cés|4 +freely|free=ly|4 +frutos|fru=tos|4 +Fueron|Fue=ron|4 +fáciles|fá=ci=les|4 +gallardos|ga=llar=dos|4 +ganase|ga=na=se|4 +General|Ge=ne=ral|4 +gobernado|go=ber=na=do|4 +golpear|gol=pear|4 +Gracias|Gra=cias|4 +grados|gra=dos|4 +grandísimos|gran=dí=si=mos|4 +granos|gra=nos|4 +griega|grie=ga|4 +griegos|grie=gos|4 +grillos|gri=llos|4 +guardarme|guar=dar=me|4 +guardase|guar=da=se|4 +guardó|guar=dó|4 +guerrero|gue=rre=ro|4 +guiando|guian=do|4 +guijarro|gui=ja=rro|4 +gustaba|gus=ta=ba|4 +gustar|gus=tar|4 +gustosa|gus=to=sa|4 +gustáis|gus=táis|4 +GUTENBERG|GU=TEN=BERG|4 +Gutiérrez|Gu=tié=rrez|4 +haberles|ha=ber=les|4 +haberlo|ha=ber=lo|4 +habitación|ha=bi=ta=ción|4 +habiéndola|ha=bién=do=la|4 +habiéndome|ha=bién=do=me|4 +hablarla|ha=blar=la|4 +hablarle|ha=blar=le|4 +habrían|ha=brían|4 +hagamos|ha=ga=mos|4 +hallados|ha=lla=dos|4 +hallares|ha=lla=res|4 +hallarla|ha=llar=la|4 +hallaros|ha=lla=ros|4 +hallaréis|ha=lla=réis|4 +hallastes|ha=llas=tes|4 +hermanas|her=ma=nas|4 +hermosuras|her=mo=su=ras|4 +hermosísimas|her=mo=sí=si=mas|4 +hiciste|hi=cis=te|4 +Hidalgo|Hi=dal=go|4 +hileras|hi=le=ras|4 +holder|hol=der|4 +honrar|hon=rar|4 +honras|hon=ras|4 +Horacio|Ho=ra=cio|4 +horrendo|ho=rren=do|4 +hubiéredes|hu=bié=re=des|4 +huelen|hue=len|4 +humanos|hu=ma=nos|4 +huéspeda|huéspe=da|4 +ijadas|ija=das|4 +imaginados|ima=gi=na=dos|4 +impedido|im=pe=di=do|4 +impedimento|im=pe=di=men=to|4 +impida|im=pi=da|4 +importunidades|im=por=tu=ni=da=des|4 +impresor|im=pre=sor|4 +inauditas|inau=di=tas|4 +incomodidad|in=co=mo=di=dad|4 +individual|in=di=vi=dual|4 +infiere|in=fie=re|4 +Ingenioso|In=ge=nio=so|4 +injuria|in=ju=ria|4 +inmortalidad|in=mor=ta=li=dad|4 +insolencia|in=so=len=cia|4 +insolencias|in=so=len=cias|4 +insolentes|in=so=len=tes|4 +intentar|in=ten=tar|4 +interese|in=te=re=se|4 +inumerables|inu=me=ra=bles|4 +inventor|in=ven=tor|4 +jardines|jar=di=nes|4 +juzgada|juz=ga=da|4 +lamentaciones|la=men=ta=cio=nes|4 +lanzada|lan=za=da|4 +largamente|lar=ga=men=te|4 +lascivos|las=ci=vos|4 +latina|la=ti=na|4 +lavatorio|la=va=to=rio|4 +leales|lea=les|4 +levantaban|le=van=ta=ban|4 +levantarle|le=van=tar=le|4 +leyeren|le=ye=ren|4 +libranza|li=bran=za|4 +librar|li=brar|4 +librase|li=bra=se|4 +limpiar|lim=piar|4 +lindezas|lin=de=zas|4 +lisura|li=su=ra|4 +llamase|lla=ma=se|4 +Llegando|Lle=gan=do|4 +llevados|lle=va=dos|4 +llevarme|lle=var=me|4 +llevará|lle=va=rá|4 +llevaré|lle=va=ré|4 +llevándole|lle=ván=do=le|4 +logrado|lo=gra=do|4 +luenga|luen=ga|4 +lunares|lu=na=res|4 +lícita|lí=ci=ta|4 +líquidos|lí=qui=dos|4 +maduro|ma=du=ro|4 +Madásima|Ma=dá=si=ma|4 +magnífico|mag=ní=fi=co|4 +maguer|ma=guer|4 +Maguncia|Ma=gun=cia|4 +majada|ma=ja=da|4 +maldiciendo|mal=di=cien=do|4 +maliciosa|ma=li=cio=sa|4 +malicioso|ma=li=cio=so|4 +maligno|ma=lig=no|4 +Mallorca|Ma=llor=ca|4 +manada|ma=na=da|4 +mandare|man=da=re|4 +mansedumbre|man=s=e=dum=bre|4 +manteamiento|man=tea=mien=to|4 +maravedí|ma=ra=ve=dí|4 +maravillosamente|ma=ra=vi=llo=sa=men=te|4 +maridos|ma=ri=dos|4 +matando|ma=tan=do|4 +medianera|me=dia=ne=ra|4 +medidas|me=di=das|4 +mejilla|me=ji=lla|4 +mejillas|me=ji=llas|4 +mejoría|me=jo=ría|4 +memorables|me=mo=ra=bles|4 +mengua|men=gua|4 +menores|me=no=res|4 +menosprecio|me=nos=pre=cio|4 +mentirosa|men=ti=ro=sa|4 +mentís|men=tís|4 +menuda|me=nu=da|4 +menudas|me=nu=das|4 +mercancía|mer=can=cía|4 +merezcan|me=rez=can|4 +meterse|me=ter=se|4 +mezcladas|mez=cla=das|4 +mezclando|mez=clan=do|4 +mezclar|mez=clar|4 +mienten|mien=ten|4 +millares|mi=lla=res|4 +millón|mi=llón|4 +ministro|mi=nis=tro|4 +modernos|mo=der=nos|4 +mojicones|mo=ji=co=nes|4 +Montiel|Mon=tiel|4 +montón|mon=tón|4 +morada|mo=ra=da|4 +moscas|mos=cas|4 +mostraban|mos=tra=ban|4 +mostrando|mos=tran=do|4 +mostrenco|mos=tren=co|4 +mostráis|mos=tráis|4 +Muchos|Mu=chos|4 +mueren|mue=ren|4 +muertes|muer=tes|4 +muestren|mues=tren|4 +mundos|mun=dos|4 +murallas|mu=ra=llas|4 +Murcia|Mur=cia|4 +murmurar|mur=mu=rar|4 +mármoles|már=mo=les|4 +mísero|mí=se=ro|4 +músicos|mú=si=cos|4 +nacieron|na=cie=ron|4 +naipes|nai=pes|4 +navaja|na=va=ja|4 +necesitados|ne=ce=si=ta=dos|4 +negociante|ne=go=cian=te|4 +negociar|ne=go=ciar|4 +Neptuno|Nep=tuno|4 +nietos|nie=tos|4 +Ninguna|Nin=gu=na|4 +nobles|no=bles|4 +nombraba|nom=bra=ba|4 +norabuena|no=ra=bue=na|4 +obispos|obis=pos|4 +obligar|obli=gar|4 +ocioso|ocio=so|4 +ocupación|ocu=pa=ción|4 +online|on=li=ne|4 +opiniones|opi=nio=nes|4 +ordenare|or=de=na=re|4 +ordenaron|or=de=na=ron|4 +ovillo|ovi=llo|4 +pacíficamente|pa=cí=fi=ca=men=te|4 +pagaba|pa=ga=ba|4 +pagase|pa=ga=se|4 +paletas|pa=le=tas|4 +Palmerín|Pal=me=rín|4 +paraban|pa=ra=ban|4 +Parapilla|Pa=ra=pi=lla|4 +pararon|pa=ra=ron|4 +parasismo|pa=ra=sis=mo|4 +pareciéndome|pa=re=cién=do=me|4 +Paredes|Pa=re=des|4 +parezco|pa=rez=co|4 +partieron|par=tie=ron|4 +paréceme|pa=ré=ce=me|4 +pasajero|pa=sa=je=ro|4 +pasaremos|pa=sa=re=mos|4 +pasearse|pa=sear=se|4 +paseándose|pa=seán=do=se|4 +pasión|pa=sión|4 +patente|pa=ten=te|4 +paveses|pa=ve=ses|4 +pañizuelo|pa=ñi=zue=lo|4 +pedirme|pe=dir=me|4 +pedrada|pe=dra=da|4 +pelean|pe=lean|4 +peleando|pe=lean=do|4 +pellizcaron|pe=lliz=ca=ron|4 +pensamos|pen=sa=mos|4 +pensábamos|pen=sá=ba=mos|4 +Pensáis|Pen=sáis|4 +Pentapolín|Pen=ta=po=lín|4 +pequeñas|pe=que=ñas|4 +peregrina|pe=re=gri=na|4 +permitía|per=mi=tía|4 +perpetua|per=pe=tua|4 +perseverar|per=se=ve=rar|4 +persigue|per=si=gue|4 +person|per=son|4 +persuasiones|per=sua=sio=nes|4 +pescador|pes=ca=dor|4 +petición|pe=ti=ción|4 +phrase|ph=ra=se|4 +Pidiéronle|Pi=dié=ron=le|4 +piedad|pie=dad|4 +pierden|pier=den|4 +pintados|pin=ta=dos|4 +pintando|pin=tan=do|4 +pintura|pin=tu=ra|4 +pirámide|pi=rá=mi=de|4 +plantas|plan=tas|4 +pliegos|plie=gos|4 +podenco|po=den=co|4 +poderosas|po=de=ro=sas|4 +poderse|po=der=se|4 +podáis|po=dáis|4 +Poesía|Poesía|4 +polvareda|pol=va=re=da|4 +pondrás|pon=drás|4 +ponerla|po=ner=la|4 +pongas|pon=gas|4 +poniéndome|po=nién=do=me|4 +portal|por=tal|4 +posted|pos=ted|4 +postura|pos=tu=ra|4 +pradecillo|pra=de=ci=llo|4 +precioso|pre=cio=so|4 +precisas|pre=ci=sas|4 +preguntaban|pre=gun=ta=ban|4 +preguntaron|pre=gun=ta=ron|4 +pregunte|pre=gun=te|4 +pregón|pre=gón|4 +premios|pre=mios|4 +presentar|pre=sen=tar|4 +presta|pres=ta|4 +presté|pres=té|4 +prevención|pre=ven=ción|4 +Primero|Pri=me=ro|4 +probado|pro=ba=do|4 +procurado|pro=cu=ra=do|4 +procuran|pro=cu=ran|4 +proezas|proe=zas|4 +profecía|pro=fe=cía|4 +profundidad|pro=fun=di=dad|4 +PROJECT|PRO=JECT|4 +providing|pro=vi=ding|4 +pruebas|prue=bas|4 +pródigo|pró=di=go|4 +próspero|prós=pe=ro|4 +prósperos|prós=pe=ros|4 +pudieras|pu=die=ras|4 +Puestos|Pues=tos|4 +pugnando|pug=nan=do|4 +puntual|pun=tual|4 +purísimo|pu=rí=si=mo|4 +pusimos|pu=si=mos|4 +Pusiéronle|Pu=sié=ron=le|4 +públicas|pú=bli=cas|4 +quedarme|que=dar=me|4 +quedaría|que=da=ría|4 +quedasen|que=da=sen|4 +quedéis|que=déis|4 +quemado|que=ma=do|4 +queremos|que=re=mos|4 +querrá|que=rrá|4 +quicios|qui=cios|4 +quiebra|quie=bra|4 +Quijana|Qui=ja=na|4 +Quijotes|Qui=jo=tes|4 +quilates|qui=la=tes|4 +quitalle|qui=ta=lle|4 +quitara|qui=ta=ra|4 +quitase|qui=ta=se|4 +Quiñones|Qui=ño=nes|4 +rancor|ran=cor|4 +rebaño|re=ba=ño|4 +rebuznaron|re=buz=na=ron|4 +recebirla|re=ce=bir=la|4 +reciba|re=ci=ba|4 +recitantes|re=ci=tan=tes|4 +recoger|re=co=ger|4 +recogieron|re=co=gie=ron|4 +recogiese|re=co=gie=se|4 +refriega|re=frie=ga|4 +regalados|re=ga=la=dos|4 +regalar|re=ga=lar|4 +regiones|re=gio=nes|4 +regoldar|re=gol=dar|4 +relinchos|re=lin=chos|4 +rematado|re=ma=ta=do|4 +remisión|re=mi=sión|4 +rendir|ren=dir|4 +renegados|re=ne=ga=dos|4 +repente|re=pen=te|4 +repliques|re=pli=ques|4 +representaba|re=pre=sen=ta=ba|4 +representaban|re=pre=sen=ta=ban|4 +representadas|re=pre=sen=ta=das|4 +representantes|re=pre=sen=tan=tes|4 +requirements|re=qui=re=men=ts|4 +residen|re=si=den|4 +resistir|re=sis=tir|4 +resplandeciente|res=plan=de=cien=te|4 +resplandor|res=plan=dor|4 +respondiendo|res=pon=dien=do|4 +respondían|res=pon=dían|4 +ribera|ri=be=ra|4 +ricamente|ri=ca=men=te|4 +riendo|rien=do|4 +rindieron|rin=die=ron|4 +rindiese|rin=die=se|4 +riquísimas|ri=quí=si=mas|4 +riquísimo|ri=quí=si=mo|4 +robada|ro=ba=da|4 +robador|ro=ba=dor|4 +rocino|ro=cino|4 +rodeados|ro=dea=dos|4 +rodear|ro=dear|4 +rollizo|ro=lli=zo|4 +Romana|Ro=ma=na|4 +romances|ro=man=ces|4 +rompiendo|rom=pien=do|4 +ruines|rui=nes|4 +réplica|répli=ca|4 +rétulo|ré=tu=lo|4 +rústicas|rús=ti=cas|4 +sabida|sa=bi=da|4 +sabidor|sa=bi=dor|4 +sabidores|sa=bi=do=res|4 +sabroso|sa=bro=so|4 +sabría|sa=bría|4 +sacadas|saca=das|4 +sacara|sa=ca=ra|4 +sacasen|saca=sen|4 +Sacripante|Sa=cri=pan=te|4 +sacudiendo|sa=cu=dien=do|4 +salarios|sa=la=rios|4 +salgamos|sal=ga=mos|4 +salidas|sali=das|4 +saliere|salie=re|4 +salteadores|sal=tea=do=res|4 +saltos|sal=tos|4 +saludó|salu=dó|4 +salvajes|sal=va=jes|4 +satisfaga|sa=tis=fa=ga|4 +satisfecha|sa=tis=fe=cha|4 +seguirle|se=guir=le|4 +semblante|sem=blan=te|4 +sentase|sen=ta=se|4 +Sentóse|Sen=tó=se|4 +sepamos|se=pa=mos|4 +Sepamos|Se=pa=mos|4 +sepulcro|se=pul=cro|4 +sepulturas|se=pul=tu=ras|4 +sereno|se=reno|4 +servirla|ser=vir=la|4 +servirá|ser=vi=rá|4 +seréis|se=réis|4 +señoríos|se=ño=ríos|4 +siestas|sies=tas|4 +siguientes|si=guien=tes|4 +siguiéndole|si=guién=do=le|4 +sillas|si=llas|4 +sillón|si=llón|4 +sintiendo|sin=tien=do|4 +sobresaltos|so=bre=sal=tos|4 +Sobrino|So=brino|4 +socorrerle|so=co=rrer=le|4 +soguilla|so=gui=lla|4 +sonaron|so=na=ron|4 +soplar|so=plar|4 +sospiros|sos=pi=ros|4 +soñadas|so=ña=das|4 +states|sta=tes|4 +status|sta=tus|4 +suavidad|sua=vi=dad|4 +subjeto|sub=je=to|4 +sucede|su=ce=de|4 +sucedidos|su=ce=di=dos|4 +sueños|sue=ños|4 +suficiente|su=fi=cien=te|4 +sufrido|su=fri=do|4 +supieron|su=pie=ron|4 +suplicó|su=pli=có|4 +support|su=pport|4 +sábana|sá=ba=na|4 +tachas|ta=chas|4 +tahalí|taha=lí|4 +tamaña|ta=ma=ña|4 +tardaba|tar=da=ba|4 +tardado|tar=da=do|4 +techado|te=cha=do|4 +temblando|tem=blan=do|4 +temiera|te=mie=ra|4 +temores|te=mo=res|4 +temple|tem=ple|4 +tendremos|ten=dre=mos|4 +teníamos|te=nía=mos|4 +Tienes|Tie=nes|4 +tiernamente|tier=na=men=te|4 +tiernas|tier=nas|4 +tintero|tin=te=ro|4 +tirones|ti=ro=nes|4 +toallas|toa=llas|4 +tocarle|to=car=le|4 +tocase|to=ca=se|4 +toledano|to=le=dano|4 +tomaré|to=ma=ré|4 +tomasen|to=ma=sen|4 +tomándola|to=mán=do=la|4 +toméis|to=méis|4 +Tomóle|To=mó=le|4 +topaba|to=pa=ba|4 +topado|to=pa=do|4 +topase|to=pa=se|4 +torcer|tor=cer|4 +torcido|tor=ci=do|4 +Tordesillas|Tor=de=si=llas|4 +tradición|tra=di=ción|4 +traeré|trae=ré|4 +traigáis|trai=gáis|4 +transformaron|trans=for=ma=ron|4 +transparente|trans=pa=ren=te|4 +traspasado|tras=pa=sa=do|4 +trasudando|tra=su=dan=do|4 +tratamos|tra=ta=mos|4 +tratasen|tra=ta=sen|4 +tratáredes|tra=tá=re=des|4 +traéis|traéis|4 +tropezando|tro=pe=zan=do|4 +turbación|tur=ba=ción|4 +turbes|tur=bes|4 +turbio|tur=bio|4 +Turpín|Tur=pín|4 +turquesca|tur=ques=ca|4 +tálamo|tá=la=mo|4 +Ténganse|Tén=gan=se|4 +umbrales|um=bra=les|4 +Urganda|Ur=gan=da|4 +valenciano|va=len=ciano|4 +valentísimo|va=len=tí=si=mo|4 +valerosas|va=le=ro=sas|4 +vaquilla|va=qui=lla|4 +varias|va=rias|4 +varones|va=ro=nes|4 +vehemencia|vehe=men=cia|4 +velando|ve=lan=do|4 +vengada|ven=ga=da|4 +vengarme|ven=gar=me|4 +venidero|ve=ni=de=ro|4 +verjas|ver=jas|4 +vestiglo|ves=ti=glo|4 +vestirse|ves=tir=se|4 +viajes|via=jes|4 +Viedma|Vied=ma|4 +viejas|vie=jas|4 +Vireno|Vi=reno|4 +vislumbres|vis=lum=bres|4 +vistos|vis=tos|4 +vituperio|vi=tu=pe=rio|4 +volunteers|vo=lun=teers|4 +volvernos|vol=ver=nos|4 +volviera|vol=vie=ra|4 +volvióse|vol=vió=se|4 +volvámonos|vol=vá=mo=nos|4 +vosotras|vo=so=tras|4 +Vuelve|Vuel=ve|4 +vuelves|vuel=ves|4 +Vuestras|Vues=tras|4 +Vuestro|Vues=tro|4 +website|web=si=te|4 +within|wi=thin|4 +zagales|za=ga=les|4 +átomos|áto=mos|4 +ímpetu|ím=pe=tu|4 +últimas|úl=ti=mas|4 +abejas|abe=jas|3 +Abindarráez|Abin=da=rráez|3 +aborrece|abo=rre=ce|3 +aborrecimiento|abo=rre=ci=mien=to|3 +aborrezco|abo=rrez=co|3 +abrasa|abra=sa|3 +Abrazóle|Abra=zó=le|3 +abriera|abrie=ra|3 +Abrióle|Abrió=le|3 +absorto|ab=sor=to|3 +abundoso|abun=do=so|3 +acababan|aca=ba=ban|3 +acabamiento|aca=ba=mien=to|3 +acabara|aca=ba=ra|3 +Acabaron|Aca=ba=ron|3 +acabarse|aca=bar=se|3 +acabará|aca=ba=rá|3 +acabaré|aca=ba=ré|3 +acaben|aca=ben|3 +acatamiento|aca=ta=mien=to|3 +acercando|acer=can=do|3 +acertaba|acer=ta=ba|3 +acertada|acer=ta=da|3 +aciago|acia=go|3 +acidente|aci=den=te|3 +acierta|acier=ta|3 +acogida|aco=gi=da|3 +acogió|aco=gió|3 +acometa|aco=me=ta|3 +acometen|aco=me=ten|3 +acometido|aco=me=ti=do|3 +acometimientos|aco=me=ti=mien=tos|3 +acometía|aco=me=tía|3 +acomodaba|aco=mo=da=ba|3 +acomodándose|aco=mo=dán=do=se|3 +acompaña|acom=pa=ña|3 +acompañaba|acom=pa=ña=ba|3 +acompañamos|acom=pa=ña=mos|3 +acompañan|acom=pa=ñan|3 +acompañando|acom=pa=ñan=do|3 +aconsejar|acon=se=jar|3 +aconsejarte|acon=se=jar=te|3 +acordamos|acor=da=mos|3 +acordar|acor=dar|3 +acordarme|acor=dar=me|3 +acordaron|acor=da=ron|3 +acordándose|acor=dán=do=se|3 +acortar|acor=tar|3 +acostó|acos=tó|3 +acotaciones|aco=ta=cio=nes|3 +acrecentó|acre=cen=tó|3 +acuerdan|acuer=dan|3 +aderezados|ade=re=za=dos|3 +adevinaba|ade=vi=na=ba|3 +admiraban|ad=mi=ra=ban|3 +Admirada|Ad=mi=ra=da|3 +admiran|ad=mi=ran|3 +admiren|ad=mi=ren|3 +admirábanse|ad=mi=rá=ban=se|3 +admirábase|ad=mi=rá=ba=se|3 +admitir|ad=mi=tir|3 +adornar|ador=nar|3 +adornos|ador=nos|3 +adversos|ad=ver=sos|3 +advertidos|ad=ver=ti=dos|3 +afable|afa=ble|3 +afectación|afec=ta=ción|3 +afincamiento|afin=ca=mien=to|3 +afirmaba|afir=ma=ba|3 +afirmo|afir=mo|3 +afirmó|afir=mó|3 +afortunada|afor=tu=na=da|3 +afrentar|afren=tar|3 +afrentas|afren=tas|3 +Africa|Afri=ca|3 +afuera|afue=ra|3 +agobiado|ago=bia=do|3 +agorero|ago=re=ro|3 +agosto|agos=to|3 +agradare|agra=da=re|3 +agradecieron|agra=de=cie=ron|3 +aguardó|aguar=dó|3 +agudezas|agu=de=zas|3 +agudos|agu=dos|3 +Aguilar|Agui=lar|3 +aguileña|agui=le=ña|3 +agüela|agüe=la|3 +Agüero|Agüe=ro|3 +ahorquen|ahor=quen|3 +alabados|ala=ba=dos|3 +alabastro|ala=bas=tro|3 +alargaba|alar=ga=ba|3 +albaceas|al=ba=ceas|3 +alborotó|al=bo=ro=tó|3 +Alborotóse|Al=bo=ro=tó=se|3 +alborozado|al=bo=ro=za=do|3 +Albraca|Al=bra=ca|3 +alcabalas|al=ca=ba=las|3 +alcances|al=can=ces|3 +alcancé|al=can=cé|3 +alcanzarle|al=can=zar=le|3 +alcornoques|al=cor=no=ques|3 +alférez|al=fé=rez|3 +alguacil|al=gua=cil|3 +alheña|alhe=ña|3 +alhombra|alhom=bra|3 +aliviado|ali=via=do|3 +aljaba|al=ja=ba|3 +almalafa|al=ma=la=fa|3 +almenas|al=me=nas|3 +almohadas|al=moha=das|3 +alojado|alo=ja=do|3 +alojar|alo=jar|3 +altanería|al=ta=ne=ría|3 +alzado|al=za=do|3 +alzarse|al=zar=se|3 +alzándose|al=zán=do=se|3 +alárabes|alá=ra=bes|3 +amable|ama=ble|3 +amanece|ama=ne=ce|3 +amanecerá|ama=ne=ce=rá|3 +amaneció|ama=ne=ció|3 +Amaneció|Ama=ne=ció|3 +amarillas|ama=ri=llas|3 +amarillez|ama=ri=llez|3 +amenazando|ame=na=zan=do|3 +amojamado|amo=ja=ma=do|3 +amorosamente|amo=ro=sa=men=te|3 +amparar|am=pa=rar|3 +ampare|am=pa=re|3 +Andaba|An=da=ba|3 +andantesca|an=dan=tes=ca|3 +andarme|an=dar=me|3 +Andrada|An=dra=da|3 +anduve|an=du=ve|3 +anduviera|an=du=vie=ra|3 +anduviese|an=du=vie=se|3 +andáis|an=dáis|3 +animoso|ani=mo=so|3 +aniquilar|ani=qui=lar|3 From 83315b61795e216d9e87c77dd2ee67c1b1cb416b Mon Sep 17 00:00:00 2001 From: Daniel Chelling Date: Tue, 27 Jan 2026 06:29:15 -0800 Subject: [PATCH 03/13] perf: optimize large EPUB indexing from O(n^2) to O(n) (#458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Optimizes EPUB metadata indexing for large books (2000+ chapters) from ~30 minutes to ~50 seconds by replacing O(n²) algorithms with O(n log n) hash-indexed lookups. Fixes #134 ## Problem Three phases had O(n²) complexity due to nested loops: | Phase | Operation | Before (2768 chapters) | |-------|-----------|------------------------| | OPF Pass | For each spine ref, scan all manifest items | ~25 min | | TOC Pass | For each TOC entry, scan all spine items | ~5 min | | buildBookBin | For each spine item, scan ZIP central directory | ~8.4 min | Total: **~30+ minutes** for first-time indexing of large EPUBs. ## Solution Replace linear scans with sorted hash indexes + binary search: - **OPF Pass**: Build `{hash(id), len, offset}` index from manifest, binary search for each spine ref - **TOC Pass**: Build `{hash(href), len, spineIndex}` index from spine, binary search for each TOC entry - **buildBookBin**: New `ZipFile::fillUncompressedSizes()` API - single ZIP central directory scan with batch hash matching All indexes use FNV-1a hashing with length as secondary key to minimize collisions. Indexes are freed immediately after each phase. ## Results **Shadow Slave EPUB (2768 chapters):** | Phase | Before | After | Speedup | |-------|--------|-------|---------| | OPF pass | ~25 min | 10.8 sec | ~140x | | TOC pass | ~5 min | 4.7 sec | ~60x | | buildBookBin | 506 sec | 34.6 sec | ~15x | | **Total** | **~30+ min** | **~50 sec** | **~36x** | **Normal EPUB (87 chapters):** 1.7 sec - no regression. ## Memory Peak temporary memory during indexing: - OPF index: ~33KB (2770 items × 12 bytes) - TOC index: ~33KB (2768 items × 12 bytes) - ZIP batch: ~44KB (targets + sizes arrays) All indexes cleared immediately after each phase. No OOM risk on ESP32-C3. ## Note on Threshold All optimizations are gated by `LARGE_SPINE_THRESHOLD = 400` to preserve existing behavior for small books. However, the algorithms work correctly for any book size and are faster even for small books: | Book Size | Old O(n²) | New O(n log n) | Improvement | |-----------|-----------|----------------|-------------| | 10 ch | 100 ops | 50 ops | 2x | | 100 ch | 10K ops | 800 ops | 12x | | 400 ch | 160K ops | 4K ops | 40x | If preferred, the threshold could be removed to use the optimized path universally. ## Testing - [x] Shadow Slave (2768 chapters): 50s first-time indexing, loads and navigates correctly - [x] Normal book (87 chapters): 1.7s indexing, no regression - [x] Build passes - [x] clang-format passes ## Files Changed - `lib/Epub/Epub/parsers/ContentOpfParser.h/.cpp` - OPF manifest index - `lib/Epub/Epub/BookMetadataCache.h/.cpp` - TOC index + batch size lookup - `lib/ZipFile/ZipFile.h/.cpp` - New `fillUncompressedSizes()` API - `lib/Epub/Epub.cpp` - Timing logs
Algorithm Details (click to expand) ### Phase 1: OPF Pass - Manifest to Spine Lookup **Problem**: Each `` in spine must find matching `` in manifest. ``` OLD: For each of 2768 spine refs, scan all 2770 manifest items = 7.6M string comparisons NEW: While parsing manifest, build index: { hash("ch001"), len=5, file_offset=120 } Sort index, then binary search for each spine ref: 2768 × log₂(2770) ≈ 2768 × 11 = 30K comparisons ``` ### Phase 2: TOC Pass - TOC Entry to Spine Index Lookup **Problem**: Each TOC entry with `href="chapter0001.xhtml"` must find its spine index. ``` OLD: For each of 2768 TOC entries, scan all 2768 spine entries = 7.6M string comparisons NEW: At beginTocPass(), read spine once and build index: { hash("OEBPS/chapter0001.xhtml"), len=25, spineIndex=0 } Sort index, binary search for each TOC entry: 2768 × log₂(2768) ≈ 30K comparisons Clear index at endTocPass() to free memory. ``` ### Phase 3: buildBookBin - ZIP Size Lookup **Problem**: Need uncompressed file size for each spine item (for reading progress). Sizes are in ZIP central directory. ``` OLD: For each of 2768 spine items, scan ZIP central directory (2773 entries) = 7.6M filename reads + string comparisons Time: 506 seconds NEW: Step 1: Build targets from spine { hash("OEBPS/chapter0001.xhtml"), len=25, index=0 } Sort by (hash, len) Step 2: Single pass through ZIP central directory For each entry: - Compute hash ON THE FLY (no string allocation) - Binary search targets - If match: sizes[target.index] = uncompressedSize Step 3: Use sizes array directly (O(1) per spine item) Total: 2773 entries × log₂(2768) ≈ 33K comparisons Time: 35 seconds ``` ### Why Hash + Length? Using 64-bit FNV-1a hash + string length as a composite key: - Collision probability: ~1 in 2⁶⁴ × typical_path_lengths - No string storage needed in index (just 12-16 bytes per entry) - Integer comparisons are faster than string comparisons - Verification on match handles the rare collision case
--- _AI-assisted development. All changes tested on hardware._ --- lib/Epub/Epub.cpp | 9 ++ lib/Epub/Epub/BookMetadataCache.cpp | 165 ++++++++++++++++----- lib/Epub/Epub/BookMetadataCache.h | 23 +++ lib/Epub/Epub/parsers/ContentOpfParser.cpp | 74 +++++++-- lib/Epub/Epub/parsers/ContentOpfParser.h | 24 +++ lib/ZipFile/ZipFile.cpp | 134 +++++++++++++++-- lib/ZipFile/ZipFile.h | 26 ++++ 7 files changed, 396 insertions(+), 59 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 33f920b4..7559e3b3 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -226,6 +226,8 @@ bool Epub::load(const bool buildIfMissing) { Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis()); setupCacheDir(); + const uint32_t indexingStart = millis(); + // Begin building cache - stream entries to disk immediately if (!bookMetadataCache->beginWrite()) { Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis()); @@ -233,6 +235,7 @@ bool Epub::load(const bool buildIfMissing) { } // OPF Pass + const uint32_t opfStart = millis(); BookMetadataCache::BookMetadata bookMetadata; if (!bookMetadataCache->beginContentOpfPass()) { Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis()); @@ -246,8 +249,10 @@ bool Epub::load(const bool buildIfMissing) { Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis()); return false; } + Serial.printf("[%lu] [EBP] OPF pass completed in %lu ms\n", millis(), millis() - opfStart); // TOC Pass - try EPUB 3 nav first, fall back to NCX + const uint32_t tocStart = millis(); if (!bookMetadataCache->beginTocPass()) { Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis()); return false; @@ -276,6 +281,7 @@ bool Epub::load(const bool buildIfMissing) { Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis()); return false; } + Serial.printf("[%lu] [EBP] TOC pass completed in %lu ms\n", millis(), millis() - tocStart); // Close the cache files if (!bookMetadataCache->endWrite()) { @@ -284,10 +290,13 @@ bool Epub::load(const bool buildIfMissing) { } // Build final book.bin + const uint32_t buildStart = millis(); if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) { Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis()); return false; } + Serial.printf("[%lu] [EBP] buildBookBin completed in %lu ms\n", millis(), millis() - buildStart); + Serial.printf("[%lu] [EBP] Total indexing completed in %lu ms\n", millis(), millis() - indexingStart); if (!bookMetadataCache->cleanupTmpFiles()) { Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis()); diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 374cad2f..e7242138 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -40,7 +40,6 @@ bool BookMetadataCache::endContentOpfPass() { bool BookMetadataCache::beginTocPass() { Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis()); - // Open spine file for reading if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { return false; } @@ -48,12 +47,41 @@ bool BookMetadataCache::beginTocPass() { spineFile.close(); return false; } + + if (spineCount >= LARGE_SPINE_THRESHOLD) { + spineHrefIndex.clear(); + spineHrefIndex.reserve(spineCount); + spineFile.seek(0); + for (int i = 0; i < spineCount; i++) { + auto entry = readSpineEntry(spineFile); + SpineHrefIndexEntry idx; + idx.hrefHash = fnvHash64(entry.href); + idx.hrefLen = static_cast(entry.href.size()); + idx.spineIndex = static_cast(i); + spineHrefIndex.push_back(idx); + } + std::sort(spineHrefIndex.begin(), spineHrefIndex.end(), + [](const SpineHrefIndexEntry& a, const SpineHrefIndexEntry& b) { + return a.hrefHash < b.hrefHash || (a.hrefHash == b.hrefHash && a.hrefLen < b.hrefLen); + }); + spineFile.seek(0); + useSpineHrefIndex = true; + Serial.printf("[%lu] [BMC] Using fast index for %d spine items\n", millis(), spineCount); + } else { + useSpineHrefIndex = false; + } + return true; } bool BookMetadataCache::endTocPass() { tocFile.close(); spineFile.close(); + + spineHrefIndex.clear(); + spineHrefIndex.shrink_to_fit(); + useSpineHrefIndex = false; + return true; } @@ -124,6 +152,18 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta // LUTs complete // Loop through spines from spine file matching up TOC indexes, calculating cumulative size and writing to book.bin + // Build spineIndex->tocIndex mapping in one pass (O(n) instead of O(n*m)) + std::vector spineToTocIndex(spineCount, -1); + tocFile.seek(0); + for (int j = 0; j < tocCount; j++) { + auto tocEntry = readTocEntry(tocFile); + if (tocEntry.spineIndex >= 0 && tocEntry.spineIndex < spineCount) { + if (spineToTocIndex[tocEntry.spineIndex] == -1) { + spineToTocIndex[tocEntry.spineIndex] = static_cast(j); + } + } + } + ZipFile zip(epubPath); // Pre-open zip file to speed up size calculations if (!zip.open()) { @@ -133,31 +173,56 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta tocFile.close(); return false; } - // TODO: For large ZIPs loading the all localHeaderOffsets will crash. - // However not having them loaded is extremely slow. Need a better solution here. - // Perhaps only a cache of spine items or a better way to speedup lookups? - if (!zip.loadAllFileStatSlims()) { - Serial.printf("[%lu] [BMC] Could not load zip local header offsets for size calculations\n", millis()); - bookFile.close(); - spineFile.close(); - tocFile.close(); - zip.close(); - return false; + // NOTE: We intentionally skip calling loadAllFileStatSlims() here. + // For large EPUBs (2000+ chapters), pre-loading all ZIP central directory entries + // into memory causes OOM crashes on ESP32-C3's limited ~380KB RAM. + // Instead, for large books we use a one-pass batch lookup that scans the ZIP + // central directory once and matches against spine targets using hash comparison. + // This is O(n*log(m)) instead of O(n*m) while avoiding memory exhaustion. + // See: https://github.com/crosspoint-reader/crosspoint-reader/issues/134 + + std::vector spineSizes; + bool useBatchSizes = false; + + if (spineCount >= LARGE_SPINE_THRESHOLD) { + Serial.printf("[%lu] [BMC] Using batch size lookup for %d spine items\n", millis(), spineCount); + + std::vector targets; + targets.reserve(spineCount); + + spineFile.seek(0); + for (int i = 0; i < spineCount; i++) { + auto entry = readSpineEntry(spineFile); + std::string path = FsHelpers::normalisePath(entry.href); + + ZipFile::SizeTarget t; + t.hash = ZipFile::fnvHash64(path.c_str(), path.size()); + t.len = static_cast(path.size()); + t.index = static_cast(i); + targets.push_back(t); + } + + std::sort(targets.begin(), targets.end(), [](const ZipFile::SizeTarget& a, const ZipFile::SizeTarget& b) { + return a.hash < b.hash || (a.hash == b.hash && a.len < b.len); + }); + + spineSizes.resize(spineCount, 0); + int matched = zip.fillUncompressedSizes(targets, spineSizes); + Serial.printf("[%lu] [BMC] Batch lookup matched %d/%d spine items\n", millis(), matched, spineCount); + + targets.clear(); + targets.shrink_to_fit(); + + useBatchSizes = true; } + uint32_t cumSize = 0; spineFile.seek(0); int lastSpineTocIndex = -1; for (int i = 0; i < spineCount; i++) { auto spineEntry = readSpineEntry(spineFile); - tocFile.seek(0); - for (int j = 0; j < tocCount; j++) { - auto tocEntry = readTocEntry(tocFile); - if (tocEntry.spineIndex == i) { - spineEntry.tocIndex = j; - break; - } - } + spineEntry.tocIndex = spineToTocIndex[i]; // Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs // Logging here is for debugging @@ -169,16 +234,25 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta } lastSpineTocIndex = spineEntry.tocIndex; - // Calculate size for cumulative size size_t itemSize = 0; - const std::string path = FsHelpers::normalisePath(spineEntry.href); - if (zip.getInflatedFileSize(path.c_str(), &itemSize)) { - cumSize += itemSize; - spineEntry.cumulativeSize = cumSize; + if (useBatchSizes) { + itemSize = spineSizes[i]; + if (itemSize == 0) { + const std::string path = FsHelpers::normalisePath(spineEntry.href); + if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) { + Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str()); + } + } } else { - Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str()); + const std::string path = FsHelpers::normalisePath(spineEntry.href); + if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) { + Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str()); + } } + cumSize += itemSize; + spineEntry.cumulativeSize = cumSize; + // Write out spine data to book.bin writeSpineEntry(bookFile, spineEntry); } @@ -248,21 +322,38 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri return; } - int spineIndex = -1; - // find spine index - // TODO: This lookup is slow as need to scan through all items each time. We can't hold it all in memory due to size. - // But perhaps we can load just the hrefs in a vector/list to do an index lookup? - spineFile.seek(0); - for (int i = 0; i < spineCount; i++) { - auto spineEntry = readSpineEntry(spineFile); - if (spineEntry.href == href) { - spineIndex = i; + int16_t spineIndex = -1; + + if (useSpineHrefIndex) { + uint64_t targetHash = fnvHash64(href); + uint16_t targetLen = static_cast(href.size()); + + auto it = + std::lower_bound(spineHrefIndex.begin(), spineHrefIndex.end(), SpineHrefIndexEntry{targetHash, targetLen, 0}, + [](const SpineHrefIndexEntry& a, const SpineHrefIndexEntry& b) { + return a.hrefHash < b.hrefHash || (a.hrefHash == b.hrefHash && a.hrefLen < b.hrefLen); + }); + + while (it != spineHrefIndex.end() && it->hrefHash == targetHash && it->hrefLen == targetLen) { + spineIndex = it->spineIndex; break; } - } - if (spineIndex == -1) { - Serial.printf("[%lu] [BMC] addTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str()); + if (spineIndex == -1) { + Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str()); + } + } else { + spineFile.seek(0); + for (int i = 0; i < spineCount; i++) { + auto spineEntry = readSpineEntry(spineFile); + if (spineEntry.href == href) { + spineIndex = static_cast(i); + break; + } + } + if (spineIndex == -1) { + Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str()); + } } const TocEntry entry(title, href, anchor, level, spineIndex); diff --git a/lib/Epub/Epub/BookMetadataCache.h b/lib/Epub/Epub/BookMetadataCache.h index 29b2ae4a..20ce6559 100644 --- a/lib/Epub/Epub/BookMetadataCache.h +++ b/lib/Epub/Epub/BookMetadataCache.h @@ -2,7 +2,9 @@ #include +#include #include +#include class BookMetadataCache { public: @@ -53,6 +55,27 @@ class BookMetadataCache { FsFile spineFile; FsFile tocFile; + // Index for fast href→spineIndex lookup (used only for large EPUBs) + struct SpineHrefIndexEntry { + uint64_t hrefHash; // FNV-1a 64-bit hash + uint16_t hrefLen; // length for collision reduction + int16_t spineIndex; + }; + std::vector spineHrefIndex; + bool useSpineHrefIndex = false; + + static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400; + + // FNV-1a 64-bit hash function + static uint64_t fnvHash64(const std::string& s) { + uint64_t hash = 14695981039346656037ull; + for (char c : s) { + hash ^= static_cast(c); + hash *= 1099511628211ull; + } + return hash; + } + uint32_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const; uint32_t writeTocEntry(FsFile& file, const TocEntry& entry) const; SpineEntry readSpineEntry(FsFile& file) const; diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 9fbeb386..ce0e22ea 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -38,6 +38,9 @@ ContentOpfParser::~ContentOpfParser() { if (SdMan.exists((cachePath + itemCacheFile).c_str())) { SdMan.remove((cachePath + itemCacheFile).c_str()); } + itemIndex.clear(); + itemIndex.shrink_to_fit(); + useItemIndex = false; } size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); } @@ -129,6 +132,15 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name "[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n", millis()); } + + // Sort item index for binary search if we have enough items + if (self->itemIndex.size() >= LARGE_SPINE_THRESHOLD) { + std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) { + return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen); + }); + self->useItemIndex = true; + Serial.printf("[%lu] [COF] Using fast index for %zu manifest items\n", millis(), self->itemIndex.size()); + } return; } @@ -180,6 +192,15 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name } } + // Record index entry for fast lookup later + if (self->tempItemStore) { + ItemIndexEntry entry; + entry.idHash = fnvHash(itemId); + entry.idLen = static_cast(itemId.size()); + entry.fileOffset = static_cast(self->tempItemStore.position()); + self->itemIndex.push_back(entry); + } + // Write items down to SD card serialization::writeString(self->tempItemStore, itemId); serialization::writeString(self->tempItemStore, href); @@ -215,19 +236,50 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name for (int i = 0; atts[i]; i += 2) { if (strcmp(atts[i], "idref") == 0) { const std::string idref = atts[i + 1]; - // Resolve the idref to href using items map - // TODO: This lookup is slow as need to scan through all items each time. - // It can take up to 200ms per item when getting to 1500 items. - self->tempItemStore.seek(0); - std::string itemId; std::string href; - while (self->tempItemStore.available()) { - serialization::readString(self->tempItemStore, itemId); - serialization::readString(self->tempItemStore, href); - if (itemId == idref) { - self->cache->createSpineEntry(href); - break; + bool found = false; + + if (self->useItemIndex) { + // Fast path: binary search + uint32_t targetHash = fnvHash(idref); + uint16_t targetLen = static_cast(idref.size()); + + auto it = std::lower_bound(self->itemIndex.begin(), self->itemIndex.end(), + ItemIndexEntry{targetHash, targetLen, 0}, + [](const ItemIndexEntry& a, const ItemIndexEntry& b) { + return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen); + }); + + // Check for match (may need to check a few due to hash collisions) + while (it != self->itemIndex.end() && it->idHash == targetHash) { + self->tempItemStore.seek(it->fileOffset); + std::string itemId; + serialization::readString(self->tempItemStore, itemId); + if (itemId == idref) { + serialization::readString(self->tempItemStore, href); + found = true; + break; + } + ++it; } + } else { + // Slow path: linear scan (for small manifests, keeps original behavior) + // TODO: This lookup is slow as need to scan through all items each time. + // It can take up to 200ms per item when getting to 1500 items. + self->tempItemStore.seek(0); + std::string itemId; + while (self->tempItemStore.available()) { + serialization::readString(self->tempItemStore, itemId); + serialization::readString(self->tempItemStore, href); + if (itemId == idref) { + found = true; + break; + } + } + } + + if (found && self->cache) { + self->cache->createSpineEntry(href); } } } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.h b/lib/Epub/Epub/parsers/ContentOpfParser.h index 8c56a86f..b40a3787 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.h +++ b/lib/Epub/Epub/parsers/ContentOpfParser.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include + #include "Epub.h" #include "expat.h" @@ -28,6 +31,27 @@ class ContentOpfParser final : public Print { FsFile tempItemStore; std::string coverItemId; + // Index for fast idref→href lookup (used only for large EPUBs) + struct ItemIndexEntry { + uint32_t idHash; // FNV-1a hash of itemId + uint16_t idLen; // length for collision reduction + uint32_t fileOffset; // offset in .items.bin + }; + std::vector itemIndex; + bool useItemIndex = false; + + static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400; + + // FNV-1a hash function + static uint32_t fnvHash(const std::string& s) { + uint32_t hash = 2166136261u; + for (char c : s) { + hash ^= static_cast(c); + hash *= 16777619u; + } + return hash; + } + static void startElement(void* userData, const XML_Char* name, const XML_Char** atts); static void characterData(void* userData, const XML_Char* s, int len); static void endElement(void* userData, const XML_Char* name); diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp index 2a97858a..a5f65ea3 100644 --- a/lib/ZipFile/ZipFile.cpp +++ b/lib/ZipFile/ZipFile.cpp @@ -4,6 +4,8 @@ #include #include +#include + bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) { // Setup inflator const auto inflator = static_cast(malloc(sizeof(tinfl_decompressor))); @@ -74,6 +76,10 @@ bool ZipFile::loadAllFileStatSlims() { file.seekCur(m + k); } + // Set cursor to start of central directory for sequential access + lastCentralDirPos = zipDetails.centralDirOffset; + lastCentralDirPosValid = true; + if (!wasOpen) { close(); } @@ -102,15 +108,35 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) { return false; } - file.seek(zipDetails.centralDirOffset); + // Phase 1: Try scanning from cursor position first + uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset; + uint32_t wrapPos = zipDetails.centralDirOffset; + bool wrapped = false; + bool found = false; + + file.seek(startPos); uint32_t sig; char itemName[256]; - bool found = false; - while (file.available()) { - file.read(&sig, 4); - if (sig != 0x02014b50) break; // End of list + while (true) { + uint32_t entryStart = file.position(); + + if (file.read(&sig, 4) != 4 || sig != 0x02014b50) { + // End of central directory + if (!wrapped && lastCentralDirPosValid && startPos != zipDetails.centralDirOffset) { + // Wrap around to beginning + file.seek(zipDetails.centralDirOffset); + wrapped = true; + continue; + } + break; + } + + // If we've wrapped and reached our start position, stop + if (wrapped && entryStart >= startPos) { + break; + } file.seekCur(6); file.read(&fileStat->method, 2); @@ -123,15 +149,25 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) { file.read(&k, 2); file.seekCur(8); file.read(&fileStat->localHeaderOffset, 4); - file.read(itemName, nameLen); - itemName[nameLen] = '\0'; - if (strcmp(itemName, filename) == 0) { - found = true; - break; + if (nameLen < 256) { + file.read(itemName, nameLen); + itemName[nameLen] = '\0'; + + if (strcmp(itemName, filename) == 0) { + // Found it! Update cursor to next entry + file.seekCur(m + k); + lastCentralDirPos = file.position(); + lastCentralDirPosValid = true; + found = true; + break; + } + } else { + // Name too long, skip it + file.seekCur(nameLen); } - // Skip the rest of this entry (extra field + comment) + // Skip extra field + comment file.seekCur(m + k); } @@ -253,6 +289,8 @@ bool ZipFile::close() { if (file) { file.close(); } + lastCentralDirPos = 0; + lastCentralDirPosValid = false; return true; } @@ -266,6 +304,80 @@ bool ZipFile::getInflatedFileSize(const char* filename, size_t* size) { return true; } +int ZipFile::fillUncompressedSizes(std::vector& targets, std::vector& sizes) { + if (targets.empty()) { + return 0; + } + + const bool wasOpen = isOpen(); + if (!wasOpen && !open()) { + return 0; + } + + if (!loadZipDetails()) { + if (!wasOpen) { + close(); + } + return 0; + } + + file.seek(zipDetails.centralDirOffset); + + int matched = 0; + uint32_t sig; + char itemName[256]; + + while (file.available()) { + file.read(&sig, 4); + if (sig != 0x02014b50) break; + + file.seekCur(6); + uint16_t method; + file.read(&method, 2); + file.seekCur(8); + uint32_t compressedSize, uncompressedSize; + file.read(&compressedSize, 4); + file.read(&uncompressedSize, 4); + uint16_t nameLen, m, k; + file.read(&nameLen, 2); + file.read(&m, 2); + file.read(&k, 2); + file.seekCur(8); + uint32_t localHeaderOffset; + file.read(&localHeaderOffset, 4); + + if (nameLen < 256) { + file.read(itemName, nameLen); + itemName[nameLen] = '\0'; + + uint64_t hash = fnvHash64(itemName, nameLen); + SizeTarget key = {hash, nameLen, 0}; + + auto it = std::lower_bound(targets.begin(), targets.end(), key, [](const SizeTarget& a, const SizeTarget& b) { + return a.hash < b.hash || (a.hash == b.hash && a.len < b.len); + }); + + while (it != targets.end() && it->hash == hash && it->len == nameLen) { + if (it->index < sizes.size()) { + sizes[it->index] = uncompressedSize; + matched++; + } + ++it; + } + } else { + file.seekCur(nameLen); + } + + file.seekCur(m + k); + } + + if (!wasOpen) { + close(); + } + + return matched; +} + uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) { const bool wasOpen = isOpen(); if (!wasOpen && !open()) { diff --git a/lib/ZipFile/ZipFile.h b/lib/ZipFile/ZipFile.h index 0144ed42..0c82e5a0 100644 --- a/lib/ZipFile/ZipFile.h +++ b/lib/ZipFile/ZipFile.h @@ -3,6 +3,7 @@ #include #include +#include class ZipFile { public: @@ -19,12 +20,33 @@ class ZipFile { bool isSet; }; + // Target for batch uncompressed size lookup (sorted by hash, then len) + struct SizeTarget { + uint64_t hash; // FNV-1a 64-bit hash of normalized path + uint16_t len; // Length of path for collision reduction + uint16_t index; // Caller's index (e.g. spine index) + }; + + // FNV-1a 64-bit hash computed from char buffer (no std::string allocation) + static uint64_t fnvHash64(const char* s, size_t len) { + uint64_t hash = 14695981039346656037ull; + for (size_t i = 0; i < len; i++) { + hash ^= static_cast(s[i]); + hash *= 1099511628211ull; + } + return hash; + } + private: const std::string& filePath; FsFile file; ZipDetails zipDetails = {0, 0, false}; std::unordered_map fileStatSlimCache; + // Cursor for sequential central-dir scanning optimization + uint32_t lastCentralDirPos = 0; + bool lastCentralDirPosValid = false; + bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat); long getDataOffset(const FileStatSlim& fileStat); bool loadZipDetails(); @@ -39,6 +61,10 @@ class ZipFile { bool close(); bool loadAllFileStatSlims(); bool getInflatedFileSize(const char* filename, size_t* size); + // Batch lookup: scan ZIP central dir once and fill sizes for matching targets. + // targets must be sorted by (hash, len). sizes[target.index] receives uncompressedSize. + // Returns number of targets matched. + int fillUncompressedSizes(std::vector& targets, std::vector& sizes); // Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple // These functions will open and close the zip as needed uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false); From e0b6b9b28a4ed34bd60a51a1e851ca8d75a81ebb Mon Sep 17 00:00:00 2001 From: V Date: Tue, 27 Jan 2026 14:30:27 +0000 Subject: [PATCH 04/13] refactor: Re-work for OTA feature (#509) ## Summary Finally, I have received my device and got to chance to work on OTA. https://github.com/crosspoint-reader/crosspoint-reader/issues/176 * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Existing OTA functionality is very buggy, many of times (I would say 8 out of 10) are end up with fail for me. When the time that it works it is very slow and take ages. For others looks like end up with crash or different issues. * **What changes are included?** To be honest, I'm not familiar with Arduino APIs of OTA process, but looks like not good as much esp-idf itself. I always found Arduino APIs very bulky for esp32. Wrappers and wrappers. ## Additional Context Right now, OTA takes ~ 3min 10sec (of course depends on size of .bin file). Can be tested with playing version info inside from `platform.ini` file. ``` [crosspoint] version = 0.14.0 ``` --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< NO >**_ --- src/activities/settings/OtaUpdateActivity.cpp | 11 +- src/network/OtaUpdater.cpp | 219 +++++++++++++----- src/network/OtaUpdater.h | 15 +- 3 files changed, 174 insertions(+), 71 deletions(-) diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index 0393847d..86dcf2ac 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -97,7 +97,7 @@ void OtaUpdateActivity::onExit() { void OtaUpdateActivity::displayTaskLoop() { while (true) { - if (updateRequired) { + if (updateRequired || updater.getRender()) { updateRequired = false; xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); @@ -115,8 +115,9 @@ void OtaUpdateActivity::render() { float updaterProgress = 0; if (state == UPDATE_IN_PROGRESS) { - Serial.printf("[%lu] [OTA] Update progress: %d / %d\n", millis(), updater.processedSize, updater.totalSize); - updaterProgress = static_cast(updater.processedSize) / static_cast(updater.totalSize); + Serial.printf("[%lu] [OTA] Update progress: %d / %d\n", millis(), updater.getProcessedSize(), + updater.getTotalSize()); + updaterProgress = static_cast(updater.getProcessedSize()) / static_cast(updater.getTotalSize()); // Only update every 2% at the most if (static_cast(updaterProgress * 50) == lastUpdaterPercentage / 2) { return; @@ -154,7 +155,7 @@ void OtaUpdateActivity::render() { (std::to_string(static_cast(updaterProgress * 100)) + "%").c_str()); renderer.drawCenteredText( UI_10_FONT_ID, 440, - (std::to_string(updater.processedSize) + " / " + std::to_string(updater.totalSize)).c_str()); + (std::to_string(updater.getProcessedSize()) + " / " + std::to_string(updater.getTotalSize())).c_str()); renderer.displayBuffer(); return; } @@ -194,7 +195,7 @@ void OtaUpdateActivity::loop() { xSemaphoreGive(renderingMutex); updateRequired = true; vTaskDelay(10 / portTICK_PERIOD_MS); - const auto res = updater.installUpdate([this](const size_t, const size_t) { updateRequired = true; }); + const auto res = updater.installUpdate(); if (res != OtaUpdater::OK) { Serial.printf("[%lu] [OTA] Update failed: %d\n", millis(), res); diff --git a/src/network/OtaUpdater.cpp b/src/network/OtaUpdater.cpp index d831af0a..1733e136 100644 --- a/src/network/OtaUpdater.cpp +++ b/src/network/OtaUpdater.cpp @@ -1,38 +1,123 @@ #include "OtaUpdater.h" #include -#include -#include + +#include "esp_http_client.h" +#include "esp_https_ota.h" +#include "esp_wifi.h" namespace { constexpr char latestReleaseUrl[] = "https://api.github.com/repos/crosspoint-reader/crosspoint-reader/releases/latest"; + +/* This is buffer and size holder to keep upcoming data from latestReleaseUrl */ +char* local_buf; +int output_len; + +/* + * When esp_crt_bundle.h included, it is pointing wrong header file + * which is something under WifiClientSecure because of our framework based on arduno platform. + * To manage this obstacle, don't include anything, just extern and it will point correct one. + */ +extern "C" { +extern esp_err_t esp_crt_bundle_attach(void* conf); } +esp_err_t http_client_set_header_cb(esp_http_client_handle_t http_client) { + return esp_http_client_set_header(http_client, "User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION); +} + +esp_err_t event_handler(esp_http_client_event_t* event) { + /* We do interested in only HTTP_EVENT_ON_DATA event only */ + if (event->event_id != HTTP_EVENT_ON_DATA) return ESP_OK; + + if (!esp_http_client_is_chunked_response(event->client)) { + int content_len = esp_http_client_get_content_length(event->client); + int copy_len = 0; + + if (local_buf == NULL) { + /* local_buf life span is tracked by caller checkForUpdate */ + local_buf = static_cast(calloc(content_len + 1, sizeof(char))); + output_len = 0; + if (local_buf == NULL) { + Serial.printf("[%lu] [OTA] HTTP Client Out of Memory Failed, Allocation %d\n", millis(), content_len); + return ESP_ERR_NO_MEM; + } + } + copy_len = min(event->data_len, (content_len - output_len)); + if (copy_len) { + memcpy(local_buf + output_len, event->data, copy_len); + } + output_len += copy_len; + } else { + /* Code might be hits here, It happened once (for version checking) but I need more logs to handle that */ + int chunked_len; + esp_http_client_get_chunk_length(event->client, &chunked_len); + Serial.printf("[%lu] [OTA] esp_http_client_is_chunked_response failed, chunked_len: %d\n", millis(), chunked_len); + } + + return ESP_OK; +} /* event_handler */ +} /* namespace */ + OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { - const std::unique_ptr client(new WiFiClientSecure); - client->setInsecure(); - HTTPClient http; + JsonDocument filter; + esp_err_t esp_err; + JsonDocument doc; - Serial.printf("[%lu] [OTA] Fetching: %s\n", millis(), latestReleaseUrl); + esp_http_client_config_t client_config = { + .url = latestReleaseUrl, + .event_handler = event_handler, + /* Default HTTP client buffer size 512 byte only */ + .buffer_size = 8192, + .buffer_size_tx = 8192, + .skip_cert_common_name_check = true, + .crt_bundle_attach = esp_crt_bundle_attach, + .keep_alive_enable = true, + }; - http.begin(*client, latestReleaseUrl); - http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION); + /* To track life time of local_buf, dtor will be called on exit from that function */ + struct localBufCleaner { + char** bufPtr; + ~localBufCleaner() { + if (*bufPtr) { + free(*bufPtr); + *bufPtr = NULL; + } + } + } localBufCleaner = {&local_buf}; - const int httpCode = http.GET(); - if (httpCode != HTTP_CODE_OK) { - Serial.printf("[%lu] [OTA] HTTP error: %d\n", millis(), httpCode); - http.end(); + esp_http_client_handle_t client_handle = esp_http_client_init(&client_config); + if (!client_handle) { + Serial.printf("[%lu] [OTA] HTTP Client Handle Failed\n", millis()); + return INTERNAL_UPDATE_ERROR; + } + + esp_err = esp_http_client_set_header(client_handle, "User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION); + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] esp_http_client_set_header Failed : %s\n", millis(), esp_err_to_name(esp_err)); + esp_http_client_cleanup(client_handle); + return INTERNAL_UPDATE_ERROR; + } + + esp_err = esp_http_client_perform(client_handle); + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] esp_http_client_perform Failed : %s\n", millis(), esp_err_to_name(esp_err)); + esp_http_client_cleanup(client_handle); return HTTP_ERROR; } - JsonDocument doc; - JsonDocument filter; + /* esp_http_client_close will be called inside cleanup as well*/ + esp_err = esp_http_client_cleanup(client_handle); + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] esp_http_client_cleanupp Failed : %s\n", millis(), esp_err_to_name(esp_err)); + return INTERNAL_UPDATE_ERROR; + } + filter["tag_name"] = true; filter["assets"][0]["name"] = true; filter["assets"][0]["browser_download_url"] = true; filter["assets"][0]["size"] = true; - const DeserializationError error = deserializeJson(doc, *client, DeserializationOption::Filter(filter)); - http.end(); + const DeserializationError error = deserializeJson(doc, local_buf, DeserializationOption::Filter(filter)); if (error) { Serial.printf("[%lu] [OTA] JSON parse failed: %s\n", millis(), error.c_str()); return JSON_PARSE_ERROR; @@ -42,6 +127,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { Serial.printf("[%lu] [OTA] No tag_name found\n", millis()); return JSON_PARSE_ERROR; } + if (!doc["assets"].is()) { Serial.printf("[%lu] [OTA] No assets found\n", millis()); return JSON_PARSE_ERROR; @@ -104,67 +190,74 @@ bool OtaUpdater::isUpdateNewer() const { const std::string& OtaUpdater::getLatestVersion() const { return latestVersion; } -OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate(const std::function& onProgress) { +OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate() { if (!isUpdateNewer()) { return UPDATE_OLDER_ERROR; } - const std::unique_ptr client(new WiFiClientSecure); - client->setInsecure(); - HTTPClient http; + esp_https_ota_handle_t ota_handle = NULL; + esp_err_t esp_err; + /* Signal for OtaUpdateActivity */ + render = false; - Serial.printf("[%lu] [OTA] Fetching: %s\n", millis(), otaUrl.c_str()); + esp_http_client_config_t client_config = { + .url = otaUrl.c_str(), + .timeout_ms = 15000, + /* Default HTTP client buffer size 512 byte only + * not sufficent to handle URL redirection cases or + * parsing of large HTTP headers. + */ + .buffer_size = 8192, + .buffer_size_tx = 8192, + .skip_cert_common_name_check = true, + .crt_bundle_attach = esp_crt_bundle_attach, + .keep_alive_enable = true, + }; - http.begin(*client, otaUrl.c_str()); - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION); - const int httpCode = http.GET(); + esp_https_ota_config_t ota_config = { + .http_config = &client_config, + .http_client_init_cb = http_client_set_header_cb, + }; - if (httpCode != HTTP_CODE_OK) { - Serial.printf("[%lu] [OTA] Download failed: %d\n", millis(), httpCode); - http.end(); + /* For better timing and connectivity, we disable power saving for WiFi */ + esp_wifi_set_ps(WIFI_PS_NONE); + + esp_err = esp_https_ota_begin(&ota_config, &ota_handle); + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] HTTP OTA Begin Failed: %s\n", millis(), esp_err_to_name(esp_err)); + return INTERNAL_UPDATE_ERROR; + } + + do { + esp_err = esp_https_ota_perform(ota_handle); + processedSize = esp_https_ota_get_image_len_read(ota_handle); + /* Sent signal to OtaUpdateActivity */ + render = true; + vTaskDelay(10 / portTICK_PERIOD_MS); + } while (esp_err == ESP_ERR_HTTPS_OTA_IN_PROGRESS); + + /* Return back to default power saving for WiFi in case of failing */ + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] esp_https_ota_perform Failed: %s\n", millis(), esp_err_to_name(esp_err)); + esp_https_ota_finish(ota_handle); return HTTP_ERROR; } - // 2. Get length and stream - const size_t contentLength = http.getSize(); - - if (contentLength != otaSize) { - Serial.printf("[%lu] [OTA] Invalid content length\n", millis()); - http.end(); - return HTTP_ERROR; - } - - // 3. Begin the ESP-IDF Update process - if (!Update.begin(otaSize)) { - Serial.printf("[%lu] [OTA] Not enough space. Error: %s\n", millis(), Update.errorString()); - http.end(); + if (!esp_https_ota_is_complete_data_received(ota_handle)) { + Serial.printf("[%lu] [OTA] esp_https_ota_is_complete_data_received Failed: %s\n", millis(), + esp_err_to_name(esp_err)); + esp_https_ota_finish(ota_handle); return INTERNAL_UPDATE_ERROR; } - this->totalSize = otaSize; - Serial.printf("[%lu] [OTA] Update started\n", millis()); - Update.onProgress([this, onProgress](const size_t progress, const size_t total) { - this->processedSize = progress; - this->totalSize = total; - onProgress(progress, total); - }); - const size_t written = Update.writeStream(*client); - http.end(); - - if (written == otaSize) { - Serial.printf("[%lu] [OTA] Successfully written %u bytes\n", millis(), written); - } else { - Serial.printf("[%lu] [OTA] Written only %u/%u bytes. Error: %s\n", millis(), written, otaSize, - Update.errorString()); + esp_err = esp_https_ota_finish(ota_handle); + if (esp_err != ESP_OK) { + Serial.printf("[%lu] [OTA] esp_https_ota_finish Failed: %s\n", millis(), esp_err_to_name(esp_err)); return INTERNAL_UPDATE_ERROR; } - if (Update.end() && Update.isFinished()) { - Serial.printf("[%lu] [OTA] Update complete\n", millis()); - return OK; - } else { - Serial.printf("[%lu] [OTA] Error Occurred: %s\n", millis(), Update.errorString()); - return INTERNAL_UPDATE_ERROR; - } + Serial.printf("[%lu] [OTA] Update completed\n", millis()); + return OK; } diff --git a/src/network/OtaUpdater.h b/src/network/OtaUpdater.h index 817f24b1..24e04cf5 100644 --- a/src/network/OtaUpdater.h +++ b/src/network/OtaUpdater.h @@ -8,6 +8,9 @@ class OtaUpdater { std::string latestVersion; std::string otaUrl; size_t otaSize = 0; + size_t processedSize = 0; + size_t totalSize = 0; + bool render = false; public: enum OtaUpdaterError { @@ -19,12 +22,18 @@ class OtaUpdater { INTERNAL_UPDATE_ERROR, OOM_ERROR, }; - size_t processedSize = 0; - size_t totalSize = 0; + + size_t getOtaSize() const { return otaSize; } + + size_t getProcessedSize() const { return processedSize; } + + size_t getTotalSize() const { return totalSize; } + + bool getRender() const { return render; } OtaUpdater() = default; bool isUpdateNewer() const; const std::string& getLatestVersion() const; OtaUpdaterError checkForUpdate(); - OtaUpdaterError installUpdate(const std::function& onProgress); + OtaUpdaterError installUpdate(); }; From 140fcb9db56c3d271116b98fcd2e8ecf28bb174a Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 27 Jan 2026 20:09:05 +0500 Subject: [PATCH 05/13] fix: missing front layout in mapLabels() (#564) ## Summary * adds missing front layout to mapLabels function --- src/MappedInputManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 14c45deb..25095be7 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -85,6 +85,8 @@ MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const return {previous, next, back, confirm}; case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT: return {previous, back, confirm, next}; + case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT: + return {back, confirm, next, previous}; case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: default: return {back, confirm, previous, next}; From 8c1c80787a1620b728c5afa926a7f95c66c8503d Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 28 Jan 2026 02:43:04 +1100 Subject: [PATCH 06/13] fix: Render keyboard entry over multiple lines (#567) ## Summary * Render keyboard entry over multiple lines * Grows display areas based on input text * Shown on OPDS entry, but applies everywhere ## Additional Context * Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/554 | One line | Multi-line | | --- | --- | | ![IMG_5925](https://github.com/user-attachments/assets/28be00a8-7b90-4bf6-9ebf-4d4ad6642bc9) | ![IMG_5926](https://github.com/user-attachments/assets/1c69a96f-d868-49a1-866c-546ca7b784ab) | --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? No --- src/activities/util/KeyboardEntryActivity.cpp | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp index 8c36ac33..3a6befac 100644 --- a/src/activities/util/KeyboardEntryActivity.cpp +++ b/src/activities/util/KeyboardEntryActivity.cpp @@ -256,8 +256,9 @@ void KeyboardEntryActivity::render() const { renderer.drawCenteredText(UI_10_FONT_ID, startY, title.c_str()); // Draw input field - const int inputY = startY + 22; - renderer.drawText(UI_10_FONT_ID, 10, inputY, "["); + const int inputStartY = startY + 22; + int inputEndY = startY + 22; + renderer.drawText(UI_10_FONT_ID, 10, inputStartY, "["); std::string displayText; if (isPassword) { @@ -269,19 +270,29 @@ void KeyboardEntryActivity::render() const { // Show cursor at end displayText += "_"; - // Truncate if too long for display - use actual character width from font - int approxCharWidth = renderer.getSpaceWidth(UI_10_FONT_ID); - if (approxCharWidth < 1) approxCharWidth = 8; // Fallback to approximate width - const int maxDisplayLen = (pageWidth - 40) / approxCharWidth; - if (displayText.length() > static_cast(maxDisplayLen)) { - displayText = "..." + displayText.substr(displayText.length() - maxDisplayLen + 3); - } + // Render input text across multiple lines + int lineStartIdx = 0; + int lineEndIdx = displayText.length(); + while (true) { + std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx); + const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, lineText.c_str()); + if (textWidth <= pageWidth - 40) { + renderer.drawText(UI_10_FONT_ID, 20, inputEndY, lineText.c_str()); + if (lineEndIdx == displayText.length()) { + break; + } - renderer.drawText(UI_10_FONT_ID, 20, inputY, displayText.c_str()); - renderer.drawText(UI_10_FONT_ID, pageWidth - 15, inputY, "]"); + inputEndY += renderer.getLineHeight(UI_10_FONT_ID); + lineStartIdx = lineEndIdx; + lineEndIdx = displayText.length(); + } else { + lineEndIdx -= 1; + } + } + renderer.drawText(UI_10_FONT_ID, pageWidth - 15, inputEndY, "]"); // Draw keyboard - use compact spacing to fit 5 rows on screen - const int keyboardStartY = inputY + 25; + const int keyboardStartY = inputEndY + 25; constexpr int keyWidth = 18; constexpr int keyHeight = 18; constexpr int keySpacing = 3; From 1286fc15ecd3b743d462fb719fa8a105c7d28f88 Mon Sep 17 00:00:00 2001 From: Uri Tauber Date: Tue, 27 Jan 2026 17:58:02 +0200 Subject: [PATCH 07/13] feat: consolidate reader menu into tabbed TOC view --- src/activities/reader/ChaptersTab.cpp | 153 ++++++++++ src/activities/reader/ChaptersTab.h | 44 +++ src/activities/reader/EpubReaderActivity.cpp | 88 ++---- src/activities/reader/EpubReaderActivity.h | 2 +- .../EpubReaderChapterSelectionActivity.cpp | 265 ------------------ .../EpubReaderChapterSelectionActivity.h | 70 ----- .../reader/EpubReaderMenuActivity.cpp | 96 ------- .../reader/EpubReaderMenuActivity.h | 33 --- .../reader/EpubReaderTocActivity.cpp | 132 +++++++++ src/activities/reader/EpubReaderTocActivity.h | 75 +++++ ...FootnotesActivity.cpp => FootnotesTab.cpp} | 66 ++--- ...aderFootnotesActivity.h => FootnotesTab.h} | 33 +-- src/activities/reader/TocTab.h | 24 ++ 13 files changed, 498 insertions(+), 583 deletions(-) create mode 100644 src/activities/reader/ChaptersTab.cpp create mode 100644 src/activities/reader/ChaptersTab.h delete mode 100644 src/activities/reader/EpubReaderChapterSelectionActivity.cpp delete mode 100644 src/activities/reader/EpubReaderChapterSelectionActivity.h delete mode 100644 src/activities/reader/EpubReaderMenuActivity.cpp delete mode 100644 src/activities/reader/EpubReaderMenuActivity.h create mode 100644 src/activities/reader/EpubReaderTocActivity.cpp create mode 100644 src/activities/reader/EpubReaderTocActivity.h rename src/activities/reader/{EpubReaderFootnotesActivity.cpp => FootnotesTab.cpp} (51%) rename src/activities/reader/{EpubReaderFootnotesActivity.h => FootnotesTab.h} (58%) create mode 100644 src/activities/reader/TocTab.h diff --git a/src/activities/reader/ChaptersTab.cpp b/src/activities/reader/ChaptersTab.cpp new file mode 100644 index 00000000..df47d1fc --- /dev/null +++ b/src/activities/reader/ChaptersTab.cpp @@ -0,0 +1,153 @@ +#include "ChaptersTab.h" + +#include + +#include "KOReaderCredentialStore.h" +#include "MappedInputManager.h" +#include "fontIds.h" + +namespace { +constexpr int SKIP_PAGE_MS = 700; +constexpr int LINE_HEIGHT = 30; +} // namespace + +void ChaptersTab::onEnter() { + buildFilteredChapterList(); + + selectorIndex = 0; + for (size_t i = 0; i < filteredSpineIndices.size(); i++) { + if (filteredSpineIndices[i] == currentSpineIndex) { + selectorIndex = i; + break; + } + } + + if (hasSyncOption()) { + selectorIndex += 1; + } + updateRequired = true; +} + +bool ChaptersTab::hasSyncOption() const { return KOREADER_STORE.hasCredentials(); } + +int ChaptersTab::getTotalItems() const { + const int syncCount = hasSyncOption() ? 2 : 0; + return filteredSpineIndices.size() + syncCount; +} + +bool ChaptersTab::isSyncItem(int index) const { + if (!hasSyncOption()) return false; + return index == 0 || index == getTotalItems() - 1; +} + +int ChaptersTab::tocIndexFromItemIndex(int itemIndex) const { + const int offset = hasSyncOption() ? 1 : 0; + return itemIndex - offset; +} + +int ChaptersTab::getPageItems(int contentTop, int contentHeight) const { + int items = contentHeight / LINE_HEIGHT; + return (items < 1) ? 1 : items; +} + +void ChaptersTab::buildFilteredChapterList() { + filteredSpineIndices.clear(); + for (int i = 0; i < epub->getSpineItemsCount(); i++) { + if (epub->shouldHideFromToc(i)) continue; + int tocIndex = epub->getTocIndexForSpineIndex(i); + if (tocIndex == -1) continue; + filteredSpineIndices.push_back(i); + } +} + +void ChaptersTab::loop() { + const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up); + const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down); + const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; + const int totalItems = getTotalItems(); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (hasSyncOption() && (selectorIndex == 0 || selectorIndex == totalItems - 1)) { + onLaunchSync(); + return; + } + + int filteredIndex = selectorIndex; + if (hasSyncOption()) filteredIndex -= 1; + + if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { + onSelectSpineIndex(filteredSpineIndices[filteredIndex]); + } + } else if (upReleased) { + if (totalItems > 0) { + if (skipPage) { + // This logic matches MyLibraryActivity + // But for simplicity let's just do a page jump + } + selectorIndex = (selectorIndex + totalItems - 1) % totalItems; + updateRequired = true; + } + } else if (downReleased) { + if (totalItems > 0) { + selectorIndex = (selectorIndex + 1) % totalItems; + updateRequired = true; + } + } +} + +void ChaptersTab::render(int contentTop, int contentHeight) { + const auto pageWidth = renderer.getScreenWidth(); + const int pageItems = getPageItems(contentTop, contentHeight); + const int totalItems = getTotalItems(); + + const auto pageStartIndex = selectorIndex / pageItems * pageItems; + renderer.fillRect(0, contentTop + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT); + + for (int i = 0; i < pageItems; i++) { + int itemIndex = pageStartIndex + i; + if (itemIndex >= totalItems) break; + + const int displayY = contentTop + i * LINE_HEIGHT; + const bool isSelected = (itemIndex == selectorIndex); + + if (isSyncItem(itemIndex)) { + renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected); + } else { + int filteredIndex = itemIndex; + if (hasSyncOption()) filteredIndex -= 1; + + if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { + int spineIndex = filteredSpineIndices[filteredIndex]; + int tocIndex = epub->getTocIndexForSpineIndex(spineIndex); + + if (tocIndex == -1) { + renderer.drawText(UI_10_FONT_ID, 20, displayY, "Unnamed", !isSelected); + } else { + auto item = epub->getTocItem(tocIndex); + const int indentSize = 20 + (item.level - 1) * 15; + const std::string chapterName = + renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); + renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); + } + } + } + } +} + +int ChaptersTab::getCurrentPage() const { + // We don't have enough context here to know pageItems easily without contentHeight + // For now let's just return a placeholder or calculate it if we can. + // Actually onEnter can't know the height either if it's dynamic. + // Let's assume contentTop=60, contentHeight=screenHeight-120 + const int availableHeight = renderer.getScreenHeight() - 120; + const int itemsPerPage = availableHeight / LINE_HEIGHT; + return selectorIndex / (itemsPerPage > 0 ? itemsPerPage : 1) + 1; +} + +int ChaptersTab::getTotalPages() const { + const int availableHeight = renderer.getScreenHeight() - 120; + const int itemsPerPage = availableHeight / LINE_HEIGHT; + const int totalItems = getTotalItems(); + if (totalItems == 0) return 1; + return (totalItems + itemsPerPage - 1) / (itemsPerPage > 0 ? itemsPerPage : 1); +} diff --git a/src/activities/reader/ChaptersTab.h b/src/activities/reader/ChaptersTab.h new file mode 100644 index 00000000..ae80107a --- /dev/null +++ b/src/activities/reader/ChaptersTab.h @@ -0,0 +1,44 @@ +#pragma once +#include + +#include +#include +#include + +#include "TocTab.h" + +class ChaptersTab final : public TocTab { + std::shared_ptr epub; + int currentSpineIndex; + int selectorIndex = 0; + bool updateRequired = false; + std::vector filteredSpineIndices; + + const std::function onSelectSpineIndex; + const std::function onLaunchSync; + + int getPageItems(int contentTop, int contentHeight) const; + int getTotalItems() const; + bool hasSyncOption() const; + bool isSyncItem(int index) const; + int tocIndexFromItemIndex(int itemIndex) const; + void buildFilteredChapterList(); + + public: + ChaptersTab(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub, + int currentSpineIndex, std::function onSelectSpineIndex, std::function onLaunchSync) + : TocTab(renderer, mappedInput), + epub(epub), + currentSpineIndex(currentSpineIndex), + onSelectSpineIndex(onSelectSpineIndex), + onLaunchSync(onLaunchSync) {} + + void onEnter() override; + void loop() override; + void render(int contentTop, int contentHeight) override; + + int getCurrentPage() const override; + int getTotalPages() const override; + bool isUpdateRequired() const override { return updateRequired; } + void clearUpdateRequired() override { updateRequired = false; } +}; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 07c3a1bd..52605dba 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -7,9 +7,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" -#include "EpubReaderChapterSelectionActivity.h" -#include "EpubReaderFootnotesActivity.h" -#include "EpubReaderMenuActivity.h" +#include "EpubReaderTocActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" @@ -131,65 +129,41 @@ void EpubReaderActivity::loop() { const int currentPage = section ? section->currentPage : 0; const int totalPages = section ? section->pageCount : 0; - // Show menu instead of direct chapter selection, to allow access to footnotes + // Show consolidated TOC activity (Chapters and Footnotes) exitActivity(); - enterNewActivity(new EpubReaderMenuActivity( - this->renderer, this->mappedInput, + enterNewActivity(new EpubReaderTocActivity( + this->renderer, this->mappedInput, epub, epub->getPath(), currentSpineIndex, currentPage, totalPages, + currentPageFootnotes, [this] { - // onGoBack from menu - updateRequired = true; - // Re-enter reader activity logic if needed (handled by stack) - // Actually ActivityWithSubactivity handles subActivity exit naturally + // onGoBack exitActivity(); + updateRequired = true; }, - [this, currentPage, totalPages](EpubReaderMenuActivity::MenuOption option) { - // onSelectOption - handle menu choice - if (option == EpubReaderMenuActivity::CHAPTERS) { - // Show chapter selection - exitActivity(); - enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->mappedInput, epub, epub->getPath(), currentSpineIndex, currentPage, totalPages, - [this] { - exitActivity(); - updateRequired = true; - }, - [this](int newSpineIndex) { - if (currentSpineIndex != newSpineIndex) { - currentSpineIndex = newSpineIndex; - nextPageNumber = 0; - section.reset(); - } - exitActivity(); - updateRequired = true; - }, - [this](int newSpineIndex, int newPage) { - // Handle sync position - if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { - currentSpineIndex = newSpineIndex; - nextPageNumber = newPage; - section.reset(); - } - exitActivity(); - updateRequired = true; - })); - } else if (option == EpubReaderMenuActivity::FOOTNOTES) { - // Show footnotes page with current page notes - exitActivity(); - enterNewActivity(new EpubReaderFootnotesActivity( - this->renderer, this->mappedInput, - currentPageFootnotes, // Pass collected footnotes (reference) - [this] { - // onGoBack from footnotes - exitActivity(); - updateRequired = true; - }, - [this](const char* href) { - // onSelectFootnote - navigate to the footnote location - navigateToHref(href, true); // true = save current position - exitActivity(); - updateRequired = true; - })); + [this](int newSpineIndex) { + // onSelectSpineIndex + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); } + exitActivity(); + updateRequired = true; + }, + [this](const char* href) { + // onSelectFootnote + navigateToHref(href, true); + exitActivity(); + updateRequired = true; + }, + [this](int newSpineIndex, int newPage) { + // onSyncPosition + if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { + currentSpineIndex = newSpineIndex; + nextPageNumber = newPage; + section.reset(); + } + exitActivity(); + updateRequired = true; })); xSemaphoreGive(renderingMutex); } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index b7497ce1..5033b896 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -5,7 +5,7 @@ #include #include -#include "EpubReaderFootnotesActivity.h" +#include "FootnotesTab.h" #include "activities/ActivityWithSubactivity.h" class EpubReaderActivity final : public ActivityWithSubactivity { diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp deleted file mode 100644 index 3492a2f6..00000000 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include "EpubReaderChapterSelectionActivity.h" - -#include - -#include "KOReaderCredentialStore.h" -#include "KOReaderSyncActivity.h" -#include "MappedInputManager.h" -#include "fontIds.h" - -namespace { -// Time threshold for treating a long press as a page-up/page-down -constexpr int SKIP_PAGE_MS = 700; -} // namespace - -bool EpubReaderChapterSelectionActivity::hasSyncOption() const { return KOREADER_STORE.hasCredentials(); } - -int EpubReaderChapterSelectionActivity::getTotalItems() const { - // Add 2 for sync options (top and bottom) if credentials are configured - const int syncCount = hasSyncOption() ? 2 : 0; - return epub->getTocItemsCount() + syncCount; -} - -bool EpubReaderChapterSelectionActivity::isSyncItem(int index) const { - if (!hasSyncOption()) return false; - // First item and last item are sync options - return index == 0 || index == getTotalItems() - 1; -} - -int EpubReaderChapterSelectionActivity::tocIndexFromItemIndex(int itemIndex) const { - // Account for the sync option at the top - const int offset = hasSyncOption() ? 1 : 0; - return itemIndex - offset; -} - -int EpubReaderChapterSelectionActivity::getPageItems() const { - // Layout constants used in renderScreen - constexpr int startY = 60; - constexpr int lineHeight = 30; - - const int screenHeight = renderer.getScreenHeight(); - const int endY = screenHeight - lineHeight; - - const int availableHeight = endY - startY; - int items = availableHeight / lineHeight; - - // Ensure we always have at least one item per page to avoid division by zero - if (items < 1) { - items = 1; - } - return items; -} - -void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) { - auto* self = static_cast(param); - self->displayTaskLoop(); -} - -void EpubReaderChapterSelectionActivity::buildFilteredChapterList() { - filteredSpineIndices.clear(); - - for (int i = 0; i < epub->getSpineItemsCount(); i++) { - // Skip footnote pages - if (epub->shouldHideFromToc(i)) { - Serial.printf("[%lu] [CHAP] Hiding footnote page at spine index: %d\n", millis(), i); - continue; - } - - // Skip pages without TOC entry (unnamed pages) - int tocIndex = epub->getTocIndexForSpineIndex(i); - if (tocIndex == -1) { - Serial.printf("[%lu] [CHAP] Hiding unnamed page at spine index: %d\n", millis(), i); - continue; - } - - filteredSpineIndices.push_back(i); - } - - Serial.printf("[%lu] [CHAP] Filtered chapters: %d out of %d\n", millis(), filteredSpineIndices.size(), - epub->getSpineItemsCount()); -} - -void EpubReaderChapterSelectionActivity::onEnter() { - ActivityWithSubactivity::onEnter(); - - if (!epub) { - return; - } - - renderingMutex = xSemaphoreCreateMutex(); - - // Build filtered chapter list (excluding footnote pages) - buildFilteredChapterList(); - - // Find the index in filtered list that corresponds to currentSpineIndex - selectorIndex = 0; - for (size_t i = 0; i < filteredSpineIndices.size(); i++) { - if (filteredSpineIndices[i] == currentSpineIndex) { - selectorIndex = i; - break; - } - } - - // Account for sync option offset when finding current TOC index (if applicable) - // For simplicity, if we are using the filtered list, we might just put "Sync" at the top of THAT list? - // But wait, the filtered list is spine indices. - // The master logic used TOC indices directly. - // Let's adapt: We will display the filtered list. - // If sync is enabled, we prepend/append it to the selector range. - - if (hasSyncOption()) { - selectorIndex += 1; // Offset for top sync option - } - - // Trigger first update - updateRequired = true; - xTaskCreate(&EpubReaderChapterSelectionActivity::taskTrampoline, "EpubReaderChapterSelectionActivityTask", - 4096, // Stack size - this, // Parameters - 1, // Priority - &displayTaskHandle // Task handle - ); -} - -void EpubReaderChapterSelectionActivity::onExit() { - ActivityWithSubactivity::onExit(); - - // Wait until not rendering to delete task to avoid killing mid-instruction to EPD - xSemaphoreTake(renderingMutex, portMAX_DELAY); - if (displayTaskHandle) { - vTaskDelete(displayTaskHandle); - displayTaskHandle = nullptr; - } - vSemaphoreDelete(renderingMutex); - renderingMutex = nullptr; -} - -void EpubReaderChapterSelectionActivity::launchSyncActivity() { - xSemaphoreTake(renderingMutex, portMAX_DELAY); - exitActivity(); - enterNewActivity(new KOReaderSyncActivity( - renderer, mappedInput, epub, epubPath, currentSpineIndex, currentPage, totalPagesInSpine, - [this]() { - // On cancel - exitActivity(); - updateRequired = true; - }, - [this](int newSpineIndex, int newPage) { - // On sync complete - exitActivity(); - onSyncPosition(newSpineIndex, newPage); - })); - xSemaphoreGive(renderingMutex); -} - -void EpubReaderChapterSelectionActivity::loop() { - if (subActivity) { - subActivity->loop(); - return; - } - - const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || - mappedInput.wasReleased(MappedInputManager::Button::Left); - const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || - mappedInput.wasReleased(MappedInputManager::Button::Right); - - const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; - const int pageItems = getPageItems(); - - // Total items = filtered chapters + sync options - const int syncCount = hasSyncOption() ? 2 : 0; - const int totalItems = filteredSpineIndices.size() + syncCount; - - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - // Check if sync option is selected - if (hasSyncOption()) { - if (selectorIndex == 0 || selectorIndex == totalItems - 1) { - launchSyncActivity(); - return; - } - } - - // It's a chapter. Calculate index in filtered list. - int filteredIndex = selectorIndex; - if (hasSyncOption()) filteredIndex -= 1; // Remove top sync offset - - if (filteredIndex >= 0 && filteredIndex < filteredSpineIndices.size()) { - onSelectSpineIndex(filteredSpineIndices[filteredIndex]); - } - } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { - onGoBack(); - } else if (prevReleased) { - if (skipPage) { - selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + totalItems) % totalItems; - } else { - selectorIndex = (selectorIndex + totalItems - 1) % totalItems; - } - updateRequired = true; - } else if (nextReleased) { - if (skipPage) { - selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % totalItems; - } else { - selectorIndex = (selectorIndex + 1) % totalItems; - } - updateRequired = true; - } -} - -void EpubReaderChapterSelectionActivity::displayTaskLoop() { - while (true) { - if (updateRequired && !subActivity) { - updateRequired = false; - xSemaphoreTake(renderingMutex, portMAX_DELAY); - renderScreen(); - xSemaphoreGive(renderingMutex); - } - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -void EpubReaderChapterSelectionActivity::renderScreen() { - renderer.clearScreen(); - - const auto pageWidth = renderer.getScreenWidth(); - const int pageItems = getPageItems(); - const int totalItems = getTotalItems(); - - const std::string title = - renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD); - - const auto pageStartIndex = selectorIndex / pageItems * pageItems; - renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); - - for (int i = 0; i < pageItems; i++) { - int itemIndex = pageStartIndex + i; - if (itemIndex >= totalItems) break; - - const int displayY = 60 + i * 30; - const bool isSelected = (itemIndex == selectorIndex); - - if (isSyncItem(itemIndex)) { - renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected); - } else { - const int tocIndex = tocIndexFromItemIndex(itemIndex); - - if (tocIndex == -1) { - renderer.drawText(UI_10_FONT_ID, 20, displayY, "Unnamed", !isSelected); - } else { - // Master's rendering logic - auto item = epub->getTocItem(tocIndex); - - const int indentSize = 20 + (item.level - 1) * 15; - const std::string chapterName = - renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); - - renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); - } - } - } - - const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); - renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - - renderer.displayBuffer(); -} diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.h b/src/activities/reader/EpubReaderChapterSelectionActivity.h deleted file mode 100644 index 8726b453..00000000 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include -#include - -#include "../ActivityWithSubactivity.h" - -class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity { - std::shared_ptr epub; - std::string epubPath; - TaskHandle_t displayTaskHandle = nullptr; - SemaphoreHandle_t renderingMutex = nullptr; - int currentSpineIndex = 0; - int currentPage = 0; - int totalPagesInSpine = 0; - int selectorIndex = 0; - bool updateRequired = false; - const std::function onGoBack; - const std::function onSelectSpineIndex; - const std::function onSyncPosition; - - // Number of items that fit on a page, derived from logical screen height. - // This adapts automatically when switching between portrait and landscape. - int getPageItems() const; - - // Total items including sync options (top and bottom) - int getTotalItems() const; - - // Check if sync option is available (credentials configured) - bool hasSyncOption() const; - - // Check if given item index is a sync option (first or last) - bool isSyncItem(int index) const; - - // Convert item index to TOC index (accounting for top sync option offset) - int tocIndexFromItemIndex(int itemIndex) const; - - // Filtered list of spine indices (excluding footnote pages) - std::vector filteredSpineIndices; - - static void taskTrampoline(void* param); - [[noreturn]] void displayTaskLoop(); - void renderScreen(); - void buildFilteredChapterList(); - void launchSyncActivity(); - - public: - explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, - const std::shared_ptr& epub, const std::string& epubPath, - const int currentSpineIndex, const int currentPage, - const int totalPagesInSpine, const std::function& onGoBack, - const std::function& onSelectSpineIndex, - const std::function& onSyncPosition) - : ActivityWithSubactivity("EpubReaderChapterSelection", renderer, mappedInput), - epub(epub), - epubPath(epubPath), - currentSpineIndex(currentSpineIndex), - currentPage(currentPage), - totalPagesInSpine(totalPagesInSpine), - onGoBack(onGoBack), - onSelectSpineIndex(onSelectSpineIndex), - onSyncPosition(onSyncPosition) {} - void onEnter() override; - void onExit() override; - void loop() override; -}; diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp deleted file mode 100644 index bc77c661..00000000 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "EpubReaderMenuActivity.h" - -#include -#include - -#include "MappedInputManager.h" -#include "fontIds.h" - -constexpr int MENU_ITEMS_COUNT = 2; - -void EpubReaderMenuActivity::taskTrampoline(void* param) { - auto* self = static_cast(param); - self->displayTaskLoop(); -} - -void EpubReaderMenuActivity::onEnter() { - renderingMutex = xSemaphoreCreateMutex(); - selectorIndex = 0; - - // Trigger first update - updateRequired = true; - xTaskCreate(&EpubReaderMenuActivity::taskTrampoline, "EpubReaderMenuTask", - 2048, // Stack size - this, // Parameters - 1, // Priority - &displayTaskHandle // Task handle - ); -} - -void EpubReaderMenuActivity::onExit() { - // Wait until not rendering to delete task to avoid killing mid-instruction to EPD - xSemaphoreTake(renderingMutex, portMAX_DELAY); - if (displayTaskHandle) { - vTaskDelete(displayTaskHandle); - displayTaskHandle = nullptr; - } - vSemaphoreDelete(renderingMutex); - renderingMutex = nullptr; -} - -void EpubReaderMenuActivity::loop() { - const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || - mappedInput.wasReleased(MappedInputManager::Button::Left); - const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || - mappedInput.wasReleased(MappedInputManager::Button::Right); - - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - onSelectOption(static_cast(selectorIndex)); - } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { - onGoBack(); - } else if (prevReleased) { - selectorIndex = (selectorIndex + MENU_ITEMS_COUNT - 1) % MENU_ITEMS_COUNT; - updateRequired = true; - } else if (nextReleased) { - selectorIndex = (selectorIndex + 1) % MENU_ITEMS_COUNT; - updateRequired = true; - } -} - -void EpubReaderMenuActivity::displayTaskLoop() { - while (true) { - if (updateRequired) { - updateRequired = false; - xSemaphoreTake(renderingMutex, portMAX_DELAY); - renderScreen(); - xSemaphoreGive(renderingMutex); - } - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} - -void EpubReaderMenuActivity::renderScreen() { - renderer.clearScreen(); - - const auto pageWidth = renderer.getScreenWidth(); - renderer.drawCenteredText(UI_12_FONT_ID, 10, "Menu", true, EpdFontFamily::BOLD); - - const char* menuItems[MENU_ITEMS_COUNT] = {"Go to chapter", "View footnotes"}; - - const int startY = 100; - const int itemHeight = 40; - - for (int i = 0; i < MENU_ITEMS_COUNT; i++) { - const int y = startY + i * itemHeight; - - // Draw selection indicator - if (i == selectorIndex) { - renderer.fillRect(10, y + 2, pageWidth - 20, itemHeight - 4); - renderer.drawText(UI_12_FONT_ID, 30, y, menuItems[i], false); - } else { - renderer.drawText(UI_12_FONT_ID, 30, y, menuItems[i], true); - } - } - - renderer.displayBuffer(); -} diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h deleted file mode 100644 index 64706a46..00000000 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include -#include - -#include "../Activity.h" - -class EpubReaderMenuActivity final : public Activity { - public: - enum MenuOption { CHAPTERS, FOOTNOTES }; - - private: - TaskHandle_t displayTaskHandle = nullptr; - SemaphoreHandle_t renderingMutex = nullptr; - int selectorIndex = 0; - bool updateRequired = false; - const std::function onGoBack; - const std::function onSelectOption; - - static void taskTrampoline(void* param); - [[noreturn]] void displayTaskLoop(); - void renderScreen(); - - public: - explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, - const std::function& onGoBack, - const std::function& onSelectOption) - : Activity("EpubReaderMenu", renderer, mappedInput), onGoBack(onGoBack), onSelectOption(onSelectOption) {} - - void onEnter() override; - void onExit() override; - void loop() override; -}; diff --git a/src/activities/reader/EpubReaderTocActivity.cpp b/src/activities/reader/EpubReaderTocActivity.cpp new file mode 100644 index 00000000..e3369369 --- /dev/null +++ b/src/activities/reader/EpubReaderTocActivity.cpp @@ -0,0 +1,132 @@ +#include "EpubReaderTocActivity.h" + +#include +#include + +#include "KOReaderSyncActivity.h" +#include "MappedInputManager.h" +#include "ScreenComponents.h" +#include "fontIds.h" + +namespace { +constexpr int TAB_BAR_Y = 15; +constexpr int CONTENT_START_Y = 60; +} // namespace + +void EpubReaderTocActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void EpubReaderTocActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + renderingMutex = xSemaphoreCreateMutex(); + + chaptersTab->onEnter(); + footnotesTab->onEnter(); + + updateRequired = true; + xTaskCreate(&EpubReaderTocActivity::taskTrampoline, "EpubReaderTocTask", 4096, this, 1, &displayTaskHandle); +} + +void EpubReaderTocActivity::onExit() { + ActivityWithSubactivity::onExit(); + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void EpubReaderTocActivity::launchSyncActivity() { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new KOReaderSyncActivity( + renderer, mappedInput, epub, epubPath, currentSpineIndex, currentPage, totalPagesInSpine, + [this]() { + // On cancel + exitActivity(); + updateRequired = true; + }, + [this](int newSpineIndex, int newPage) { + // On sync complete + exitActivity(); + onSyncPosition(newSpineIndex, newPage); + })); + xSemaphoreGive(renderingMutex); +} + +void EpubReaderTocActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onGoBack(); + return; + } + + const bool leftReleased = mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool rightReleased = mappedInput.wasReleased(MappedInputManager::Button::Right); + + if (leftReleased && currentTab == Tab::FOOTNOTES) { + currentTab = Tab::CHAPTERS; + updateRequired = true; + return; + } + if (rightReleased && currentTab == Tab::CHAPTERS) { + currentTab = Tab::FOOTNOTES; + updateRequired = true; + return; + } + + getCurrentTab()->loop(); + if (getCurrentTab()->isUpdateRequired()) { + updateRequired = true; + } +} + +void EpubReaderTocActivity::displayTaskLoop() { + while (true) { + if (updateRequired && !subActivity) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void EpubReaderTocActivity::renderScreen() { + renderer.clearScreen(); + + // Draw tab bar + std::vector tabs = {{"Chapters", currentTab == Tab::CHAPTERS}, {"Footnotes", currentTab == Tab::FOOTNOTES}}; + ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs); + + const int screenHeight = renderer.getScreenHeight(); + const int contentHeight = screenHeight - CONTENT_START_Y - 60; + + getCurrentTab()->render(CONTENT_START_Y, contentHeight); + + // Draw scroll indicator + ScreenComponents::drawScrollIndicator(renderer, getCurrentTab()->getCurrentPage(), getCurrentTab()->getTotalPages(), + CONTENT_START_Y, contentHeight); + + // Draw button hints + const auto labels = mappedInput.mapLabels("« Back", "Select", "< Tab", "Tab >"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<"); + + renderer.displayBuffer(); +} + +TocTab* EpubReaderTocActivity::getCurrentTab() const { + return (currentTab == Tab::CHAPTERS) ? static_cast(chaptersTab.get()) + : static_cast(footnotesTab.get()); +} diff --git a/src/activities/reader/EpubReaderTocActivity.h b/src/activities/reader/EpubReaderTocActivity.h new file mode 100644 index 00000000..fce847a4 --- /dev/null +++ b/src/activities/reader/EpubReaderTocActivity.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include + +#include +#include + +#include "../ActivityWithSubactivity.h" +#include "ChaptersTab.h" +#include "FootnotesTab.h" + +class EpubReaderTocActivity final : public ActivityWithSubactivity { + public: + enum class Tab { CHAPTERS, FOOTNOTES }; + + private: + std::shared_ptr epub; + std::string epubPath; + const FootnotesData& footnotes; + + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + + int currentSpineIndex = 0; + int currentPage = 0; + int totalPagesInSpine = 0; + + Tab currentTab = Tab::CHAPTERS; + std::unique_ptr chaptersTab; + std::unique_ptr footnotesTab; + + bool updateRequired = false; + + const std::function onGoBack; + const std::function onSelectSpineIndex; + const std::function onSelectFootnote; + const std::function onSyncPosition; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void renderScreen(); + TocTab* getCurrentTab() const; + + public: + EpubReaderTocActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub, + const std::string& epubPath, int currentSpineIndex, int currentPage, int totalPagesInSpine, + const FootnotesData& footnotes, std::function onGoBack, + std::function onSelectSpineIndex, std::function onSelectFootnote, + std::function onSyncPosition) + : ActivityWithSubactivity("EpubReaderToc", renderer, mappedInput), + epub(epub), + epubPath(epubPath), + currentSpineIndex(currentSpineIndex), + currentPage(currentPage), + totalPagesInSpine(totalPagesInSpine), + footnotes(footnotes), + onGoBack(onGoBack), + onSelectSpineIndex(onSelectSpineIndex), + onSelectFootnote(onSelectFootnote), + onSyncPosition(onSyncPosition) { + chaptersTab = std::unique_ptr(new ChaptersTab( + renderer, mappedInput, epub, currentSpineIndex, + [this](int spineIndex) { this->onSelectSpineIndex(spineIndex); }, + [this]() { this->launchSyncActivity(); })); + footnotesTab = std::unique_ptr(new FootnotesTab( + renderer, mappedInput, footnotes, [this](const char* href) { this->onSelectFootnote(href); })); + } + + void onEnter() override; + void onExit() override; + void loop() override; + + void launchSyncActivity(); +}; diff --git a/src/activities/reader/EpubReaderFootnotesActivity.cpp b/src/activities/reader/FootnotesTab.cpp similarity index 51% rename from src/activities/reader/EpubReaderFootnotesActivity.cpp rename to src/activities/reader/FootnotesTab.cpp index b61bcd22..d374aca8 100644 --- a/src/activities/reader/EpubReaderFootnotesActivity.cpp +++ b/src/activities/reader/FootnotesTab.cpp @@ -1,4 +1,4 @@ -#include "EpubReaderFootnotesActivity.h" +#include "FootnotesTab.h" #include #include @@ -6,30 +6,16 @@ #include "MappedInputManager.h" #include "fontIds.h" -void EpubReaderFootnotesActivity::onEnter() { +namespace { +constexpr int LINE_HEIGHT = 40; +} + +void FootnotesTab::onEnter() { selectedIndex = 0; - render(); + updateRequired = true; } -void EpubReaderFootnotesActivity::onExit() { - // Nothing to clean up -} - -void EpubReaderFootnotesActivity::loop() { - if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { - onGoBack(); - return; - } - - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - const FootnoteEntry* entry = footnotes.getEntry(selectedIndex); - if (entry) { - Serial.printf("[%lu] [FNS] Selected footnote: %s -> %s\n", millis(), entry->number, entry->href); - onSelectFootnote(entry->href); - } - return; - } - +void FootnotesTab::loop() { bool needsRedraw = false; if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { @@ -46,35 +32,32 @@ void EpubReaderFootnotesActivity::loop() { } } + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + const FootnoteEntry* entry = footnotes.getEntry(selectedIndex); + if (entry) { + onSelectFootnote(entry->href); + } + } + if (needsRedraw) { - render(); + updateRequired = true; } } -void EpubReaderFootnotesActivity::render() { - renderer.clearScreen(); - - constexpr int startY = 50; - constexpr int lineHeight = 40; - constexpr int marginLeft = 20; - - // Title - renderer.drawText(UI_12_FONT_ID, marginLeft, 20, "Footnotes", EpdFontFamily::BOLD); +void FootnotesTab::render(int contentTop, int contentHeight) { + const int marginLeft = 20; if (footnotes.getCount() == 0) { - renderer.drawText(SMALL_FONT_ID, marginLeft, startY + 20, "No footnotes on this page"); - renderer.displayBuffer(); + renderer.drawText(SMALL_FONT_ID, marginLeft, contentTop + 20, "No footnotes on this page"); return; } - // Display footnotes for (int i = 0; i < footnotes.getCount(); i++) { const FootnoteEntry* entry = footnotes.getEntry(i); if (!entry) continue; - const int y = startY + i * lineHeight; + const int y = contentTop + i * LINE_HEIGHT; - // Draw selection indicator (arrow) if (i == selectedIndex) { renderer.drawText(UI_12_FONT_ID, marginLeft - 10, y, ">", EpdFontFamily::BOLD); renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number, EpdFontFamily::BOLD); @@ -82,10 +65,7 @@ void EpubReaderFootnotesActivity::render() { renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number); } } - - // Instructions at bottom - renderer.drawText(SMALL_FONT_ID, marginLeft, renderer.getScreenHeight() - 40, - "UP/DOWN: Select CONFIRM: Go to footnote BACK: Return"); - - renderer.displayBuffer(); } + +int FootnotesTab::getCurrentPage() const { return 1; } +int FootnotesTab::getTotalPages() const { return 1; } diff --git a/src/activities/reader/EpubReaderFootnotesActivity.h b/src/activities/reader/FootnotesTab.h similarity index 58% rename from src/activities/reader/EpubReaderFootnotesActivity.h rename to src/activities/reader/FootnotesTab.h index e9fe4b64..fb5f5856 100644 --- a/src/activities/reader/EpubReaderFootnotesActivity.h +++ b/src/activities/reader/FootnotesTab.h @@ -1,10 +1,9 @@ #pragma once #include #include -#include #include "../../lib/Epub/Epub/FootnoteEntry.h" -#include "../Activity.h" +#include "TocTab.h" class FootnotesData { private: @@ -47,26 +46,24 @@ class FootnotesData { } }; -class EpubReaderFootnotesActivity final : public Activity { +class FootnotesTab final : public TocTab { const FootnotesData& footnotes; - const std::function onGoBack; - const std::function onSelectFootnote; - int selectedIndex; + int selectedIndex = 0; + bool updateRequired = false; + + const std::function onSelectFootnote; public: - EpubReaderFootnotesActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const FootnotesData& footnotes, - const std::function& onGoBack, - const std::function& onSelectFootnote) - : Activity("EpubReaderFootnotes", renderer, mappedInput), - footnotes(footnotes), - onGoBack(onGoBack), - onSelectFootnote(onSelectFootnote), - selectedIndex(0) {} + FootnotesTab(GfxRenderer& renderer, MappedInputManager& mappedInput, const FootnotesData& footnotes, + std::function onSelectFootnote) + : TocTab(renderer, mappedInput), footnotes(footnotes), onSelectFootnote(onSelectFootnote) {} void onEnter() override; - void onExit() override; void loop() override; + void render(int contentTop, int contentHeight) override; - private: - void render(); -}; \ No newline at end of file + int getCurrentPage() const override; + int getTotalPages() const override; + bool isUpdateRequired() const override { return updateRequired; } + void clearUpdateRequired() override { updateRequired = false; } +}; diff --git a/src/activities/reader/TocTab.h b/src/activities/reader/TocTab.h new file mode 100644 index 00000000..37deb969 --- /dev/null +++ b/src/activities/reader/TocTab.h @@ -0,0 +1,24 @@ +#pragma once + +class GfxRenderer; +class MappedInputManager; + +class TocTab { + protected: + GfxRenderer& renderer; + MappedInputManager& mappedInput; + + public: + TocTab(GfxRenderer& renderer, MappedInputManager& mappedInput) : renderer(renderer), mappedInput(mappedInput) {} + virtual ~TocTab() = default; + + virtual void onEnter() = 0; + virtual void onExit() {} + virtual void loop() = 0; + virtual void render(int contentTop, int contentHeight) = 0; + + virtual int getCurrentPage() const = 0; + virtual int getTotalPages() const = 0; + virtual bool isUpdateRequired() const = 0; + virtual void clearUpdateRequired() = 0; +}; From 5894ae5afe728efec73e8ab1ceaf1b0bfe4216cc Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Tue, 27 Jan 2026 17:32:33 +0100 Subject: [PATCH 08/13] chore: .gitignore: add compile_commands.json & .cache (#568) ## Summary * **What is the goal of this PR?** Quality of Life * **What changes are included?** Add compile_commands.json & .cache to .gitignore . Both are use by clangd that can help IDE support. Run `pio run --target compiledb` to generate `compile_commands.json`. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 754c9f68..ec281eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ lib/EpdFont/fontsrc *.generated.h .vs build -**/__pycache__/ \ No newline at end of file +**/__pycache__/ +/compile_commands.json +/.cache From 712c566664577020a7ee382531346d51c464c35f Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 28 Jan 2026 03:33:36 +1100 Subject: [PATCH 09/13] fix: Correctly render italics on image alt placeholders (#569) ## Summary * Correctly render italics on image alt placeholders * Parser incorrectly handled depth of self-closing tags * Self-closing tags immediately call start and end tag ## Additional Context * Previously, it would incorrectly make the whole chapter bold/italics, or not italicised the image alt --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? No --- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 86 ++++++++++++------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index f6d96be4..298c4ec6 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -84,41 +84,42 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (strcmp(name, "table") == 0) { // Add placeholder text self->startNewTextBlock(TextBlock::CENTER_ALIGN); - if (self->currentTextBlock) { - self->currentTextBlock->addWord("[Table omitted]", EpdFontFamily::ITALIC); - } - // Skip table contents - self->skipUntilDepth = self->depth; + self->italicUntilDepth = min(self->italicUntilDepth, self->depth); + // Advance depth before processing character data (like you would for a element with text) self->depth += 1; + self->characterData(userData, "[Table omitted]", strlen("[Table omitted]")); + + // Skip table contents (skip until parent as we pre-advanced depth above) + self->skipUntilDepth = self->depth - 1; return; } if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) { // TODO: Start processing image tags - std::string alt; + std::string alt = "[Image]"; if (atts != nullptr) { for (int i = 0; atts[i]; i += 2) { if (strcmp(atts[i], "alt") == 0) { - // add " " (counts as whitespace) at the end of alt - // so the corresponding text block ends. - // TODO: A zero-width breaking space would be more appropriate (once/if we support it) - alt = "[Image: " + std::string(atts[i + 1]) + "] "; + if (strlen(atts[i + 1]) > 0) { + alt = "[Image: " + std::string(atts[i + 1]) + "]"; + } + break; } } - Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str()); - - self->startNewTextBlock(TextBlock::CENTER_ALIGN); - self->italicUntilDepth = min(self->italicUntilDepth, self->depth); - self->depth += 1; - self->characterData(userData, alt.c_str(), alt.length()); - return; - } else { - // Skip for now - self->skipUntilDepth = self->depth; - self->depth += 1; - return; } + + Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str()); + + self->startNewTextBlock(TextBlock::CENTER_ALIGN); + self->italicUntilDepth = min(self->italicUntilDepth, self->depth); + // Advance depth before processing character data (like you would for a element with text) + self->depth += 1; + self->characterData(userData, alt.c_str(), alt.length()); + + // Skip table contents (skip until parent as we pre-advanced depth above) + self->skipUntilDepth = self->depth - 1; + return; } if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) { @@ -143,25 +144,43 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) { self->startNewTextBlock(TextBlock::CENTER_ALIGN); self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth); - } else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) { + self->depth += 1; + return; + } + + if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) { if (strcmp(name, "br") == 0) { if (self->partWordBufferIndex > 0) { // flush word preceding
to currentTextBlock before calling startNewTextBlock self->flushPartWordBuffer(); } self->startNewTextBlock(self->currentTextBlock->getStyle()); - } else { - self->startNewTextBlock((TextBlock::Style)self->paragraphAlignment); - if (strcmp(name, "li") == 0) { - self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR); - } + self->depth += 1; + return; } - } else if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) { - self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth); - } else if (matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS)) { - self->italicUntilDepth = std::min(self->italicUntilDepth, self->depth); + + self->startNewTextBlock(static_cast(self->paragraphAlignment)); + if (strcmp(name, "li") == 0) { + self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR); + } + + self->depth += 1; + return; } + if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) { + self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth); + self->depth += 1; + return; + } + + if (matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS)) { + self->italicUntilDepth = std::min(self->italicUntilDepth, self->depth); + self->depth += 1; + return; + } + + // Unprocessed tag, just increasing depth and continue forward self->depth += 1; } @@ -227,7 +246,8 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n // text styling needs to be overhauled to fix it. const bool shouldBreakText = matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS) || matches(name, HEADER_TAGS, NUM_HEADER_TAGS) || - matches(name, BOLD_TAGS, NUM_BOLD_TAGS) || matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS) || self->depth == 1; + matches(name, BOLD_TAGS, NUM_BOLD_TAGS) || matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS) || + strcmp(name, "table") == 0 || matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS) || self->depth == 1; if (shouldBreakText) { self->flushPartWordBuffer(); From ebcd813ff6c49ccf9bafb3146b51d2c0d2278170 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 28 Jan 2026 04:08:04 +1100 Subject: [PATCH 10/13] chore: Cut release 0.16.0 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7f42637d..e8574470 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ default_envs = default [crosspoint] -version = 0.15.0 +version = 0.16.0 [base] platform = espressif32 @ 6.12.0 From 172916afd41e6f92ee32970055a2c982a7d6b3ec Mon Sep 17 00:00:00 2001 From: Eliz Date: Tue, 27 Jan 2026 17:25:03 +0000 Subject: [PATCH 11/13] feat: Display epub metadata on Recents (#511) * **What is the goal of this PR?** Implement a metadata viewer for the Recents screen * **What changes are included?** | Recents | Files | | --- | --- | | image | image | For the Files screen, I have not made any changes on purpose. For the Recents screen, we now display the Book title and author. If it is a file with no epub metadata like txt or md, we display the file name without the file extension. --- Did you use AI tools to help write this code? _**< YES >**_ Although I went trough all the code manually and made changes as well, please be aware the majority of the code is AI generated. --------- Co-authored-by: Eliz Kilic --- src/RecentBooksStore.cpp | 58 ++++++++++------ src/RecentBooksStore.h | 18 +++-- src/activities/home/MyLibraryActivity.cpp | 72 +++++++++++--------- src/activities/home/MyLibraryActivity.h | 4 +- src/activities/reader/EpubReaderActivity.cpp | 2 +- src/activities/reader/TxtReaderActivity.cpp | 2 +- src/activities/reader/XtcReaderActivity.cpp | 2 +- 7 files changed, 95 insertions(+), 63 deletions(-) diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index 03cfbbd7..5932de36 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -7,22 +7,23 @@ #include namespace { -constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 1; +constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 2; constexpr char RECENT_BOOKS_FILE[] = "/.crosspoint/recent.bin"; constexpr int MAX_RECENT_BOOKS = 10; } // namespace RecentBooksStore RecentBooksStore::instance; -void RecentBooksStore::addBook(const std::string& path) { +void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author) { // Remove existing entry if present - auto it = std::find(recentBooks.begin(), recentBooks.end(), path); + auto it = + std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { recentBooks.erase(it); } // Add to front - recentBooks.insert(recentBooks.begin(), path); + recentBooks.insert(recentBooks.begin(), {path, title, author}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -46,7 +47,9 @@ bool RecentBooksStore::saveToFile() const { serialization::writePod(outputFile, count); for (const auto& book : recentBooks) { - serialization::writeString(outputFile, book); + serialization::writeString(outputFile, book.path); + serialization::writeString(outputFile, book.title); + serialization::writeString(outputFile, book.author); } outputFile.close(); @@ -63,24 +66,41 @@ bool RecentBooksStore::loadFromFile() { uint8_t version; serialization::readPod(inputFile, version); if (version != RECENT_BOOKS_FILE_VERSION) { - Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version); - inputFile.close(); - return false; - } + if (version == 1) { + // Old version, just read paths + uint8_t count; + serialization::readPod(inputFile, count); + recentBooks.clear(); + recentBooks.reserve(count); + for (uint8_t i = 0; i < count; i++) { + std::string path; + serialization::readString(inputFile, path); + // Title and author will be empty, they will be filled when the book is + // opened again + recentBooks.push_back({path, "", ""}); + } + } else { + Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version); + inputFile.close(); + return false; + } + } else { + uint8_t count; + serialization::readPod(inputFile, count); - uint8_t count; - serialization::readPod(inputFile, count); + recentBooks.clear(); + recentBooks.reserve(count); - recentBooks.clear(); - recentBooks.reserve(count); - - for (uint8_t i = 0; i < count; i++) { - std::string path; - serialization::readString(inputFile, path); - recentBooks.push_back(path); + for (uint8_t i = 0; i < count; i++) { + std::string path, title, author; + serialization::readString(inputFile, path); + serialization::readString(inputFile, title); + serialization::readString(inputFile, author); + recentBooks.push_back({path, title, author}); + } } inputFile.close(); - Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), count); + Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size()); return true; } diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index b98bd406..7b87f1e0 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -2,11 +2,19 @@ #include #include +struct RecentBook { + std::string path; + std::string title; + std::string author; + + bool operator==(const RecentBook& other) const { return path == other.path; } +}; + class RecentBooksStore { // Static instance static RecentBooksStore instance; - std::vector recentBooks; + std::vector recentBooks; public: ~RecentBooksStore() = default; @@ -14,11 +22,11 @@ class RecentBooksStore { // Get singleton instance static RecentBooksStore& getInstance() { return instance; } - // Add a book path to the recent list (moves to front if already exists) - void addBook(const std::string& path); + // Add a book to the recent list (moves to front if already exists) + void addBook(const std::string& path, const std::string& title, const std::string& author); - // Get the list of recent book paths (most recent first) - const std::vector& getBooks() const { return recentBooks; } + // Get the list of recent books (most recent first) + const std::vector& getBooks() const { return recentBooks; } // Get the count of recent books int getCount() const { return static_cast(recentBooks.size()); } diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index 1db32397..29c6ea73 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -16,6 +16,7 @@ namespace { constexpr int TAB_BAR_Y = 15; constexpr int CONTENT_START_Y = 60; constexpr int LINE_HEIGHT = 30; +constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items constexpr int LEFT_MARGIN = 20; constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator @@ -47,7 +48,7 @@ int MyLibraryActivity::getPageItems() const { int MyLibraryActivity::getCurrentItemCount() const { if (currentTab == Tab::Recent) { - return static_cast(bookTitles.size()); + return static_cast(recentBooks.size()); } return static_cast(files.size()); } @@ -65,34 +66,16 @@ int MyLibraryActivity::getCurrentPage() const { } void MyLibraryActivity::loadRecentBooks() { - constexpr size_t MAX_RECENT_BOOKS = 20; - - bookTitles.clear(); - bookPaths.clear(); + recentBooks.clear(); const auto& books = RECENT_BOOKS.getBooks(); - bookTitles.reserve(std::min(books.size(), MAX_RECENT_BOOKS)); - bookPaths.reserve(std::min(books.size(), MAX_RECENT_BOOKS)); - - for (const auto& path : books) { - // Limit to maximum number of recent books - if (bookTitles.size() >= MAX_RECENT_BOOKS) { - break; - } + recentBooks.reserve(books.size()); + for (const auto& book : books) { // Skip if file no longer exists - if (!SdMan.exists(path.c_str())) { + if (!SdMan.exists(book.path.c_str())) { continue; } - - // Extract filename from path for display - std::string title = path; - const size_t lastSlash = title.find_last_of('/'); - if (lastSlash != std::string::npos) { - title = title.substr(lastSlash + 1); - } - - bookTitles.push_back(title); - bookPaths.push_back(path); + recentBooks.push_back(book); } } @@ -176,8 +159,6 @@ void MyLibraryActivity::onExit() { vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; - bookTitles.clear(); - bookPaths.clear(); files.clear(); } @@ -207,8 +188,8 @@ void MyLibraryActivity::loop() { // Confirm button - open selected item if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (currentTab == Tab::Recent) { - if (!bookPaths.empty() && selectorIndex < static_cast(bookPaths.size())) { - onSelectBook(bookPaths[selectorIndex], currentTab); + if (!recentBooks.empty() && selectorIndex < static_cast(recentBooks.size())) { + onSelectBook(recentBooks[selectorIndex].path, currentTab); } } else { // Files tab @@ -333,7 +314,7 @@ void MyLibraryActivity::render() const { void MyLibraryActivity::renderRecentTab() const { const auto pageWidth = renderer.getScreenWidth(); const int pageItems = getPageItems(); - const int bookCount = static_cast(bookTitles.size()); + const int bookCount = static_cast(recentBooks.size()); if (bookCount == 0) { renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books"); @@ -343,14 +324,37 @@ void MyLibraryActivity::renderRecentTab() const { const auto pageStartIndex = selectorIndex / pageItems * pageItems; // Draw selection highlight - renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN, - LINE_HEIGHT); + renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2, + pageWidth - RIGHT_MARGIN, RECENTS_LINE_HEIGHT); // Draw items for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) { - auto item = renderer.truncatedText(UI_10_FONT_ID, bookTitles[i].c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN); - renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + (i % pageItems) * LINE_HEIGHT, item.c_str(), - i != selectorIndex); + const auto& book = recentBooks[i]; + const int y = CONTENT_START_Y + (i % pageItems) * RECENTS_LINE_HEIGHT; + + // Line 1: Title + std::string title = book.title; + if (title.empty()) { + // Fallback for older entries or files without metadata + title = book.path; + const size_t lastSlash = title.find_last_of('/'); + if (lastSlash != std::string::npos) { + title = title.substr(lastSlash + 1); + } + const size_t dot = title.find_last_of('.'); + if (dot != std::string::npos) { + title.resize(dot); + } + } + auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN); + renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, y + 2, truncatedTitle.c_str(), i != selectorIndex); + + // Line 2: Author + if (!book.author.empty()) { + auto truncatedAuthor = + renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN); + renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, y + 32, truncatedAuthor.c_str(), i != selectorIndex); + } } } diff --git a/src/activities/home/MyLibraryActivity.h b/src/activities/home/MyLibraryActivity.h index c6c52b68..39a27ed7 100644 --- a/src/activities/home/MyLibraryActivity.h +++ b/src/activities/home/MyLibraryActivity.h @@ -8,6 +8,7 @@ #include #include "../Activity.h" +#include "RecentBooksStore.h" class MyLibraryActivity final : public Activity { public: @@ -22,8 +23,7 @@ class MyLibraryActivity final : public Activity { bool updateRequired = false; // Recent tab state - std::vector bookTitles; // Display titles for each book - std::vector bookPaths; // Paths for each visible book (excludes missing) + std::vector recentBooks; // Files tab state (from FileSelectionActivity) std::string basepath = "/"; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 89be3bc7..509f2eaf 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -85,7 +85,7 @@ void EpubReaderActivity::onEnter() { // Save current epub as last opened epub and add to recent books APP_STATE.openEpubPath = epub->getPath(); APP_STATE.saveToFile(); - RECENT_BOOKS.addBook(epub->getPath()); + RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor()); // Trigger first update updateRequired = true; diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 7df083a6..e4978221 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -60,7 +60,7 @@ void TxtReaderActivity::onEnter() { // Save current txt as last opened file and add to recent books APP_STATE.openEpubPath = txt->getPath(); APP_STATE.saveToFile(); - RECENT_BOOKS.addBook(txt->getPath()); + RECENT_BOOKS.addBook(txt->getPath(), "", ""); // Trigger first update updateRequired = true; diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 9761e27d..c97f2094 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -45,7 +45,7 @@ void XtcReaderActivity::onEnter() { // Save current XTC as last opened book and add to recent books APP_STATE.openEpubPath = xtc->getPath(); APP_STATE.saveToFile(); - RECENT_BOOKS.addBook(xtc->getPath()); + RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), xtc->getAuthor()); // Trigger first update updateRequired = true; From 62f9a863c9e57952ca7dc90fd88e4611f096fac6 Mon Sep 17 00:00:00 2001 From: Uri Tauber Date: Tue, 27 Jan 2026 19:25:56 +0200 Subject: [PATCH 12/13] refactor: use static design for reader TOC to avoid memory fragmentation --- src/activities/reader/ChaptersTab.cpp | 153 ------------- src/activities/reader/ChaptersTab.h | 44 ---- src/activities/reader/EpubReaderActivity.h | 2 +- .../reader/EpubReaderTocActivity.cpp | 209 ++++++++++++++++-- src/activities/reader/EpubReaderTocActivity.h | 47 ++-- .../{FootnotesTab.h => FootnotesData.h} | 27 +-- src/activities/reader/FootnotesTab.cpp | 71 ------ src/activities/reader/TocTab.h | 24 -- 8 files changed, 223 insertions(+), 354 deletions(-) delete mode 100644 src/activities/reader/ChaptersTab.cpp delete mode 100644 src/activities/reader/ChaptersTab.h rename src/activities/reader/{FootnotesTab.h => FootnotesData.h} (50%) delete mode 100644 src/activities/reader/FootnotesTab.cpp delete mode 100644 src/activities/reader/TocTab.h diff --git a/src/activities/reader/ChaptersTab.cpp b/src/activities/reader/ChaptersTab.cpp deleted file mode 100644 index df47d1fc..00000000 --- a/src/activities/reader/ChaptersTab.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "ChaptersTab.h" - -#include - -#include "KOReaderCredentialStore.h" -#include "MappedInputManager.h" -#include "fontIds.h" - -namespace { -constexpr int SKIP_PAGE_MS = 700; -constexpr int LINE_HEIGHT = 30; -} // namespace - -void ChaptersTab::onEnter() { - buildFilteredChapterList(); - - selectorIndex = 0; - for (size_t i = 0; i < filteredSpineIndices.size(); i++) { - if (filteredSpineIndices[i] == currentSpineIndex) { - selectorIndex = i; - break; - } - } - - if (hasSyncOption()) { - selectorIndex += 1; - } - updateRequired = true; -} - -bool ChaptersTab::hasSyncOption() const { return KOREADER_STORE.hasCredentials(); } - -int ChaptersTab::getTotalItems() const { - const int syncCount = hasSyncOption() ? 2 : 0; - return filteredSpineIndices.size() + syncCount; -} - -bool ChaptersTab::isSyncItem(int index) const { - if (!hasSyncOption()) return false; - return index == 0 || index == getTotalItems() - 1; -} - -int ChaptersTab::tocIndexFromItemIndex(int itemIndex) const { - const int offset = hasSyncOption() ? 1 : 0; - return itemIndex - offset; -} - -int ChaptersTab::getPageItems(int contentTop, int contentHeight) const { - int items = contentHeight / LINE_HEIGHT; - return (items < 1) ? 1 : items; -} - -void ChaptersTab::buildFilteredChapterList() { - filteredSpineIndices.clear(); - for (int i = 0; i < epub->getSpineItemsCount(); i++) { - if (epub->shouldHideFromToc(i)) continue; - int tocIndex = epub->getTocIndexForSpineIndex(i); - if (tocIndex == -1) continue; - filteredSpineIndices.push_back(i); - } -} - -void ChaptersTab::loop() { - const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up); - const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down); - const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; - const int totalItems = getTotalItems(); - - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - if (hasSyncOption() && (selectorIndex == 0 || selectorIndex == totalItems - 1)) { - onLaunchSync(); - return; - } - - int filteredIndex = selectorIndex; - if (hasSyncOption()) filteredIndex -= 1; - - if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { - onSelectSpineIndex(filteredSpineIndices[filteredIndex]); - } - } else if (upReleased) { - if (totalItems > 0) { - if (skipPage) { - // This logic matches MyLibraryActivity - // But for simplicity let's just do a page jump - } - selectorIndex = (selectorIndex + totalItems - 1) % totalItems; - updateRequired = true; - } - } else if (downReleased) { - if (totalItems > 0) { - selectorIndex = (selectorIndex + 1) % totalItems; - updateRequired = true; - } - } -} - -void ChaptersTab::render(int contentTop, int contentHeight) { - const auto pageWidth = renderer.getScreenWidth(); - const int pageItems = getPageItems(contentTop, contentHeight); - const int totalItems = getTotalItems(); - - const auto pageStartIndex = selectorIndex / pageItems * pageItems; - renderer.fillRect(0, contentTop + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT); - - for (int i = 0; i < pageItems; i++) { - int itemIndex = pageStartIndex + i; - if (itemIndex >= totalItems) break; - - const int displayY = contentTop + i * LINE_HEIGHT; - const bool isSelected = (itemIndex == selectorIndex); - - if (isSyncItem(itemIndex)) { - renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected); - } else { - int filteredIndex = itemIndex; - if (hasSyncOption()) filteredIndex -= 1; - - if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { - int spineIndex = filteredSpineIndices[filteredIndex]; - int tocIndex = epub->getTocIndexForSpineIndex(spineIndex); - - if (tocIndex == -1) { - renderer.drawText(UI_10_FONT_ID, 20, displayY, "Unnamed", !isSelected); - } else { - auto item = epub->getTocItem(tocIndex); - const int indentSize = 20 + (item.level - 1) * 15; - const std::string chapterName = - renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); - renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); - } - } - } - } -} - -int ChaptersTab::getCurrentPage() const { - // We don't have enough context here to know pageItems easily without contentHeight - // For now let's just return a placeholder or calculate it if we can. - // Actually onEnter can't know the height either if it's dynamic. - // Let's assume contentTop=60, contentHeight=screenHeight-120 - const int availableHeight = renderer.getScreenHeight() - 120; - const int itemsPerPage = availableHeight / LINE_HEIGHT; - return selectorIndex / (itemsPerPage > 0 ? itemsPerPage : 1) + 1; -} - -int ChaptersTab::getTotalPages() const { - const int availableHeight = renderer.getScreenHeight() - 120; - const int itemsPerPage = availableHeight / LINE_HEIGHT; - const int totalItems = getTotalItems(); - if (totalItems == 0) return 1; - return (totalItems + itemsPerPage - 1) / (itemsPerPage > 0 ? itemsPerPage : 1); -} diff --git a/src/activities/reader/ChaptersTab.h b/src/activities/reader/ChaptersTab.h deleted file mode 100644 index ae80107a..00000000 --- a/src/activities/reader/ChaptersTab.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include - -#include -#include -#include - -#include "TocTab.h" - -class ChaptersTab final : public TocTab { - std::shared_ptr epub; - int currentSpineIndex; - int selectorIndex = 0; - bool updateRequired = false; - std::vector filteredSpineIndices; - - const std::function onSelectSpineIndex; - const std::function onLaunchSync; - - int getPageItems(int contentTop, int contentHeight) const; - int getTotalItems() const; - bool hasSyncOption() const; - bool isSyncItem(int index) const; - int tocIndexFromItemIndex(int itemIndex) const; - void buildFilteredChapterList(); - - public: - ChaptersTab(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub, - int currentSpineIndex, std::function onSelectSpineIndex, std::function onLaunchSync) - : TocTab(renderer, mappedInput), - epub(epub), - currentSpineIndex(currentSpineIndex), - onSelectSpineIndex(onSelectSpineIndex), - onLaunchSync(onLaunchSync) {} - - void onEnter() override; - void loop() override; - void render(int contentTop, int contentHeight) override; - - int getCurrentPage() const override; - int getTotalPages() const override; - bool isUpdateRequired() const override { return updateRequired; } - void clearUpdateRequired() override { updateRequired = false; } -}; diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 5033b896..03ccab2d 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -5,7 +5,7 @@ #include #include -#include "FootnotesTab.h" +#include "FootnotesData.h" #include "activities/ActivityWithSubactivity.h" class EpubReaderActivity final : public ActivityWithSubactivity { diff --git a/src/activities/reader/EpubReaderTocActivity.cpp b/src/activities/reader/EpubReaderTocActivity.cpp index e3369369..0fbf43b3 100644 --- a/src/activities/reader/EpubReaderTocActivity.cpp +++ b/src/activities/reader/EpubReaderTocActivity.cpp @@ -3,6 +3,7 @@ #include #include +#include "KOReaderCredentialStore.h" #include "KOReaderSyncActivity.h" #include "MappedInputManager.h" #include "ScreenComponents.h" @@ -11,6 +12,9 @@ namespace { constexpr int TAB_BAR_Y = 15; constexpr int CONTENT_START_Y = 60; +constexpr int CHAPTER_LINE_HEIGHT = 30; +constexpr int FOOTNOTE_LINE_HEIGHT = 40; +constexpr int SKIP_PAGE_MS = 700; } // namespace void EpubReaderTocActivity::taskTrampoline(void* param) { @@ -22,8 +26,21 @@ void EpubReaderTocActivity::onEnter() { ActivityWithSubactivity::onEnter(); renderingMutex = xSemaphoreCreateMutex(); - chaptersTab->onEnter(); - footnotesTab->onEnter(); + // Init chapters state + buildFilteredChapterList(); + chaptersSelectorIndex = 0; + for (size_t i = 0; i < filteredSpineIndices.size(); i++) { + if (filteredSpineIndices[i] == currentSpineIndex) { + chaptersSelectorIndex = i; + break; + } + } + if (hasSyncOption()) { + chaptersSelectorIndex += 1; + } + + // Init footnotes state + footnotesSelectedIndex = 0; updateRequired = true; xTaskCreate(&EpubReaderTocActivity::taskTrampoline, "EpubReaderTocTask", 4096, this, 1, &displayTaskHandle); @@ -44,16 +61,14 @@ void EpubReaderTocActivity::launchSyncActivity() { xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); enterNewActivity(new KOReaderSyncActivity( - renderer, mappedInput, epub, epubPath, currentSpineIndex, currentPage, totalPagesInSpine, + renderer, mappedInput, this->epub, epubPath, currentSpineIndex, currentPage, totalPagesInSpine, [this]() { - // On cancel exitActivity(); - updateRequired = true; + this->updateRequired = true; }, [this](int newSpineIndex, int newPage) { - // On sync complete exitActivity(); - onSyncPosition(newSpineIndex, newPage); + this->onSyncPosition(newSpineIndex, newPage); })); xSemaphoreGive(renderingMutex); } @@ -83,8 +98,65 @@ void EpubReaderTocActivity::loop() { return; } - getCurrentTab()->loop(); - if (getCurrentTab()->isUpdateRequired()) { + if (currentTab == Tab::CHAPTERS) { + loopChapters(); + } else { + loopFootnotes(); + } +} + +void EpubReaderTocActivity::loopChapters() { + const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up); + const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down); + const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; + const int totalItems = getChaptersTotalItems(); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (hasSyncOption() && (chaptersSelectorIndex == 0 || chaptersSelectorIndex == totalItems - 1)) { + launchSyncActivity(); + return; + } + + int filteredIndex = chaptersSelectorIndex; + if (hasSyncOption()) filteredIndex -= 1; + + if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { + onSelectSpineIndex(filteredSpineIndices[filteredIndex]); + } + } else if (upReleased) { + if (totalItems > 0) { + chaptersSelectorIndex = (chaptersSelectorIndex + totalItems - 1) % totalItems; + updateRequired = true; + } + } else if (downReleased) { + if (totalItems > 0) { + chaptersSelectorIndex = (chaptersSelectorIndex + 1) % totalItems; + updateRequired = true; + } + } +} + +void EpubReaderTocActivity::loopFootnotes() { + bool needsRedraw = false; + if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { + if (footnotesSelectedIndex > 0) { + footnotesSelectedIndex--; + needsRedraw = true; + } + } + if (mappedInput.wasPressed(MappedInputManager::Button::Down)) { + if (footnotesSelectedIndex < footnotes.getCount() - 1) { + footnotesSelectedIndex++; + needsRedraw = true; + } + } + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + const FootnoteEntry* entry = footnotes.getEntry(footnotesSelectedIndex); + if (entry) { + onSelectFootnote(entry->href); + } + } + if (needsRedraw) { updateRequired = true; } } @@ -104,29 +176,128 @@ void EpubReaderTocActivity::displayTaskLoop() { void EpubReaderTocActivity::renderScreen() { renderer.clearScreen(); - // Draw tab bar std::vector tabs = {{"Chapters", currentTab == Tab::CHAPTERS}, {"Footnotes", currentTab == Tab::FOOTNOTES}}; ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs); const int screenHeight = renderer.getScreenHeight(); const int contentHeight = screenHeight - CONTENT_START_Y - 60; - getCurrentTab()->render(CONTENT_START_Y, contentHeight); + if (currentTab == Tab::CHAPTERS) { + renderChapters(CONTENT_START_Y, contentHeight); + } else { + renderFootnotes(CONTENT_START_Y, contentHeight); + } - // Draw scroll indicator - ScreenComponents::drawScrollIndicator(renderer, getCurrentTab()->getCurrentPage(), getCurrentTab()->getTotalPages(), - CONTENT_START_Y, contentHeight); + ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), CONTENT_START_Y, contentHeight); - // Draw button hints const auto labels = mappedInput.mapLabels("« Back", "Select", "< Tab", "Tab >"); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<"); renderer.displayBuffer(); } -TocTab* EpubReaderTocActivity::getCurrentTab() const { - return (currentTab == Tab::CHAPTERS) ? static_cast(chaptersTab.get()) - : static_cast(footnotesTab.get()); +void EpubReaderTocActivity::renderChapters(int contentTop, int contentHeight) { + const auto pageWidth = renderer.getScreenWidth(); + const int pageItems = getChaptersPageItems(contentHeight); + const int totalItems = getChaptersTotalItems(); + const auto pageStartIndex = chaptersSelectorIndex / pageItems * pageItems; + + renderer.fillRect(0, contentTop + (chaptersSelectorIndex % pageItems) * CHAPTER_LINE_HEIGHT - 2, pageWidth - 1, + CHAPTER_LINE_HEIGHT); + + for (int i = 0; i < pageItems; i++) { + int itemIndex = pageStartIndex + i; + if (itemIndex >= totalItems) break; + + const int displayY = contentTop + i * CHAPTER_LINE_HEIGHT; + const bool isSelected = (itemIndex == chaptersSelectorIndex); + + if (isSyncItem(itemIndex)) { + renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected); + } else { + int filteredIndex = itemIndex; + if (hasSyncOption()) filteredIndex -= 1; + + if (filteredIndex >= 0 && filteredIndex < static_cast(filteredSpineIndices.size())) { + int spineIndex = filteredSpineIndices[filteredIndex]; + int tocIndex = this->epub->getTocIndexForSpineIndex(spineIndex); + if (tocIndex == -1) { + renderer.drawText(UI_10_FONT_ID, 20, displayY, "Unnamed", !isSelected); + } else { + auto item = this->epub->getTocItem(tocIndex); + const int indentSize = 20 + (item.level - 1) * 15; + const std::string chapterName = + renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); + renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); + } + } + } + } +} + +void EpubReaderTocActivity::renderFootnotes(int contentTop, int contentHeight) { + const int marginLeft = 20; + if (footnotes.getCount() == 0) { + renderer.drawText(SMALL_FONT_ID, marginLeft, contentTop + 20, "No footnotes on this page"); + return; + } + for (int i = 0; i < footnotes.getCount(); i++) { + const FootnoteEntry* entry = footnotes.getEntry(i); + if (!entry) continue; + const int y = contentTop + i * FOOTNOTE_LINE_HEIGHT; + if (i == footnotesSelectedIndex) { + renderer.drawText(UI_12_FONT_ID, marginLeft - 10, y, ">", EpdFontFamily::BOLD); + renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number, EpdFontFamily::BOLD); + } else { + renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number); + } + } +} + +void EpubReaderTocActivity::buildFilteredChapterList() { + filteredSpineIndices.clear(); + for (int i = 0; i < this->epub->getSpineItemsCount(); i++) { + if (this->epub->shouldHideFromToc(i)) continue; + int tocIndex = this->epub->getTocIndexForSpineIndex(i); + if (tocIndex == -1) continue; + filteredSpineIndices.push_back(i); + } +} + +bool EpubReaderTocActivity::hasSyncOption() const { return KOREADER_STORE.hasCredentials(); } + +bool EpubReaderTocActivity::isSyncItem(int index) const { + if (!hasSyncOption()) return false; + return index == 0 || index == getChaptersTotalItems() - 1; +} + +int EpubReaderTocActivity::getChaptersTotalItems() const { + const int syncCount = hasSyncOption() ? 2 : 0; + return filteredSpineIndices.size() + syncCount; +} + +int EpubReaderTocActivity::getChaptersPageItems(int contentHeight) const { + int items = contentHeight / CHAPTER_LINE_HEIGHT; + return (items < 1) ? 1 : items; +} + +int EpubReaderTocActivity::getCurrentPage() const { + if (currentTab == Tab::CHAPTERS) { + const int availableHeight = renderer.getScreenHeight() - 120; + const int itemsPerPage = availableHeight / CHAPTER_LINE_HEIGHT; + return chaptersSelectorIndex / (itemsPerPage > 0 ? itemsPerPage : 1) + 1; + } + return 1; +} + +int EpubReaderTocActivity::getTotalPages() const { + if (currentTab == Tab::CHAPTERS) { + const int availableHeight = renderer.getScreenHeight() - 120; + const int itemsPerPage = availableHeight / CHAPTER_LINE_HEIGHT; + const int totalItems = getChaptersTotalItems(); + if (totalItems == 0) return 1; + return (totalItems + itemsPerPage - 1) / (itemsPerPage > 0 ? itemsPerPage : 1); + } + return 1; } diff --git a/src/activities/reader/EpubReaderTocActivity.h b/src/activities/reader/EpubReaderTocActivity.h index fce847a4..71a0f0fb 100644 --- a/src/activities/reader/EpubReaderTocActivity.h +++ b/src/activities/reader/EpubReaderTocActivity.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -7,8 +8,7 @@ #include #include "../ActivityWithSubactivity.h" -#include "ChaptersTab.h" -#include "FootnotesTab.h" +#include "FootnotesData.h" class EpubReaderTocActivity final : public ActivityWithSubactivity { public: @@ -27,11 +27,16 @@ class EpubReaderTocActivity final : public ActivityWithSubactivity { int totalPagesInSpine = 0; Tab currentTab = Tab::CHAPTERS; - std::unique_ptr chaptersTab; - std::unique_ptr footnotesTab; - bool updateRequired = false; + // Chapters tab state + int chaptersSelectorIndex = 0; + std::vector filteredSpineIndices; + + // Footnotes tab state + int footnotesSelectedIndex = 0; + + // Callbacks const std::function onGoBack; const std::function onSelectSpineIndex; const std::function onSelectFootnote; @@ -40,16 +45,33 @@ class EpubReaderTocActivity final : public ActivityWithSubactivity { static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void renderScreen(); - TocTab* getCurrentTab() const; + + // Tab-specific methods + void loopChapters(); + void loopFootnotes(); + void renderChapters(int contentTop, int contentHeight); + void renderFootnotes(int contentTop, int contentHeight); + + // Chapters helpers + void buildFilteredChapterList(); + bool hasSyncOption() const; + bool isSyncItem(int index) const; + int getChaptersTotalItems() const; + int getChaptersPageItems(int contentHeight) const; + int tocIndexFromItemIndex(int itemIndex) const; + + // Indicator helpers + int getCurrentPage() const; + int getTotalPages() const; public: - EpubReaderTocActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub, + EpubReaderTocActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub_ptr, const std::string& epubPath, int currentSpineIndex, int currentPage, int totalPagesInSpine, const FootnotesData& footnotes, std::function onGoBack, std::function onSelectSpineIndex, std::function onSelectFootnote, std::function onSyncPosition) : ActivityWithSubactivity("EpubReaderToc", renderer, mappedInput), - epub(epub), + epub(epub_ptr), epubPath(epubPath), currentSpineIndex(currentSpineIndex), currentPage(currentPage), @@ -58,14 +80,7 @@ class EpubReaderTocActivity final : public ActivityWithSubactivity { onGoBack(onGoBack), onSelectSpineIndex(onSelectSpineIndex), onSelectFootnote(onSelectFootnote), - onSyncPosition(onSyncPosition) { - chaptersTab = std::unique_ptr(new ChaptersTab( - renderer, mappedInput, epub, currentSpineIndex, - [this](int spineIndex) { this->onSelectSpineIndex(spineIndex); }, - [this]() { this->launchSyncActivity(); })); - footnotesTab = std::unique_ptr(new FootnotesTab( - renderer, mappedInput, footnotes, [this](const char* href) { this->onSelectFootnote(href); })); - } + onSyncPosition(onSyncPosition) {} void onEnter() override; void onExit() override; diff --git a/src/activities/reader/FootnotesTab.h b/src/activities/reader/FootnotesData.h similarity index 50% rename from src/activities/reader/FootnotesTab.h rename to src/activities/reader/FootnotesData.h index fb5f5856..9a35c40d 100644 --- a/src/activities/reader/FootnotesTab.h +++ b/src/activities/reader/FootnotesData.h @@ -1,9 +1,6 @@ #pragma once #include -#include - -#include "../../lib/Epub/Epub/FootnoteEntry.h" -#include "TocTab.h" +#include class FootnotesData { private: @@ -45,25 +42,3 @@ class FootnotesData { return nullptr; } }; - -class FootnotesTab final : public TocTab { - const FootnotesData& footnotes; - int selectedIndex = 0; - bool updateRequired = false; - - const std::function onSelectFootnote; - - public: - FootnotesTab(GfxRenderer& renderer, MappedInputManager& mappedInput, const FootnotesData& footnotes, - std::function onSelectFootnote) - : TocTab(renderer, mappedInput), footnotes(footnotes), onSelectFootnote(onSelectFootnote) {} - - void onEnter() override; - void loop() override; - void render(int contentTop, int contentHeight) override; - - int getCurrentPage() const override; - int getTotalPages() const override; - bool isUpdateRequired() const override { return updateRequired; } - void clearUpdateRequired() override { updateRequired = false; } -}; diff --git a/src/activities/reader/FootnotesTab.cpp b/src/activities/reader/FootnotesTab.cpp deleted file mode 100644 index d374aca8..00000000 --- a/src/activities/reader/FootnotesTab.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "FootnotesTab.h" - -#include -#include - -#include "MappedInputManager.h" -#include "fontIds.h" - -namespace { -constexpr int LINE_HEIGHT = 40; -} - -void FootnotesTab::onEnter() { - selectedIndex = 0; - updateRequired = true; -} - -void FootnotesTab::loop() { - bool needsRedraw = false; - - if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { - if (selectedIndex > 0) { - selectedIndex--; - needsRedraw = true; - } - } - - if (mappedInput.wasPressed(MappedInputManager::Button::Down)) { - if (selectedIndex < footnotes.getCount() - 1) { - selectedIndex++; - needsRedraw = true; - } - } - - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - const FootnoteEntry* entry = footnotes.getEntry(selectedIndex); - if (entry) { - onSelectFootnote(entry->href); - } - } - - if (needsRedraw) { - updateRequired = true; - } -} - -void FootnotesTab::render(int contentTop, int contentHeight) { - const int marginLeft = 20; - - if (footnotes.getCount() == 0) { - renderer.drawText(SMALL_FONT_ID, marginLeft, contentTop + 20, "No footnotes on this page"); - return; - } - - for (int i = 0; i < footnotes.getCount(); i++) { - const FootnoteEntry* entry = footnotes.getEntry(i); - if (!entry) continue; - - const int y = contentTop + i * LINE_HEIGHT; - - if (i == selectedIndex) { - renderer.drawText(UI_12_FONT_ID, marginLeft - 10, y, ">", EpdFontFamily::BOLD); - renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number, EpdFontFamily::BOLD); - } else { - renderer.drawText(UI_12_FONT_ID, marginLeft + 10, y, entry->number); - } - } -} - -int FootnotesTab::getCurrentPage() const { return 1; } -int FootnotesTab::getTotalPages() const { return 1; } diff --git a/src/activities/reader/TocTab.h b/src/activities/reader/TocTab.h deleted file mode 100644 index 37deb969..00000000 --- a/src/activities/reader/TocTab.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -class GfxRenderer; -class MappedInputManager; - -class TocTab { - protected: - GfxRenderer& renderer; - MappedInputManager& mappedInput; - - public: - TocTab(GfxRenderer& renderer, MappedInputManager& mappedInput) : renderer(renderer), mappedInput(mappedInput) {} - virtual ~TocTab() = default; - - virtual void onEnter() = 0; - virtual void onExit() {} - virtual void loop() = 0; - virtual void render(int contentTop, int contentHeight) = 0; - - virtual int getCurrentPage() const = 0; - virtual int getTotalPages() const = 0; - virtual bool isUpdateRequired() const = 0; - virtual void clearUpdateRequired() = 0; -}; From da4d3b5ea5d0141567ea598a332c92c9b1655e3e Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Tue, 27 Jan 2026 18:50:15 +0100 Subject: [PATCH 13/13] feat: add HalDisplay and HalGPIO (#522) ## Summary Extracted some changes from https://github.com/crosspoint-reader/crosspoint-reader/pull/500 to make reviewing easier This PR adds HAL (Hardware Abstraction Layer) for display and GPIO components, making it easier to write a stub or an emulated implementation of the hardware. SD card HAL will be added via another PR, because it's a bit more tricky. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** --- lib/GfxRenderer/GfxRenderer.cpp | 62 ++++++------ lib/GfxRenderer/GfxRenderer.h | 12 +-- lib/hal/HalDisplay.cpp | 51 ++++++++++ lib/hal/HalDisplay.h | 52 ++++++++++ lib/hal/HalGPIO.cpp | 55 +++++++++++ lib/hal/HalGPIO.h | 61 ++++++++++++ src/MappedInputManager.cpp | 48 +++++---- src/MappedInputManager.h | 8 +- src/activities/boot_sleep/SleepActivity.cpp | 6 +- src/activities/reader/EpubReaderActivity.cpp | 4 +- src/activities/reader/TxtReaderActivity.cpp | 4 +- src/activities/reader/XtcReaderActivity.cpp | 4 +- .../util/FullScreenMessageActivity.h | 6 +- src/main.cpp | 97 ++++++------------- 14 files changed, 322 insertions(+), 148 deletions(-) create mode 100644 lib/hal/HalDisplay.cpp create mode 100644 lib/hal/HalDisplay.h create mode 100644 lib/hal/HalGPIO.cpp create mode 100644 lib/hal/HalGPIO.h diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 1dbe8ee6..fa1c61c6 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -10,19 +10,19 @@ void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int // Logical portrait (480x800) → panel (800x480) // Rotation: 90 degrees clockwise *rotatedX = y; - *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; + *rotatedY = HalDisplay::DISPLAY_HEIGHT - 1 - x; break; } case LandscapeClockwise: { // Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right) - *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x; - *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y; + *rotatedX = HalDisplay::DISPLAY_WIDTH - 1 - x; + *rotatedY = HalDisplay::DISPLAY_HEIGHT - 1 - y; break; } case PortraitInverted: { // Logical portrait (480x800) → panel (800x480) // Rotation: 90 degrees counter-clockwise - *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - y; + *rotatedX = HalDisplay::DISPLAY_WIDTH - 1 - y; *rotatedY = x; break; } @@ -36,7 +36,7 @@ void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int } void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { - uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = display.getFrameBuffer(); // Early return if no framebuffer is set if (!frameBuffer) { @@ -49,14 +49,13 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { rotateCoordinates(x, y, &rotatedX, &rotatedY); // Bounds checking against physical panel dimensions - if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 || - rotatedY >= EInkDisplay::DISPLAY_HEIGHT) { + if (rotatedX < 0 || rotatedX >= HalDisplay::DISPLAY_WIDTH || rotatedY < 0 || rotatedY >= HalDisplay::DISPLAY_HEIGHT) { Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, rotatedX, rotatedY); return; } // Calculate byte position and bit position - const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); + const uint16_t byteIndex = rotatedY * HalDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first if (state) { @@ -164,7 +163,7 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co break; } // TODO: Rotate bits - einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height); + display.drawImage(bitmap, rotatedX, rotatedY, width, height); } void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, @@ -399,22 +398,20 @@ void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoi free(nodeX); } -void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); } +void GfxRenderer::clearScreen(const uint8_t color) const { display.clearScreen(color); } void GfxRenderer::invertScreen() const { - uint8_t* buffer = einkDisplay.getFrameBuffer(); + uint8_t* buffer = display.getFrameBuffer(); if (!buffer) { Serial.printf("[%lu] [GFX] !! No framebuffer in invertScreen\n", millis()); return; } - for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) { + for (int i = 0; i < HalDisplay::BUFFER_SIZE; i++) { buffer[i] = ~buffer[i]; } } -void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const { - einkDisplay.displayBuffer(refreshMode); -} +void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { display.displayBuffer(refreshMode); } std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth, const EpdFontFamily::Style style) const { @@ -433,13 +430,13 @@ int GfxRenderer::getScreenWidth() const { case Portrait: case PortraitInverted: // 480px wide in portrait logical coordinates - return EInkDisplay::DISPLAY_HEIGHT; + return HalDisplay::DISPLAY_HEIGHT; case LandscapeClockwise: case LandscapeCounterClockwise: // 800px wide in landscape logical coordinates - return EInkDisplay::DISPLAY_WIDTH; + return HalDisplay::DISPLAY_WIDTH; } - return EInkDisplay::DISPLAY_HEIGHT; + return HalDisplay::DISPLAY_HEIGHT; } int GfxRenderer::getScreenHeight() const { @@ -447,13 +444,13 @@ int GfxRenderer::getScreenHeight() const { case Portrait: case PortraitInverted: // 800px tall in portrait logical coordinates - return EInkDisplay::DISPLAY_WIDTH; + return HalDisplay::DISPLAY_WIDTH; case LandscapeClockwise: case LandscapeCounterClockwise: // 480px tall in landscape logical coordinates - return EInkDisplay::DISPLAY_HEIGHT; + return HalDisplay::DISPLAY_HEIGHT; } - return EInkDisplay::DISPLAY_WIDTH; + return HalDisplay::DISPLAY_WIDTH; } int GfxRenderer::getSpaceWidth(const int fontId) const { @@ -653,17 +650,18 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y } } -uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); } +uint8_t* GfxRenderer::getFrameBuffer() const { return display.getFrameBuffer(); } -size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; } +size_t GfxRenderer::getBufferSize() { return HalDisplay::BUFFER_SIZE; } -void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); } +// unused +// void GfxRenderer::grayscaleRevert() const { display.grayscaleRevert(); } -void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); } +void GfxRenderer::copyGrayscaleLsbBuffers() const { display.copyGrayscaleLsbBuffers(display.getFrameBuffer()); } -void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); } +void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(display.getFrameBuffer()); } -void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); } +void GfxRenderer::displayGrayBuffer() const { display.displayGrayBuffer(); } void GfxRenderer::freeBwBufferChunks() { for (auto& bwBufferChunk : bwBufferChunks) { @@ -681,7 +679,7 @@ void GfxRenderer::freeBwBufferChunks() { * Returns true if buffer was stored successfully, false if allocation failed. */ bool GfxRenderer::storeBwBuffer() { - const uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + const uint8_t* frameBuffer = display.getFrameBuffer(); if (!frameBuffer) { Serial.printf("[%lu] [GFX] !! No framebuffer in storeBwBuffer\n", millis()); return false; @@ -736,7 +734,7 @@ void GfxRenderer::restoreBwBuffer() { return; } - uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = display.getFrameBuffer(); if (!frameBuffer) { Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n", millis()); freeBwBufferChunks(); @@ -755,7 +753,7 @@ void GfxRenderer::restoreBwBuffer() { memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE); } - einkDisplay.cleanupGrayscaleBuffers(frameBuffer); + display.cleanupGrayscaleBuffers(frameBuffer); freeBwBufferChunks(); Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis()); @@ -766,9 +764,9 @@ void GfxRenderer::restoreBwBuffer() { * Use this when BW buffer was re-rendered instead of stored/restored. */ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const { - uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = display.getFrameBuffer(); if (frameBuffer) { - einkDisplay.cleanupGrayscaleBuffers(frameBuffer); + display.cleanupGrayscaleBuffers(frameBuffer); } } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index b1fea69b..733975f4 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include @@ -21,11 +21,11 @@ class GfxRenderer { private: static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory - static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE; - static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE, + static constexpr size_t BW_BUFFER_NUM_CHUNKS = HalDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE; + static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == HalDisplay::BUFFER_SIZE, "BW buffer chunking does not line up with display buffer size"); - EInkDisplay& einkDisplay; + HalDisplay& display; RenderMode renderMode; Orientation orientation; uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; @@ -36,7 +36,7 @@ class GfxRenderer { void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const; public: - explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {} + explicit GfxRenderer(HalDisplay& halDisplay) : display(halDisplay), renderMode(BW), orientation(Portrait) {} ~GfxRenderer() { freeBwBufferChunks(); } static constexpr int VIEWABLE_MARGIN_TOP = 9; @@ -54,7 +54,7 @@ class GfxRenderer { // Screen ops int getScreenWidth() const; int getScreenHeight() const; - void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const; + void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const; // EXPERIMENTAL: Windowed update - display only a rectangular region void displayWindow(int x, int y, int width, int height) const; void invertScreen() const; diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp new file mode 100644 index 00000000..6f69d7fc --- /dev/null +++ b/lib/hal/HalDisplay.cpp @@ -0,0 +1,51 @@ +#include +#include + +#define SD_SPI_MISO 7 + +HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY) {} + +HalDisplay::~HalDisplay() {} + +void HalDisplay::begin() { einkDisplay.begin(); } + +void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); } + +void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + bool fromProgmem) const { + einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem); +} + +EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) { + switch (mode) { + case HalDisplay::FULL_REFRESH: + return EInkDisplay::FULL_REFRESH; + case HalDisplay::HALF_REFRESH: + return EInkDisplay::HALF_REFRESH; + case HalDisplay::FAST_REFRESH: + default: + return EInkDisplay::FAST_REFRESH; + } +} + +void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode) { einkDisplay.displayBuffer(convertRefreshMode(mode)); } + +void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) { + einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen); +} + +void HalDisplay::deepSleep() { einkDisplay.deepSleep(); } + +uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); } + +void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) { + einkDisplay.copyGrayscaleBuffers(lsbBuffer, msbBuffer); +} + +void HalDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { einkDisplay.copyGrayscaleLsbBuffers(lsbBuffer); } + +void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay.copyGrayscaleMsbBuffers(msbBuffer); } + +void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); } + +void HalDisplay::displayGrayBuffer() { einkDisplay.displayGrayBuffer(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h new file mode 100644 index 00000000..6eb7156b --- /dev/null +++ b/lib/hal/HalDisplay.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +class HalDisplay { + public: + // Constructor with pin configuration + HalDisplay(); + + // Destructor + ~HalDisplay(); + + // Refresh modes + enum RefreshMode { + FULL_REFRESH, // Full refresh with complete waveform + HALF_REFRESH, // Half refresh (1720ms) - balanced quality and speed + FAST_REFRESH // Fast refresh using custom LUT + }; + + // Initialize the display hardware and driver + void begin(); + + // Display dimensions + static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH; + static constexpr uint16_t DISPLAY_HEIGHT = EInkDisplay::DISPLAY_HEIGHT; + static constexpr uint16_t DISPLAY_WIDTH_BYTES = DISPLAY_WIDTH / 8; + static constexpr uint32_t BUFFER_SIZE = DISPLAY_WIDTH_BYTES * DISPLAY_HEIGHT; + + // Frame buffer operations + void clearScreen(uint8_t color = 0xFF) const; + void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + bool fromProgmem = false) const; + + void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH); + void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false); + + // Power management + void deepSleep(); + + // Access to frame buffer + uint8_t* getFrameBuffer() const; + + void copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer); + void copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer); + void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer); + void cleanupGrayscaleBuffers(const uint8_t* bwBuffer); + + void displayGrayBuffer(); + + private: + EInkDisplay einkDisplay; +}; diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp new file mode 100644 index 00000000..803efba0 --- /dev/null +++ b/lib/hal/HalGPIO.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +void HalGPIO::begin() { + inputMgr.begin(); + SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS); + pinMode(BAT_GPIO0, INPUT); + pinMode(UART0_RXD, INPUT); +} + +void HalGPIO::update() { inputMgr.update(); } + +bool HalGPIO::isPressed(uint8_t buttonIndex) const { return inputMgr.isPressed(buttonIndex); } + +bool HalGPIO::wasPressed(uint8_t buttonIndex) const { return inputMgr.wasPressed(buttonIndex); } + +bool HalGPIO::wasAnyPressed() const { return inputMgr.wasAnyPressed(); } + +bool HalGPIO::wasReleased(uint8_t buttonIndex) const { return inputMgr.wasReleased(buttonIndex); } + +bool HalGPIO::wasAnyReleased() const { return inputMgr.wasAnyReleased(); } + +unsigned long HalGPIO::getHeldTime() const { return inputMgr.getHeldTime(); } + +void HalGPIO::startDeepSleep() { + esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); + // Ensure that the power button has been released to avoid immediately turning back on if you're holding it + while (inputMgr.isPressed(BTN_POWER)) { + delay(50); + inputMgr.update(); + } + // Enter Deep Sleep + esp_deep_sleep_start(); +} + +int HalGPIO::getBatteryPercentage() const { + static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0); + return battery.readPercentage(); +} + +bool HalGPIO::isUsbConnected() const { + // U0RXD/GPIO20 reads HIGH when USB is connected + return digitalRead(UART0_RXD) == HIGH; +} + +bool HalGPIO::isWakeupByPowerButton() const { + const auto wakeupCause = esp_sleep_get_wakeup_cause(); + const auto resetReason = esp_reset_reason(); + if (isUsbConnected()) { + return wakeupCause == ESP_SLEEP_WAKEUP_GPIO; + } else { + return (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) && (resetReason == ESP_RST_POWERON); + } +} diff --git a/lib/hal/HalGPIO.h b/lib/hal/HalGPIO.h new file mode 100644 index 00000000..11ffb22e --- /dev/null +++ b/lib/hal/HalGPIO.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) +#define EPD_SCLK 8 // SPI Clock +#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In) +#define EPD_CS 21 // Chip Select +#define EPD_DC 4 // Data/Command +#define EPD_RST 5 // Reset +#define EPD_BUSY 6 // Busy + +#define SPI_MISO 7 // SPI MISO, shared between SD card and display (Master In Slave Out) + +#define BAT_GPIO0 0 // Battery voltage + +#define UART0_RXD 20 // Used for USB connection detection + +class HalGPIO { +#if CROSSPOINT_EMULATED == 0 + InputManager inputMgr; +#endif + + public: + HalGPIO() = default; + + // Start button GPIO and setup SPI for screen and SD card + void begin(); + + // Button input methods + void update(); + bool isPressed(uint8_t buttonIndex) const; + bool wasPressed(uint8_t buttonIndex) const; + bool wasAnyPressed() const; + bool wasReleased(uint8_t buttonIndex) const; + bool wasAnyReleased() const; + unsigned long getHeldTime() const; + + // Setup wake up GPIO and enter deep sleep + void startDeepSleep(); + + // Get battery percentage (range 0-100) + int getBatteryPercentage() const; + + // Check if USB is connected + bool isUsbConnected() const; + + // Check if wakeup was caused by power button press + bool isWakeupByPowerButton() const; + + // Button indices + static constexpr uint8_t BTN_BACK = 0; + static constexpr uint8_t BTN_CONFIRM = 1; + static constexpr uint8_t BTN_LEFT = 2; + static constexpr uint8_t BTN_RIGHT = 3; + static constexpr uint8_t BTN_UP = 4; + static constexpr uint8_t BTN_DOWN = 5; + static constexpr uint8_t BTN_POWER = 6; +}; diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 25095be7..e5423724 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -19,20 +19,20 @@ struct SideLayoutMap { // Order matches CrossPointSettings::FRONT_BUTTON_LAYOUT. constexpr FrontLayoutMap kFrontLayouts[] = { - {InputManager::BTN_BACK, InputManager::BTN_CONFIRM, InputManager::BTN_LEFT, InputManager::BTN_RIGHT}, - {InputManager::BTN_LEFT, InputManager::BTN_RIGHT, InputManager::BTN_BACK, InputManager::BTN_CONFIRM}, - {InputManager::BTN_CONFIRM, InputManager::BTN_LEFT, InputManager::BTN_BACK, InputManager::BTN_RIGHT}, - {InputManager::BTN_BACK, InputManager::BTN_CONFIRM, InputManager::BTN_RIGHT, InputManager::BTN_LEFT}, + {HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT}, + {HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT, HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM}, + {HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_BACK, HalGPIO::BTN_RIGHT}, + {HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_RIGHT, HalGPIO::BTN_LEFT}, }; // Order matches CrossPointSettings::SIDE_BUTTON_LAYOUT. constexpr SideLayoutMap kSideLayouts[] = { - {InputManager::BTN_UP, InputManager::BTN_DOWN}, - {InputManager::BTN_DOWN, InputManager::BTN_UP}, + {HalGPIO::BTN_UP, HalGPIO::BTN_DOWN}, + {HalGPIO::BTN_DOWN, HalGPIO::BTN_UP}, }; } // namespace -bool MappedInputManager::mapButton(const Button button, bool (InputManager::*fn)(uint8_t) const) const { +bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint8_t) const) const { const auto frontLayout = static_cast(SETTINGS.frontButtonLayout); const auto sideLayout = static_cast(SETTINGS.sideButtonLayout); const auto& front = kFrontLayouts[frontLayout]; @@ -40,41 +40,39 @@ bool MappedInputManager::mapButton(const Button button, bool (InputManager::*fn) switch (button) { case Button::Back: - return (inputManager.*fn)(front.back); + return (gpio.*fn)(front.back); case Button::Confirm: - return (inputManager.*fn)(front.confirm); + return (gpio.*fn)(front.confirm); case Button::Left: - return (inputManager.*fn)(front.left); + return (gpio.*fn)(front.left); case Button::Right: - return (inputManager.*fn)(front.right); + return (gpio.*fn)(front.right); case Button::Up: - return (inputManager.*fn)(InputManager::BTN_UP); + return (gpio.*fn)(HalGPIO::BTN_UP); case Button::Down: - return (inputManager.*fn)(InputManager::BTN_DOWN); + return (gpio.*fn)(HalGPIO::BTN_DOWN); case Button::Power: - return (inputManager.*fn)(InputManager::BTN_POWER); + return (gpio.*fn)(HalGPIO::BTN_POWER); case Button::PageBack: - return (inputManager.*fn)(side.pageBack); + return (gpio.*fn)(side.pageBack); case Button::PageForward: - return (inputManager.*fn)(side.pageForward); + return (gpio.*fn)(side.pageForward); } return false; } -bool MappedInputManager::wasPressed(const Button button) const { return mapButton(button, &InputManager::wasPressed); } +bool MappedInputManager::wasPressed(const Button button) const { return mapButton(button, &HalGPIO::wasPressed); } -bool MappedInputManager::wasReleased(const Button button) const { - return mapButton(button, &InputManager::wasReleased); -} +bool MappedInputManager::wasReleased(const Button button) const { return mapButton(button, &HalGPIO::wasReleased); } -bool MappedInputManager::isPressed(const Button button) const { return mapButton(button, &InputManager::isPressed); } +bool MappedInputManager::isPressed(const Button button) const { return mapButton(button, &HalGPIO::isPressed); } -bool MappedInputManager::wasAnyPressed() const { return inputManager.wasAnyPressed(); } +bool MappedInputManager::wasAnyPressed() const { return gpio.wasAnyPressed(); } -bool MappedInputManager::wasAnyReleased() const { return inputManager.wasAnyReleased(); } +bool MappedInputManager::wasAnyReleased() const { return gpio.wasAnyReleased(); } -unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); } +unsigned long MappedInputManager::getHeldTime() const { return gpio.getHeldTime(); } MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const { @@ -91,4 +89,4 @@ MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const default: return {back, confirm, previous, next}; } -} +} \ No newline at end of file diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h index bee7cd4b..f507a928 100644 --- a/src/MappedInputManager.h +++ b/src/MappedInputManager.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class MappedInputManager { public: @@ -13,7 +13,7 @@ class MappedInputManager { const char* btn4; }; - explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {} + explicit MappedInputManager(HalGPIO& gpio) : gpio(gpio) {} bool wasPressed(Button button) const; bool wasReleased(Button button) const; @@ -24,7 +24,7 @@ class MappedInputManager { Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const; private: - InputManager& inputManager; + HalGPIO& gpio; - bool mapButton(Button button, bool (InputManager::*fn)(uint8_t) const) const; + bool mapButton(Button button, bool (HalGPIO::*fn)(uint8_t) const) const; }; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 95fe742f..aace2095 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -133,7 +133,7 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.invertScreen(); } - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { @@ -189,7 +189,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { renderer.invertScreen(); } - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); if (hasGreyscale) { bitmap.rewindToData(); @@ -280,5 +280,5 @@ void SleepActivity::renderCoverSleepScreen() const { void SleepActivity::renderBlankSleepScreen() const { renderer.clearScreen(); - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 509f2eaf..58668c68 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -345,7 +345,7 @@ void EpubReaderActivity::renderScreen() { auto progressCallback = [this, barX, barY, barWidth, barHeight](int progress) { const int fillWidth = (barWidth - 2) * progress / 100; renderer.fillRect(barX + 1, barY + 1, fillWidth, barHeight - 2, true); - renderer.displayBuffer(EInkDisplay::FAST_REFRESH); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); }; if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), @@ -428,7 +428,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index e4978221..e9303de3 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -256,7 +256,7 @@ void TxtReaderActivity::buildPageIndex() { // Fill progress bar const int fillWidth = (barWidth - 2) * progressPercent / 100; renderer.fillRect(barX + 1, barY + 1, fillWidth, barHeight - 2, true); - renderer.displayBuffer(EInkDisplay::FAST_REFRESH); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); } // Yield to other tasks periodically @@ -484,7 +484,7 @@ void TxtReaderActivity::renderPage() { renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index c97f2094..f579abcd 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -276,7 +276,7 @@ void XtcReaderActivity::renderPage() { // Display BW with conditional refresh based on pagesUntilFullRefresh if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); @@ -356,7 +356,7 @@ void XtcReaderActivity::renderPage() { // Display with appropriate refresh if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(EInkDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); diff --git a/src/activities/util/FullScreenMessageActivity.h b/src/activities/util/FullScreenMessageActivity.h index 3e975c91..93909503 100644 --- a/src/activities/util/FullScreenMessageActivity.h +++ b/src/activities/util/FullScreenMessageActivity.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include #include #include @@ -10,12 +10,12 @@ class FullScreenMessageActivity final : public Activity { std::string text; EpdFontFamily::Style style; - EInkDisplay::RefreshMode refreshMode; + HalDisplay::RefreshMode refreshMode; public: explicit FullScreenMessageActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string text, const EpdFontFamily::Style style = EpdFontFamily::REGULAR, - const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) + const HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) : Activity("FullScreenMessage", renderer, mappedInput), text(std::move(text)), style(style), diff --git a/src/main.cpp b/src/main.cpp index 8a081fd8..2308f0a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ #include -#include #include #include -#include +#include +#include #include #include #include @@ -26,23 +26,10 @@ #include "activities/util/FullScreenMessageActivity.h" #include "fontIds.h" -#define SPI_FQ 40000000 -// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) -#define EPD_SCLK 8 // SPI Clock -#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In) -#define EPD_CS 21 // Chip Select -#define EPD_DC 4 // Data/Command -#define EPD_RST 5 // Reset -#define EPD_BUSY 6 // Busy - -#define UART0_RXD 20 // Used for USB connection detection - -#define SD_SPI_MISO 7 - -EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); -InputManager inputManager; -MappedInputManager mappedInputManager(inputManager); -GfxRenderer renderer(einkDisplay); +HalDisplay display; +HalGPIO gpio; +MappedInputManager mappedInputManager(gpio); +GfxRenderer renderer(display); Activity* currentActivity; // Fonts @@ -170,21 +157,20 @@ void verifyPowerButtonDuration() { const uint16_t calibratedPressDuration = (calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1; - inputManager.update(); - // Verify the user has actually pressed + gpio.update(); // Needed because inputManager.isPressed() may take up to ~500ms to return the correct state - while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) { + while (!gpio.isPressed(HalGPIO::BTN_POWER) && millis() - start < 1000) { delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration. - inputManager.update(); + gpio.update(); } t2 = millis(); - if (inputManager.isPressed(InputManager::BTN_POWER)) { + if (gpio.isPressed(HalGPIO::BTN_POWER)) { do { delay(10); - inputManager.update(); - } while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < calibratedPressDuration); - abort = inputManager.getHeldTime() < calibratedPressDuration; + gpio.update(); + } while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() < calibratedPressDuration); + abort = gpio.getHeldTime() < calibratedPressDuration; } else { abort = true; } @@ -192,16 +178,15 @@ void verifyPowerButtonDuration() { if (abort) { // Button released too early. Returning to sleep. // IMPORTANT: Re-arm the wakeup trigger before sleeping again - esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); - esp_deep_sleep_start(); + gpio.startDeepSleep(); } } void waitForPowerRelease() { - inputManager.update(); - while (inputManager.isPressed(InputManager::BTN_POWER)) { + gpio.update(); + while (gpio.isPressed(HalGPIO::BTN_POWER)) { delay(50); - inputManager.update(); + gpio.update(); } } @@ -210,14 +195,11 @@ void enterDeepSleep() { exitActivity(); enterNewActivity(new SleepActivity(renderer, mappedInputManager)); - einkDisplay.deepSleep(); + display.deepSleep(); Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1); Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis()); - esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); - // Ensure that the power button has been released to avoid immediately turning back on if you're holding it - waitForPowerRelease(); - // Enter Deep Sleep - esp_deep_sleep_start(); + + gpio.startDeepSleep(); } void onGoHome(); @@ -261,7 +243,7 @@ void onGoHome() { } void setupDisplayAndFonts() { - einkDisplay.begin(); + display.begin(); Serial.printf("[%lu] [ ] Display initialized\n", millis()); renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily); #ifndef OMIT_FONTS @@ -284,27 +266,13 @@ void setupDisplayAndFonts() { Serial.printf("[%lu] [ ] Fonts setup\n", millis()); } -bool isUsbConnected() { - // U0RXD/GPIO20 reads HIGH when USB is connected - return digitalRead(UART0_RXD) == HIGH; -} - -bool isWakeupByPowerButton() { - const auto wakeupCause = esp_sleep_get_wakeup_cause(); - const auto resetReason = esp_reset_reason(); - if (isUsbConnected()) { - return wakeupCause == ESP_SLEEP_WAKEUP_GPIO; - } else { - return (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) && (resetReason == ESP_RST_POWERON); - } -} - void setup() { t1 = millis(); + gpio.begin(); + // Only start serial if USB connected - pinMode(UART0_RXD, INPUT); - if (isUsbConnected()) { + if (gpio.isUsbConnected()) { Serial.begin(115200); // Wait up to 3 seconds for Serial to be ready to catch early logs unsigned long start = millis(); @@ -313,13 +281,6 @@ void setup() { } } - inputManager.begin(); - // Initialize pins - pinMode(BAT_GPIO0, INPUT); - - // Initialize SPI with custom pins - SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS); - // SD Card Initialization // We need 6 open files concurrently when parsing a new chapter if (!SdMan.begin()) { @@ -333,7 +294,7 @@ void setup() { SETTINGS.loadFromFile(); KOREADER_STORE.loadFromFile(); - if (isWakeupByPowerButton()) { + if (gpio.isWakeupByPowerButton()) { // For normal wakeups, verify power button press duration Serial.printf("[%lu] [ ] Verifying power button press duration\n", millis()); verifyPowerButtonDuration(); @@ -370,7 +331,7 @@ void loop() { const unsigned long loopStartTime = millis(); static unsigned long lastMemPrint = 0; - inputManager.update(); + gpio.update(); if (Serial && millis() - lastMemPrint >= 10000) { Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(), @@ -380,8 +341,7 @@ void loop() { // Check for any user activity (button press or release) or active background work static unsigned long lastActivityTime = millis(); - if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased() || - (currentActivity && currentActivity->preventAutoSleep())) { + if (gpio.wasAnyPressed() || gpio.wasAnyReleased() || (currentActivity && currentActivity->preventAutoSleep())) { lastActivityTime = millis(); // Reset inactivity timer } @@ -393,8 +353,7 @@ void loop() { return; } - if (inputManager.isPressed(InputManager::BTN_POWER) && - inputManager.getHeldTime() > SETTINGS.getPowerButtonDuration()) { + if (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() > SETTINGS.getPowerButtonDuration()) { enterDeepSleep(); // This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start return;