Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a328fd912d | ||
|
|
e3f2e27078 | ||
|
|
9bb488dc86 | ||
|
|
c331651e4e | ||
|
|
bfb8aacdae | ||
|
|
e027f60bf0 | ||
|
|
78c71f1c77 | ||
|
|
d364e54a69 | ||
|
|
3421171670 | ||
|
|
0848649ce2 |
@@ -23,9 +23,3 @@ lib/EpdFont/scripts/output/
|
|||||||
# (worktrees, scheduled-task locks, settings.local, scout CLEANUP.md) out.
|
# (worktrees, scheduled-task locks, settings.local, scout CLEANUP.md) out.
|
||||||
.claude/*
|
.claude/*
|
||||||
!.claude/skills/
|
!.claude/skills/
|
||||||
/managed_components
|
|
||||||
/.dummy
|
|
||||||
dependencies.lock
|
|
||||||
sdkconfig.default
|
|
||||||
sdkconfig.defaults
|
|
||||||
CMakeLists.txt
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
|
|||||||
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
||||||
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
|
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
|
||||||
| grep -v -E '^lib/uzlib/' \
|
| grep -v -E '^lib/uzlib/' \
|
||||||
| grep -v -E '^lib/miniz/third_party/' \
|
|
||||||
| xargs -r "${CLANG_FORMAT_BIN}" -style=file -i
|
| xargs -r "${CLANG_FORMAT_BIN}" -style=file -i
|
||||||
# Restore strict pipeline failure handling for the rest of the script.
|
# Restore strict pipeline failure handling for the rest of the script.
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|||||||
@@ -90,19 +90,13 @@ if (parsedSize != fileSize) {
|
|||||||
|
|
||||||
## `section.bin`
|
## `section.bin`
|
||||||
|
|
||||||
### Version 30
|
### Version 29
|
||||||
|
|
||||||
Each file in `sections/*.bin` stores one laid-out spine section. The header is
|
Each file in `sections/*.bin` stores one laid-out spine section. The header is
|
||||||
also the cache-busting key: if any layout-affecting setting differs from the
|
also the cache-busting key: if any layout-affecting setting differs from the
|
||||||
current reader settings, the section is discarded and rebuilt.
|
current reader settings, the section is discarded and rebuilt.
|
||||||
|
|
||||||
Version 30 is binary-identical to version 29. The version was bumped because
|
Version 29 includes:
|
||||||
Arabic contextual shaping changed text measurement (`getTextAdvanceX` now
|
|
||||||
measures the shaped visual text), so word positions cached by v29 no longer
|
|
||||||
match what `drawText` renders.
|
|
||||||
|
|
||||||
Version 28 introduced serialized word style bits for underline, strikethrough,
|
|
||||||
superscript, and subscript. The format also includes:
|
|
||||||
|
|
||||||
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
|
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
|
||||||
image rendering mode, and Focus Reading
|
image rendering mode, and Focus Reading
|
||||||
@@ -125,7 +119,7 @@ import std.mem;
|
|||||||
import std.string;
|
import std.string;
|
||||||
import std.core;
|
import std.core;
|
||||||
|
|
||||||
#define EXPECTED_VERSION 30
|
#define EXPECTED_VERSION 29
|
||||||
#define MAX_STRING_LENGTH 65535
|
#define MAX_STRING_LENGTH 65535
|
||||||
#define FOOTNOTE_NUMBER_LEN 32
|
#define FOOTNOTE_NUMBER_LEN 32
|
||||||
#define FOOTNOTE_HREF_LEN 96
|
#define FOOTNOTE_HREF_LEN 96
|
||||||
|
|||||||
+3
-8
@@ -32,14 +32,9 @@ networks or in hotspot mode when you control who is connected.
|
|||||||
## Join Network Mode
|
## Join Network Mode
|
||||||
|
|
||||||
1. Select **Join Network**.
|
1. Select **Join Network**.
|
||||||
2. If you have saved Wi-Fi credentials, CrossPoint first tries the last
|
2. Pick a 2.4 GHz Wi-Fi network from the scan results.
|
||||||
connected network, then other visible saved networks in signal-strength
|
3. Enter the password if prompted.
|
||||||
order. Press **Back** to cancel or **Confirm** to stop auto-connect and show
|
4. Save credentials if you want the reader to reconnect automatically next time.
|
||||||
the network list.
|
|
||||||
3. If the network list is shown, pick a 2.4 GHz Wi-Fi network from the scan
|
|
||||||
results.
|
|
||||||
4. Enter the password if prompted.
|
|
||||||
5. Save credentials if you want the reader to reconnect automatically next time.
|
|
||||||
|
|
||||||
After connection, the reader shows:
|
After connection, the reader shows:
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule freeink-sdk updated: 421d75d011...fdd6105eb3
+18
-4
@@ -44,16 +44,15 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const combiningMark::Anchor anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = isCombining ? combiningMark::raiseAboveBase(glyph->top, glyph->height, lastBaseTop) : 0;
|
||||||
const int raiseBy = isCombining ? combiningMark::raiseAboveBase(anchor, glyph->top, glyph->height, lastBaseTop) : 0;
|
|
||||||
|
|
||||||
if (!isCombining && prevCp != 0) {
|
if (!isCombining && prevCp != 0) {
|
||||||
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
|
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
|
||||||
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
|
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int glyphBaseX = isCombining ? combiningMark::anchorOver(anchor, lastBaseX, lastBaseLeft, lastBaseWidth,
|
const int glyphBaseX =
|
||||||
glyph->left, glyph->width)
|
isCombining ? combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width)
|
||||||
: lastBaseX;
|
: lastBaseX;
|
||||||
const int glyphBaseY = startY - raiseBy;
|
const int glyphBaseY = startY - raiseBy;
|
||||||
|
|
||||||
@@ -155,6 +154,21 @@ uint32_t EpdFont::applyLigatures(uint32_t cp, const char*& text) const {
|
|||||||
return cp;
|
return cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EpdFont::hasGlyph(const uint32_t cp) const {
|
||||||
|
const int count = data->intervalCount;
|
||||||
|
if (count > 0) {
|
||||||
|
const EpdUnicodeInterval* intervals = data->intervals;
|
||||||
|
const auto it =
|
||||||
|
std::upper_bound(intervals, intervals + count, cp,
|
||||||
|
[](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; });
|
||||||
|
if (it != intervals && cp <= (it - 1)->last) return true;
|
||||||
|
}
|
||||||
|
if (data->glyphMissHandler) {
|
||||||
|
return data->glyphMissHandler(data->glyphMissCtx, cp) != nullptr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
||||||
const int count = data->intervalCount;
|
const int count = data->intervalCount;
|
||||||
if (count == 0 && !data->glyphMissHandler) return nullptr;
|
if (count == 0 && !data->glyphMissHandler) return nullptr;
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ class EpdFont {
|
|||||||
|
|
||||||
const EpdGlyph* getGlyph(uint32_t cp) const;
|
const EpdGlyph* getGlyph(uint32_t cp) const;
|
||||||
|
|
||||||
|
/// True when the font actually provides `cp` (interval table or on-demand
|
||||||
|
/// SD load) — unlike getGlyph(), never satisfied by the replacement glyph.
|
||||||
|
/// Used by layout shaping to decide whether a substitution (ligature,
|
||||||
|
/// Arabic presentation form) can really be drawn.
|
||||||
|
bool hasGlyph(uint32_t cp) const;
|
||||||
|
|
||||||
/// Returns the kerning adjustment (4.4 fixed-point in pixels) between two codepoints.
|
/// Returns the kerning adjustment (4.4 fixed-point in pixels) between two codepoints.
|
||||||
/// Returns 0 if no kerning data exists for the pair.
|
/// Returns 0 if no kerning data exists for the pair.
|
||||||
int8_t getKerning(uint32_t leftCp, uint32_t rightCp) const;
|
int8_t getKerning(uint32_t leftCp, uint32_t rightCp) const;
|
||||||
|
|||||||
+10
-75
@@ -36,71 +36,24 @@ namespace combiningMark {
|
|||||||
|
|
||||||
constexpr int MIN_GAP_PX = 1;
|
constexpr int MIN_GAP_PX = 1;
|
||||||
|
|
||||||
/// Placement of a mark relative to its base glyph. The default heuristic —
|
|
||||||
/// centered over the base, raised clear of its top — suits Latin diacritics
|
|
||||||
/// and Arabic harakat, but misplaces the Hebrew niqqud whose identity depends
|
|
||||||
/// on position: dagesh sits inside the letter body, the shin/sin dots
|
|
||||||
/// distinguish the letter by sitting over its right/left arm, and holam hangs
|
|
||||||
/// over the left corner. "Native" anchors keep the glyph's font-designed
|
|
||||||
/// height (which may overlap the base) instead of raising it.
|
|
||||||
enum class Anchor : uint8_t {
|
|
||||||
CenterRaised, ///< centered over the base, lifted above its top (default)
|
|
||||||
CenterNative, ///< centered over the base at font-native height
|
|
||||||
RightNative, ///< right edges aligned, font-native height
|
|
||||||
LeftNative, ///< left edges aligned, font-native height
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Anchor anchorFor(const uint32_t cp) {
|
|
||||||
switch (cp) {
|
|
||||||
case 0x05BC: // dagesh / mapiq / shuruk dot: inside the letter body
|
|
||||||
case 0x05BA: // holam haser for vav: straight above the vav stem
|
|
||||||
return Anchor::CenterNative;
|
|
||||||
case 0x05C1: // shin dot: over the letter's right arm
|
|
||||||
return Anchor::RightNative;
|
|
||||||
case 0x05B9: // holam: above the letter's left corner
|
|
||||||
case 0x05C2: // sin dot: over the letter's left arm
|
|
||||||
return Anchor::LeftNative;
|
|
||||||
default:
|
|
||||||
return Anchor::CenterRaised;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Horizontal offset from the base bitmap's left edge to the mark bitmap's
|
|
||||||
/// left edge for a given anchor.
|
|
||||||
constexpr int anchorShift(const Anchor anchor, const int baseWidth, const int markWidth) {
|
|
||||||
switch (anchor) {
|
|
||||||
case Anchor::LeftNative:
|
|
||||||
return 0;
|
|
||||||
case Anchor::RightNative:
|
|
||||||
return baseWidth - markWidth;
|
|
||||||
default:
|
|
||||||
return baseWidth / 2 - markWidth / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the cursor-X at which to render a combining mark so its bitmap
|
/// Compute the cursor-X at which to render a combining mark so its bitmap
|
||||||
/// lands at its anchor position over the base glyph's bitmap.
|
/// is visually centered over the base glyph's bitmap.
|
||||||
constexpr int anchorOver(const Anchor anchor, const int baseCursorPos, const int baseLeft, const int baseWidth,
|
constexpr int centerOver(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) {
|
||||||
const int markLeft, const int markWidth) {
|
return baseCursorPos + baseLeft + baseWidth / 2 - markWidth / 2 - markLeft;
|
||||||
return baseCursorPos + baseLeft + anchorShift(anchor, baseWidth, markWidth) - markLeft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotated-90CW variant of anchorOver. In the rotated coordinate system
|
/// Rotated-90CW variant of centerOver. In the rotated coordinate system
|
||||||
/// renderCharImpl uses (cursorY - left) instead of (cursorX + left), so
|
/// renderCharImpl uses (cursorY - left) instead of (cursorX + left), so
|
||||||
/// every left/width term inverts sign.
|
/// every left/width term inverts sign.
|
||||||
constexpr int anchorOverRotated90CW(const Anchor anchor, const int baseCursorPos, const int baseLeft,
|
constexpr int centerOverRotated90CW(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) {
|
||||||
const int baseWidth, const int markLeft, const int markWidth) {
|
return baseCursorPos - baseLeft - baseWidth / 2 + markWidth / 2 + markLeft;
|
||||||
return baseCursorPos - baseLeft - anchorShift(anchor, baseWidth, markWidth) + markLeft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For combining marks that sit entirely above the baseline, compute how many
|
/// For combining marks that sit entirely above the baseline, compute how many
|
||||||
/// pixels to raise the mark so there is at least MIN_GAP_PX between its bottom
|
/// pixels to raise the mark so there is at least MIN_GAP_PX between its bottom
|
||||||
/// edge and the top of the base glyph. Returns 0 for marks that extend to or
|
/// edge and the top of the base glyph. Returns 0 for marks that extend to or
|
||||||
/// below the baseline (e.g. cedilla, dot-below, ogonek) and for anchors that
|
/// below the baseline (e.g. cedilla, dot-below, ogonek).
|
||||||
/// keep the font-native height (dagesh must stay inside the letter, the
|
constexpr int raiseAboveBase(int markTop, int markHeight, int baseTop) {
|
||||||
/// shin/sin dots touch its arms).
|
|
||||||
constexpr int raiseAboveBase(const Anchor anchor, const int markTop, const int markHeight, const int baseTop) {
|
|
||||||
if (anchor != Anchor::CenterRaised) return 0;
|
|
||||||
if (markTop - markHeight <= 0) return 0;
|
if (markTop - markHeight <= 0) return 0;
|
||||||
const int gap = markTop - markHeight - baseTop;
|
const int gap = markTop - markHeight - baseTop;
|
||||||
return (gap < MIN_GAP_PX) ? (MIN_GAP_PX - gap) : 0;
|
return (gap < MIN_GAP_PX) ? (MIN_GAP_PX - gap) : 0;
|
||||||
@@ -108,20 +61,6 @@ constexpr int raiseAboveBase(const Anchor anchor, const int markTop, const int m
|
|||||||
|
|
||||||
} // namespace combiningMark
|
} // namespace combiningMark
|
||||||
|
|
||||||
/// GCC/Clang (the ESP32 firmware toolchain) pack structs with __attribute__((packed)).
|
|
||||||
/// MSVC (host unit tests) has no equivalent attribute and instead needs a #pragma pack
|
|
||||||
/// region achieving the same 1-byte alignment. These macros keep the on-disk font layout
|
|
||||||
/// identical across both toolchains.
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define EPD_PACKED_BEGIN __pragma(pack(push, 1))
|
|
||||||
#define EPD_PACKED_END __pragma(pack(pop))
|
|
||||||
#define EPD_PACKED_ATTR
|
|
||||||
#else
|
|
||||||
#define EPD_PACKED_BEGIN
|
|
||||||
#define EPD_PACKED_END
|
|
||||||
#define EPD_PACKED_ATTR __attribute__((packed))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Fixed-point conventions used by EpdGlyph and EpdFontData:
|
/// Fixed-point conventions used by EpdGlyph and EpdFontData:
|
||||||
/// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel)
|
/// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel)
|
||||||
/// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel)
|
/// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel)
|
||||||
@@ -156,21 +95,17 @@ typedef struct {
|
|||||||
|
|
||||||
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
||||||
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t codepoint; ///< Unicode codepoint
|
uint16_t codepoint; ///< Unicode codepoint
|
||||||
uint8_t classId; ///< 1-based kerning class ID
|
uint8_t classId; ///< 1-based kerning class ID
|
||||||
} EPD_PACKED_ATTR EpdKernClassEntry;
|
} __attribute__((packed)) EpdKernClassEntry;
|
||||||
EPD_PACKED_END
|
|
||||||
|
|
||||||
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
||||||
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
||||||
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
||||||
} EPD_PACKED_ATTR EpdLigaturePair;
|
} __attribute__((packed)) EpdLigaturePair;
|
||||||
EPD_PACKED_END
|
|
||||||
|
|
||||||
/// Data stored for FONT AS A WHOLE
|
/// Data stored for FONT AS A WHOLE
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) co
|
|||||||
return getFont(style)->getGlyph(cp);
|
return getFont(style)->getGlyph(cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EpdFontFamily::hasGlyph(const uint32_t cp, const Style style) const { return getFont(style)->hasGlyph(cp); }
|
||||||
|
|
||||||
int8_t EpdFontFamily::getKerning(const uint32_t leftCp, const uint32_t rightCp, const Style style) const {
|
int8_t EpdFontFamily::getKerning(const uint32_t leftCp, const uint32_t rightCp, const Style style) const {
|
||||||
return getFont(style)->getKerning(leftCp, rightCp);
|
return getFont(style)->getKerning(leftCp, rightCp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t EpdFontFamily::getLigature(const uint32_t leftCp, const uint32_t rightCp, const Style style) const {
|
||||||
|
return getFont(style)->getLigature(leftCp, rightCp);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t EpdFontFamily::applyLigatures(const uint32_t cp, const char*& text, const Style style) const {
|
uint32_t EpdFontFamily::applyLigatures(const uint32_t cp, const char*& text, const Style style) const {
|
||||||
return getFont(style)->applyLigatures(cp, text);
|
return getFont(style)->applyLigatures(cp, text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ class EpdFontFamily {
|
|||||||
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
||||||
const EpdFontData* getData(Style style = REGULAR) const;
|
const EpdFontData* getData(Style style = REGULAR) const;
|
||||||
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
||||||
|
bool hasGlyph(uint32_t cp, Style style = REGULAR) const;
|
||||||
int8_t getKerning(uint32_t leftCp, uint32_t rightCp, Style style = REGULAR) const;
|
int8_t getKerning(uint32_t leftCp, uint32_t rightCp, Style style = REGULAR) const;
|
||||||
|
uint32_t getLigature(uint32_t leftCp, uint32_t rightCp, Style style = REGULAR) const;
|
||||||
uint32_t applyLigatures(uint32_t cp, const char*& text, Style style = REGULAR) const;
|
uint32_t applyLigatures(uint32_t cp, const char*& text, Style style = REGULAR) const;
|
||||||
static constexpr bool hasTextDecoration(const Style style) {
|
static constexpr bool hasTextDecoration(const Style style) {
|
||||||
return (static_cast<uint8_t>(style) & TEXT_DECORATION_MASK) != 0;
|
return (static_cast<uint8_t>(style) & TEXT_DECORATION_MASK) != 0;
|
||||||
|
|||||||
@@ -369,11 +369,6 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8
|
|||||||
}
|
}
|
||||||
stats.pageBufferBytes += totalBytes;
|
stats.pageBufferBytes += totalBytes;
|
||||||
stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry);
|
stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry);
|
||||||
// MEMFIX-PORT: page-slot address landmark for the heap map; portable
|
|
||||||
// Landmark for the heap block map: page slots are the largest flash-font
|
|
||||||
// allocations and otherwise show up as anonymous ~4-20 KB used blocks.
|
|
||||||
LOG_DBG("FDC", "page slot buffer=%p bytes=%u glyphs=%u", static_cast<void*>(slot.buffer), (unsigned)totalBytes,
|
|
||||||
(unsigned)glyphCount);
|
|
||||||
|
|
||||||
slot.fontData = fontData;
|
slot.fontData = fontData;
|
||||||
slot.glyphCount = glyphCount;
|
slot.glyphCount = glyphCount;
|
||||||
|
|||||||
+44
-81
@@ -68,22 +68,6 @@ bool collectUniqueCodepoints(const char* text, uint32_t* codepoints, uint32_t& c
|
|||||||
const char* asCStr(const std::string& s) { return s.c_str(); }
|
const char* asCStr(const std::string& s) { return s.c_str(); }
|
||||||
const char* asCStr(const char* s) { return s; }
|
const char* asCStr(const char* s) { return s; }
|
||||||
|
|
||||||
// Keep-if-fits buffer reuse: only reallocate when the needed size exceeds the
|
|
||||||
// current capacity. Freeing + reallocating slightly different sizes every page
|
|
||||||
// turn punches non-coalescing holes in the heap (the freed block rarely fits the
|
|
||||||
// next page's need), eroding the largest contiguous block all session. With
|
|
||||||
// reuse, capacities converge on the book's max page after a few turns and page
|
|
||||||
// turns stop touching the allocator. Only three small instantiations exist
|
|
||||||
// (interval/glyph/byte arrays), so template bloat is negligible.
|
|
||||||
template <typename T, typename CapT>
|
|
||||||
bool ensureArrayCapacity(T*& buf, CapT& capacity, const uint32_t needed) {
|
|
||||||
if (buf && capacity >= needed) return true;
|
|
||||||
delete[] buf;
|
|
||||||
buf = new (std::nothrow) T[needed > 0 ? needed : 1];
|
|
||||||
capacity = buf ? static_cast<CapT>(needed) : 0;
|
|
||||||
return buf != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SdCardFont::~SdCardFont() { freeAll(); }
|
SdCardFont::~SdCardFont() { freeAll(); }
|
||||||
@@ -99,9 +83,6 @@ void SdCardFont::freeStyleMiniData(PerStyle& s) {
|
|||||||
s.miniBitmap = nullptr;
|
s.miniBitmap = nullptr;
|
||||||
s.miniIntervalCount = 0;
|
s.miniIntervalCount = 0;
|
||||||
s.miniGlyphCount = 0;
|
s.miniGlyphCount = 0;
|
||||||
s.miniIntervalCapacity = 0;
|
|
||||||
s.miniGlyphCapacity = 0;
|
|
||||||
s.miniBitmapCapacity = 0;
|
|
||||||
freeStyleMiniKern(s);
|
freeStyleMiniKern(s);
|
||||||
memset(&s.miniData, 0, sizeof(s.miniData));
|
memset(&s.miniData, 0, sizeof(s.miniData));
|
||||||
s.epdFont.data = &s.stubData;
|
s.epdFont.data = &s.stubData;
|
||||||
@@ -128,9 +109,6 @@ void SdCardFont::freeStyleMiniKern(PerStyle& s) {
|
|||||||
s.miniKernRightEntryCount = 0;
|
s.miniKernRightEntryCount = 0;
|
||||||
s.miniKernLeftClassCount = 0;
|
s.miniKernLeftClassCount = 0;
|
||||||
s.miniKernRightClassCount = 0;
|
s.miniKernRightClassCount = 0;
|
||||||
s.miniKernLeftCapacity = 0;
|
|
||||||
s.miniKernRightCapacity = 0;
|
|
||||||
s.miniKernMatrixCapacity = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdCardFont::freeStyleAll(PerStyle& s) {
|
void SdCardFont::freeStyleAll(PerStyle& s) {
|
||||||
@@ -333,13 +311,13 @@ bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, ui
|
|||||||
if (miniLookupKernClass(s.kernRightClasses, s.header.kernRightEntryCount, codepoints[i]) != 0) miniRightCount++;
|
if (miniLookupKernClass(s.kernRightClasses, s.header.kernRightEntryCount, codepoints[i]) != 0) miniRightCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: size the three mini buffers (reused across pages when they fit; the
|
// Step 4: allocate the three mini buffers. The matrix is <1KB in practice
|
||||||
// per-page sizes vary by a few entries, which as free+realloc churn was punching
|
// (<30 × <30 × 1 byte) so fragmentation is a non-issue.
|
||||||
// non-coalescing holes in the heap every page turn).
|
|
||||||
const uint32_t matrixBytes = static_cast<uint32_t>(numLeft) * numRight;
|
const uint32_t matrixBytes = static_cast<uint32_t>(numLeft) * numRight;
|
||||||
if (!ensureArrayCapacity(s.miniKernLeftClasses, s.miniKernLeftCapacity, miniLeftCount) ||
|
s.miniKernLeftClasses = new (std::nothrow) EpdKernClassEntry[miniLeftCount];
|
||||||
!ensureArrayCapacity(s.miniKernRightClasses, s.miniKernRightCapacity, miniRightCount) ||
|
s.miniKernRightClasses = new (std::nothrow) EpdKernClassEntry[miniRightCount];
|
||||||
!ensureArrayCapacity(s.miniKernMatrix, s.miniKernMatrixCapacity, matrixBytes)) {
|
s.miniKernMatrix = new (std::nothrow) int8_t[matrixBytes];
|
||||||
|
if (!s.miniKernLeftClasses || !s.miniKernRightClasses || !s.miniKernMatrix) {
|
||||||
LOG_ERR("SDCF", "Failed to allocate mini kern (%u+%u+%u bytes)", miniLeftCount * 3u, miniRightCount * 3u,
|
LOG_ERR("SDCF", "Failed to allocate mini kern (%u+%u+%u bytes)", miniLeftCount * 3u, miniRightCount * 3u,
|
||||||
matrixBytes);
|
matrixBytes);
|
||||||
freeStyleMiniKern(s);
|
freeStyleMiniKern(s);
|
||||||
@@ -815,19 +793,12 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
|||||||
return missed;
|
return missed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build mini intervals from sorted codepoints. Reset counts and fall back to the
|
// Build mini intervals from sorted codepoints
|
||||||
// stub until the rebuild completes, but KEEP the existing buffers (keep-if-fits
|
freeStyleMiniData(s);
|
||||||
// reuse) — the free-and-realloc-per-page pattern here was a primary fragmenter.
|
|
||||||
s.miniIntervalCount = 0;
|
|
||||||
s.miniGlyphCount = 0;
|
|
||||||
s.miniKernLeftEntryCount = 0;
|
|
||||||
s.miniKernRightEntryCount = 0;
|
|
||||||
s.miniKernLeftClassCount = 0;
|
|
||||||
s.miniKernRightClassCount = 0;
|
|
||||||
memset(&s.miniData, 0, sizeof(s.miniData));
|
|
||||||
s.epdFont.data = &s.stubData;
|
|
||||||
|
|
||||||
if (!ensureArrayCapacity(s.miniIntervals, s.miniIntervalCapacity, validCount)) {
|
uint32_t intervalCapacity = validCount;
|
||||||
|
s.miniIntervals = new (std::nothrow) EpdUnicodeInterval[intervalCapacity];
|
||||||
|
if (!s.miniIntervals) {
|
||||||
LOG_ERR("SDCF", "Failed to allocate mini intervals for style %u", styleIdx);
|
LOG_ERR("SDCF", "Failed to allocate mini intervals for style %u", styleIdx);
|
||||||
delete[] mappings;
|
delete[] mappings;
|
||||||
return static_cast<int>(cpCount);
|
return static_cast<int>(cpCount);
|
||||||
@@ -845,14 +816,15 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mini glyph array (reused across pages when it fits)
|
// Allocate mini glyph array
|
||||||
if (!ensureArrayCapacity(s.miniGlyphs, s.miniGlyphCapacity, validCount)) {
|
s.miniGlyphCount = validCount;
|
||||||
|
s.miniGlyphs = new (std::nothrow) EpdGlyph[s.miniGlyphCount];
|
||||||
|
if (!s.miniGlyphs) {
|
||||||
LOG_ERR("SDCF", "Failed to allocate mini glyphs for style %u", styleIdx);
|
LOG_ERR("SDCF", "Failed to allocate mini glyphs for style %u", styleIdx);
|
||||||
delete[] mappings;
|
delete[] mappings;
|
||||||
freeStyleMiniData(s);
|
freeStyleMiniData(s);
|
||||||
return static_cast<int>(cpCount);
|
return static_cast<int>(cpCount);
|
||||||
}
|
}
|
||||||
s.miniGlyphCount = validCount;
|
|
||||||
|
|
||||||
// Build sorted read order for sequential I/O
|
// Build sorted read order for sequential I/O
|
||||||
uint32_t* readOrder = new (std::nothrow) uint32_t[validCount];
|
uint32_t* readOrder = new (std::nothrow) uint32_t[validCount];
|
||||||
@@ -919,7 +891,8 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
|||||||
totalBitmapSize += s.miniGlyphs[i].dataLength;
|
totalBitmapSize += s.miniGlyphs[i].dataLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ensureArrayCapacity(s.miniBitmap, s.miniBitmapCapacity, totalBitmapSize)) {
|
s.miniBitmap = new (std::nothrow) uint8_t[totalBitmapSize > 0 ? totalBitmapSize : 1];
|
||||||
|
if (!s.miniBitmap) {
|
||||||
LOG_ERR("SDCF", "Failed to allocate mini bitmap (%u bytes) for style %u", totalBitmapSize, styleIdx);
|
LOG_ERR("SDCF", "Failed to allocate mini bitmap (%u bytes) for style %u", totalBitmapSize, styleIdx);
|
||||||
delete[] readOrder;
|
delete[] readOrder;
|
||||||
delete[] mappings;
|
delete[] mappings;
|
||||||
@@ -1088,6 +1061,28 @@ void SdCardFont::mergeIntoAdvanceTable(uint8_t styleIdx, const AdvanceEntry* sor
|
|||||||
advanceTableSize_[styleIdx] = k;
|
advanceTableSize_[styleIdx] = k;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SdCardFont::hasGlyphMeta(uint8_t styleIdx, uint32_t codepoint) const {
|
||||||
|
styleIdx &= (MAX_STYLES - 1);
|
||||||
|
const PerStyle& s = styles_[styleIdx];
|
||||||
|
if (!loaded_ || !s.present) return false;
|
||||||
|
return findGlobalGlyphIndex(s, codepoint) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t SdCardFont::ensureAdvance(uint8_t styleIdx, uint32_t codepoint) {
|
||||||
|
styleIdx &= (MAX_STYLES - 1);
|
||||||
|
if (!loaded_ || !styles_[styleIdx].present) return 0;
|
||||||
|
const uint16_t cached = getAdvance(codepoint, styleIdx);
|
||||||
|
if (cached != 0) return cached;
|
||||||
|
// 0 is either "not in table" or a genuine zero-width glyph; only pay the
|
||||||
|
// SD read when the resident interval table says the glyph exists.
|
||||||
|
if (findGlobalGlyphIndex(styles_[styleIdx], codepoint) < 0) return 0;
|
||||||
|
uint32_t cp = codepoint;
|
||||||
|
fetchAdvancesForCodepoints(&cp, 1, static_cast<uint8_t>(1u << styleIdx));
|
||||||
|
return getAdvance(codepoint, styleIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SdCardFont::styleIdxFromMissCtx(void* ctx) { return static_cast<OverflowContext*>(ctx)->styleIdx; }
|
||||||
|
|
||||||
bool SdCardFont::hasAdvanceTable() const {
|
bool SdCardFont::hasAdvanceTable() const {
|
||||||
for (uint8_t i = 0; i < MAX_STYLES; i++) {
|
for (uint8_t i = 0; i < MAX_STYLES; i++) {
|
||||||
if (advanceTable_[i]) return true;
|
if (advanceTable_[i]) return true;
|
||||||
@@ -1222,8 +1217,7 @@ int SdCardFont::fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iter>
|
template <typename Iter>
|
||||||
int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask,
|
int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask) {
|
||||||
const char* extraText) {
|
|
||||||
if (!loaded_) return -1;
|
if (!loaded_) return -1;
|
||||||
styleMask = resolveStyleMask(styleMask);
|
styleMask = resolveStyleMask(styleMask);
|
||||||
if (styleMask == 0) return 0;
|
if (styleMask == 0) return 0;
|
||||||
@@ -1243,9 +1237,6 @@ int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace,
|
|||||||
for (auto it = begin; it != end && !hitCap; ++it) {
|
for (auto it = begin; it != end && !hitCap; ++it) {
|
||||||
hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
||||||
}
|
}
|
||||||
if (extraText && !hitCap) {
|
|
||||||
hitCap = collectUniqueCodepoints(extraText, codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; }))
|
if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; }))
|
||||||
codepoints[cpCount++] = ' ';
|
codepoints[cpCount++] = ' ';
|
||||||
@@ -1263,13 +1254,12 @@ int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace,
|
|||||||
return totalMissed;
|
return totalMissed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask, const char* extraText) {
|
int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) {
|
||||||
return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask, extraText);
|
return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdCardFont::buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask,
|
int SdCardFont::buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask) {
|
||||||
const char* extraText) {
|
return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask);
|
||||||
return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask, extraText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Stats ---
|
// --- Stats ---
|
||||||
@@ -1406,33 +1396,6 @@ const EpdGlyph* SdCardFont::onGlyphMiss(void* ctx, uint32_t codepoint) {
|
|||||||
return &self->overflow_[slot].glyph;
|
return &self->overflow_[slot].glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SdCardFont::reportMemory() const {
|
|
||||||
size_t total = 0;
|
|
||||||
for (uint8_t si = 0; si < MAX_STYLES; ++si) {
|
|
||||||
const auto& s = styles_[si];
|
|
||||||
if (!s.present) continue;
|
|
||||||
size_t fixed = 0; // loaded once per family: interval/kern/lig tables
|
|
||||||
if (s.fullIntervals) fixed += s.header.intervalCount * sizeof(EpdUnicodeInterval);
|
|
||||||
if (s.bmpIntervals) fixed += s.header.intervalCount * sizeof(PerStyle::BmpInterval16);
|
|
||||||
if (s.kernLeftClasses) fixed += s.header.kernLeftEntryCount * sizeof(EpdKernClassEntry);
|
|
||||||
if (s.kernRightClasses) fixed += s.header.kernRightEntryCount * sizeof(EpdKernClassEntry);
|
|
||||||
if (s.ligaturePairs) fixed += s.header.ligaturePairCount * sizeof(EpdLigaturePair);
|
|
||||||
// kept-if-fits mini arenas: capacity (not count) is what stays resident
|
|
||||||
size_t mini = s.miniIntervalCapacity * sizeof(EpdUnicodeInterval) + s.miniGlyphCapacity * sizeof(EpdGlyph) +
|
|
||||||
s.miniBitmapCapacity + s.miniKernLeftCapacity * sizeof(EpdKernClassEntry) +
|
|
||||||
s.miniKernRightCapacity * sizeof(EpdKernClassEntry) + s.miniKernMatrixCapacity;
|
|
||||||
const size_t adv = advanceTableSize_[si] * sizeof(AdvanceEntry);
|
|
||||||
LOG_DBG("SDCF", "mem style%u: fixed=%u mini=%u adv=%u", si, (unsigned)fixed, (unsigned)mini, (unsigned)adv);
|
|
||||||
total += fixed + mini + adv;
|
|
||||||
}
|
|
||||||
size_t overflowBytes = 0;
|
|
||||||
for (uint32_t i = 0; i < overflowCount_; ++i) {
|
|
||||||
if (overflow_[i].bitmap) overflowBytes += overflow_[i].glyph.dataLength;
|
|
||||||
}
|
|
||||||
total += overflowBytes + overflowCount_ * sizeof(OverflowEntry);
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SdCardFont::isOverflowGlyph(const EpdGlyph* glyph) const {
|
bool SdCardFont::isOverflowGlyph(const EpdGlyph* glyph) const {
|
||||||
for (uint32_t i = 0; i < overflowCount_; i++) {
|
for (uint32_t i = 0; i < overflowCount_; i++) {
|
||||||
if (&overflow_[i].glyph == glyph) return true;
|
if (&overflow_[i].glyph == glyph) return true;
|
||||||
|
|||||||
+22
-29
@@ -47,17 +47,31 @@ class SdCardFont {
|
|||||||
// Build a compact advance-only table for layout measurement.
|
// Build a compact advance-only table for layout measurement.
|
||||||
// Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap),
|
// Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap),
|
||||||
// batch-reads advanceX from SD, stores in a sorted per-style table.
|
// batch-reads advanceX from SD, stores in a sorted per-style table.
|
||||||
// extraText: optional additional codepoints to warm in the same SD pass
|
|
||||||
// (e.g. shaped Arabic presentation forms the measurement path will look up).
|
|
||||||
// Returns number of codepoints not found in font coverage.
|
// Returns number of codepoints not found in font coverage.
|
||||||
int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F, const char* extraText = nullptr);
|
int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F);
|
||||||
int buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask = 0x0F,
|
int buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask = 0x0F);
|
||||||
const char* extraText = nullptr);
|
|
||||||
|
|
||||||
// Look up advanceX for a codepoint from the advance table.
|
// Look up advanceX for a codepoint from the advance table.
|
||||||
// Returns the 12.4 fixed-point advance, or 0 if not found.
|
// Returns the 12.4 fixed-point advance, or 0 if not found.
|
||||||
uint16_t getAdvance(uint32_t codepoint, uint8_t style) const;
|
uint16_t getAdvance(uint32_t codepoint, uint8_t style) const;
|
||||||
|
|
||||||
|
// Layout-side metric access — the per-glyph path CpFontAdapter uses while
|
||||||
|
// the FreeInkBook engine measures a chapter. Neither touches bitmap data:
|
||||||
|
// going through getGlyph() would stream every occurrence's bitmap through
|
||||||
|
// the 8-slot overflow ring (one SD read per character — a crawling build).
|
||||||
|
//
|
||||||
|
// hasGlyphMeta answers existence from the resident interval table (no I/O).
|
||||||
|
bool hasGlyphMeta(uint8_t styleIdx, uint32_t codepoint) const;
|
||||||
|
// ensureAdvance returns the 12.4 fixed-point advanceX, fetching the glyph
|
||||||
|
// METADATA from SD once on a miss and merging it into the persistent
|
||||||
|
// advance table (so each unique codepoint costs one small read per
|
||||||
|
// session). Returns 0 when the style lacks the codepoint.
|
||||||
|
uint16_t ensureAdvance(uint8_t styleIdx, uint32_t codepoint);
|
||||||
|
|
||||||
|
// Recover the style index from an EpdFontData::glyphMissCtx (see
|
||||||
|
// fromMissCtx below for recovering the SdCardFont itself).
|
||||||
|
static uint8_t styleIdxFromMissCtx(void* ctx);
|
||||||
|
|
||||||
// Returns true if advance table is populated for at least one style.
|
// Returns true if advance table is populated for at least one style.
|
||||||
bool hasAdvanceTable() const;
|
bool hasAdvanceTable() const;
|
||||||
|
|
||||||
@@ -104,11 +118,6 @@ class SdCardFont {
|
|||||||
uint32_t uniqueGlyphs = 0;
|
uint32_t uniqueGlyphs = 0;
|
||||||
uint32_t bitmapBytes = 0;
|
uint32_t bitmapBytes = 0;
|
||||||
};
|
};
|
||||||
// MEMFIX-PORT: SD font resident-bytes audit; portable
|
|
||||||
// Log per-style resident heap (full tables + kept-if-fits mini arenas +
|
|
||||||
// advance tables + overflow bitmaps) and return the total in bytes. Pure
|
|
||||||
// accounting — no allocation, no state change.
|
|
||||||
size_t reportMemory() const;
|
|
||||||
void logStats(const char* label = "SDCF");
|
void logStats(const char* label = "SDCF");
|
||||||
void resetStats();
|
void resetStats();
|
||||||
const Stats& getStats() const { return stats_; }
|
const Stats& getStats() const { return stats_; }
|
||||||
@@ -148,13 +157,11 @@ class SdCardFont {
|
|||||||
|
|
||||||
// Full intervals loaded from file (kept in RAM for codepoint lookup)
|
// Full intervals loaded from file (kept in RAM for codepoint lookup)
|
||||||
EpdUnicodeInterval* fullIntervals = nullptr;
|
EpdUnicodeInterval* fullIntervals = nullptr;
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
struct BmpInterval16 {
|
struct BmpInterval16 {
|
||||||
uint16_t first;
|
uint16_t first;
|
||||||
uint16_t last;
|
uint16_t last;
|
||||||
uint16_t offset;
|
uint16_t offset;
|
||||||
} EPD_PACKED_ATTR;
|
} __attribute__((packed));
|
||||||
EPD_PACKED_END
|
|
||||||
static_assert(sizeof(BmpInterval16) == 6, "BmpInterval16 must remain compact");
|
static_assert(sizeof(BmpInterval16) == 6, "BmpInterval16 must remain compact");
|
||||||
BmpInterval16* bmpIntervals = nullptr;
|
BmpInterval16* bmpIntervals = nullptr;
|
||||||
bool intervalsAreBmp16 = false;
|
bool intervalsAreBmp16 = false;
|
||||||
@@ -173,22 +180,13 @@ class SdCardFont {
|
|||||||
// Stub EpdFontData returned when not prewarmed
|
// Stub EpdFontData returned when not prewarmed
|
||||||
EpdFontData stubData{};
|
EpdFontData stubData{};
|
||||||
|
|
||||||
// Mini EpdFontData built during prewarm. Buffers are kept-if-fits across pages
|
// Mini EpdFontData built during prewarm
|
||||||
// (capacities below track allocated sizes): freeing and reallocating slightly
|
|
||||||
// different sizes on every page turn was a primary heap fragmenter — each page's
|
|
||||||
// freed hole rarely fit the next page's need, so maxAlloc eroded all session.
|
|
||||||
// After a few pages the capacities converge on the book's max and page turns
|
|
||||||
// stop allocating entirely. freeStyleMiniData() still releases everything (and
|
|
||||||
// zeroes capacities) for style eviction / font unload.
|
|
||||||
EpdFontData miniData{};
|
EpdFontData miniData{};
|
||||||
EpdUnicodeInterval* miniIntervals = nullptr;
|
EpdUnicodeInterval* miniIntervals = nullptr;
|
||||||
EpdGlyph* miniGlyphs = nullptr;
|
EpdGlyph* miniGlyphs = nullptr;
|
||||||
uint8_t* miniBitmap = nullptr;
|
uint8_t* miniBitmap = nullptr;
|
||||||
uint32_t miniIntervalCount = 0;
|
uint32_t miniIntervalCount = 0;
|
||||||
uint32_t miniGlyphCount = 0;
|
uint32_t miniGlyphCount = 0;
|
||||||
uint32_t miniIntervalCapacity = 0;
|
|
||||||
uint32_t miniGlyphCapacity = 0;
|
|
||||||
uint32_t miniBitmapCapacity = 0;
|
|
||||||
|
|
||||||
// Per-page mini kern matrix (built by buildMiniKernMatrix on each full
|
// Per-page mini kern matrix (built by buildMiniKernMatrix on each full
|
||||||
// prewarm). miniKernLeftClasses/miniKernRightClasses map ONLY the codepoints
|
// prewarm). miniKernLeftClasses/miniKernRightClasses map ONLY the codepoints
|
||||||
@@ -203,10 +201,6 @@ class SdCardFont {
|
|||||||
uint8_t miniKernLeftClassCount = 0;
|
uint8_t miniKernLeftClassCount = 0;
|
||||||
uint8_t miniKernRightClassCount = 0;
|
uint8_t miniKernRightClassCount = 0;
|
||||||
int8_t* miniKernMatrix = nullptr;
|
int8_t* miniKernMatrix = nullptr;
|
||||||
// Kept-if-fits capacities, same rationale as the mini glyph buffers above.
|
|
||||||
uint16_t miniKernLeftCapacity = 0;
|
|
||||||
uint16_t miniKernRightCapacity = 0;
|
|
||||||
uint32_t miniKernMatrixCapacity = 0;
|
|
||||||
|
|
||||||
// The EpdFont whose data pointer we manage
|
// The EpdFont whose data pointer we manage
|
||||||
EpdFont epdFont{&stubData};
|
EpdFont epdFont{&stubData};
|
||||||
@@ -272,8 +266,7 @@ class SdCardFont {
|
|||||||
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;
|
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;
|
||||||
int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask);
|
int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask);
|
||||||
template <typename Iter>
|
template <typename Iter>
|
||||||
int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask,
|
int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask);
|
||||||
const char* extraText = nullptr);
|
|
||||||
int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly);
|
int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly);
|
||||||
|
|
||||||
// Global helpers
|
// Global helpers
|
||||||
|
|||||||
@@ -88,14 +88,6 @@ void SdCardFontManager::unloadAll(GfxRenderer& renderer) {
|
|||||||
loadedPointSize_ = 0;
|
loadedPointSize_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SdCardFontManager::reportMemory() const {
|
|
||||||
size_t total = 0;
|
|
||||||
for (const auto& lf : loaded_) {
|
|
||||||
if (lf.font) total += lf.font->reportMemory();
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SdCardFontManager::getFontId(const std::string& familyName) const {
|
int SdCardFontManager::getFontId(const std::string& familyName) const {
|
||||||
if (familyName != loadedFamilyName_ || loaded_.empty()) return 0;
|
if (familyName != loadedFamilyName_ || loaded_.empty()) return 0;
|
||||||
return loaded_.front().fontId;
|
return loaded_.front().fontId;
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ class SdCardFontManager {
|
|||||||
// Get name of currently loaded family (empty if none).
|
// Get name of currently loaded family (empty if none).
|
||||||
const std::string& currentFamilyName() const { return loadedFamilyName_; };
|
const std::string& currentFamilyName() const { return loadedFamilyName_; };
|
||||||
|
|
||||||
// MEMFIX-PORT: font manager audit passthrough; portable
|
|
||||||
// Sum of loaded fonts' resident heap (see SdCardFont::reportMemory).
|
|
||||||
size_t reportMemory() const;
|
|
||||||
|
|
||||||
// Point size that was actually loaded.
|
// Point size that was actually loaded.
|
||||||
// 0 if nothing loaded.
|
// 0 if nothing loaded.
|
||||||
uint8_t currentPointSize() const { return loadedPointSize_; };
|
uint8_t currentPointSize() const { return loadedPointSize_; };
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,6 @@
|
|||||||
!NotoSerif/**
|
!NotoSerif/**
|
||||||
!NotoSans/
|
!NotoSans/
|
||||||
!NotoSans/**
|
!NotoSans/**
|
||||||
!NotoSansArabic/
|
|
||||||
!NotoSansArabic/**
|
|
||||||
!NotoSansHebrew/
|
!NotoSansHebrew/
|
||||||
!NotoSansHebrew/**
|
!NotoSansHebrew/**
|
||||||
!OpenDyslexic/
|
!OpenDyslexic/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,93 +0,0 @@
|
|||||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/arabic)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
https://openfontlicense.org
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
+1256
-1872
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1483
-2244
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,48 +31,19 @@ done
|
|||||||
UI_FONT_SIZES=(10 12)
|
UI_FONT_SIZES=(10 12)
|
||||||
UI_FONT_STYLES=("Regular" "Bold")
|
UI_FONT_STYLES=("Regular" "Bold")
|
||||||
|
|
||||||
# Arabic glyphs for UI text (menus, file browser titles). The built-in fonts
|
|
||||||
# must cover the *output* of MiniBidi's do_shape() — contextual presentation
|
|
||||||
# forms — not base letters, or shaped UI text silently drops glyphs.
|
|
||||||
# Curated for firmware-size budget: core Arabic (Presentation Forms-B,
|
|
||||||
# incl. the Lam-Alef ligature forms) plus the Farsi/Urdu extra letters'
|
|
||||||
# Presentation Forms-A blocks, the few characters shaping leaves at their
|
|
||||||
# base codepoint, Arabic punctuation, and both digit sets. No harakat and
|
|
||||||
# no Sindhi/Pashto/Kurdish forms — book text gets those from SD-card fonts.
|
|
||||||
ARABIC_INTERVALS=(
|
|
||||||
--additional-intervals 0x060C,0x060C # Arabic comma
|
|
||||||
--additional-intervals 0x061B,0x061B # Arabic semicolon
|
|
||||||
--additional-intervals 0x061F,0x061F # Arabic question mark
|
|
||||||
--additional-intervals 0x0621,0x0621 # hamza (non-joining, never shaped)
|
|
||||||
--additional-intervals 0x0640,0x0640 # tatweel
|
|
||||||
--additional-intervals 0x0660,0x0669 # Arabic-Indic digits
|
|
||||||
--additional-intervals 0x06BA,0x06BA # noon ghunna base (initial/medial keep base cp)
|
|
||||||
--additional-intervals 0x06D4,0x06D4 # Urdu full stop
|
|
||||||
--additional-intervals 0x06F0,0x06F9 # extended Arabic-Indic digits (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFB56,0xFB59 # peh (Farsi)
|
|
||||||
--additional-intervals 0xFB66,0xFB69 # tteh (Urdu)
|
|
||||||
--additional-intervals 0xFB7A,0xFB7D # tcheh (Farsi)
|
|
||||||
--additional-intervals 0xFB88,0xFB95 # ddal, jeh, rreh (Urdu), keheh, gaf (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFB9E,0xFB9F # noon ghunna isolated/final (Urdu)
|
|
||||||
--additional-intervals 0xFBA6,0xFBB1 # heh goal, heh doachashmee, yeh barree(+hamza) (Urdu)
|
|
||||||
--additional-intervals 0xFBFC,0xFBFF # farsi yeh (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFE80,0xFEFC # Presentation Forms-B: core Arabic + Lam-Alef
|
|
||||||
)
|
|
||||||
|
|
||||||
for size in ${UI_FONT_SIZES[@]}; do
|
for size in ${UI_FONT_SIZES[@]}; do
|
||||||
for style in ${UI_FONT_STYLES[@]}; do
|
for style in ${UI_FONT_STYLES[@]}; do
|
||||||
font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
||||||
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
||||||
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
||||||
arabic_path="../builtinFonts/source/NotoSansArabic/NotoSansArabic-${style}.ttf"
|
|
||||||
# Ubuntu lacks the Latin Extended Additional block (U+1EA0-U+1EF9) used for
|
# Ubuntu lacks the Latin Extended Additional block (U+1EA0-U+1EF9) used for
|
||||||
# Vietnamese tone marks. Append a Vietnamese-only Ubuntu cut so those glyphs
|
# Vietnamese tone marks. Append a Vietnamese-only Ubuntu cut so those glyphs
|
||||||
# are filled from it while every glyph Ubuntu already has stays unchanged
|
# are filled from it while every glyph Ubuntu already has stays unchanged
|
||||||
# (fontstack is ordered by descending priority).
|
# (fontstack is ordered by descending priority).
|
||||||
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
||||||
output_path="../builtinFonts/${font_name}.h"
|
output_path="../builtinFonts/${font_name}.h"
|
||||||
python fontconvert.py $font_name $size $font_path $hebrew_path $arabic_path $viet_path \
|
python fontconvert.py $font_name $size $font_path $hebrew_path $viet_path \
|
||||||
--additional-intervals 0x05D0,0x05EA "${ARABIC_INTERVALS[@]}" > $output_path
|
--additional-intervals 0x05D0,0x05EA > $output_path
|
||||||
echo "Generated $output_path"
|
echo "Generated $output_path"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
@@ -80,8 +51,7 @@ done
|
|||||||
python fontconvert.py notosans_8_regular 8 \
|
python fontconvert.py notosans_8_regular 8 \
|
||||||
../builtinFonts/source/NotoSans/NotoSans-Regular.ttf \
|
../builtinFonts/source/NotoSans/NotoSans-Regular.ttf \
|
||||||
../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-Regular.ttf \
|
../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-Regular.ttf \
|
||||||
../builtinFonts/source/NotoSansArabic/NotoSansArabic-Regular.ttf \
|
--additional-intervals 0x05D0,0x05EA > ../builtinFonts/notosans_8_regular.h
|
||||||
--additional-intervals 0x05D0,0x05EA "${ARABIC_INTERVALS[@]}" > ../builtinFonts/notosans_8_regular.h
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Running compression verification..."
|
echo "Running compression verification..."
|
||||||
|
|||||||
@@ -1,887 +0,0 @@
|
|||||||
#include "Epub.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <JpegToBmpConverter.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <PngToBmpConverter.h>
|
|
||||||
#include <Utf8.h>
|
|
||||||
#include <ZipFile.h>
|
|
||||||
|
|
||||||
#include "Epub/parsers/ContainerParser.h"
|
|
||||||
#include "Epub/parsers/ContentOpfParser.h"
|
|
||||||
#include "Epub/parsers/TocNavParser.h"
|
|
||||||
#include "Epub/parsers/TocNcxParser.h"
|
|
||||||
|
|
||||||
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
|
||||||
const auto containerPath = "META-INF/container.xml";
|
|
||||||
size_t containerSize;
|
|
||||||
|
|
||||||
// Get file size without loading it all into heap
|
|
||||||
if (!getItemSize(containerPath, &containerSize)) {
|
|
||||||
LOG_ERR("EBP", "Could not find or size META-INF/container.xml");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerParser containerParser(containerSize);
|
|
||||||
|
|
||||||
if (!containerParser.setup()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream read (reusing your existing stream logic)
|
|
||||||
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
|
|
||||||
LOG_ERR("EBP", "Could not read META-INF/container.xml");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the result
|
|
||||||
if (containerParser.fullPath.empty()) {
|
|
||||||
LOG_ERR("EBP", "Could not find valid rootfile in container.xml");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*contentOpfFile = std::move(containerParser.fullPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata, const bool writeSpineEntries) {
|
|
||||||
std::string contentOpfFilePath;
|
|
||||||
if (!findContentOpfFile(&contentOpfFilePath)) {
|
|
||||||
LOG_ERR("EBP", "Could not find content.opf in zip");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1);
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsing content.opf: %s", contentOpfFilePath.c_str());
|
|
||||||
|
|
||||||
size_t contentOpfSize;
|
|
||||||
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
|
|
||||||
LOG_ERR("EBP", "Could not get size of content.opf");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize,
|
|
||||||
writeSpineEntries ? bookMetadataCache.get() : nullptr);
|
|
||||||
if (!opfParser.setup()) {
|
|
||||||
LOG_ERR("EBP", "Could not setup content.opf parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
|
|
||||||
LOG_ERR("EBP", "Could not read content.opf");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab data from opfParser into epub. Normalize titles to NFC so NFD (combining
|
|
||||||
// mark) text renders correctly — the device fonts have no mark positioning.
|
|
||||||
bookMetadata.title = utf8ComposeNfc(opfParser.title);
|
|
||||||
bookMetadata.author = opfParser.author;
|
|
||||||
bookMetadata.language = opfParser.language;
|
|
||||||
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
|
||||||
|
|
||||||
// Guide-based cover fallback: if no cover found via metadata/properties,
|
|
||||||
// try extracting the image reference from the guide's cover page XHTML
|
|
||||||
if (bookMetadata.coverItemHref.empty() && !opfParser.guideCoverPageHref.empty()) {
|
|
||||||
LOG_DBG("EBP", "No cover from metadata, trying guide cover page: %s", opfParser.guideCoverPageHref.c_str());
|
|
||||||
size_t coverPageSize;
|
|
||||||
uint8_t* coverPageData = readItemContentsToBytes(opfParser.guideCoverPageHref, &coverPageSize, true);
|
|
||||||
if (coverPageData) {
|
|
||||||
const std::string coverPageHtml(reinterpret_cast<char*>(coverPageData), coverPageSize);
|
|
||||||
free(coverPageData);
|
|
||||||
|
|
||||||
// Determine base path of the cover page for resolving relative image references
|
|
||||||
std::string coverPageBase;
|
|
||||||
const auto lastSlash = opfParser.guideCoverPageHref.rfind('/');
|
|
||||||
if (lastSlash != std::string::npos) {
|
|
||||||
coverPageBase = opfParser.guideCoverPageHref.substr(0, lastSlash + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for image references: xlink:href="..." (SVG) and src="..." (img)
|
|
||||||
std::string imageRef;
|
|
||||||
for (const char* pattern : {"xlink:href=\"", "src=\""}) {
|
|
||||||
auto pos = coverPageHtml.find(pattern);
|
|
||||||
while (pos != std::string::npos) {
|
|
||||||
pos += strlen(pattern);
|
|
||||||
const auto endPos = coverPageHtml.find('"', pos);
|
|
||||||
if (endPos != std::string::npos) {
|
|
||||||
const auto ref = std::string_view{coverPageHtml}.substr(pos, endPos - pos);
|
|
||||||
// Cover BMP generation supports JPG/PNG only; skip GIF so an unsupported wrapper image
|
|
||||||
// does not block a later supported cover reference.
|
|
||||||
if (FsHelpers::hasPngExtension(ref) || FsHelpers::hasJpgExtension(ref)) {
|
|
||||||
imageRef = ref;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pos = coverPageHtml.find(pattern, pos);
|
|
||||||
}
|
|
||||||
if (!imageRef.empty()) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageRef.empty()) {
|
|
||||||
bookMetadata.coverItemHref = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(coverPageBase + imageRef));
|
|
||||||
LOG_DBG("EBP", "Found cover image from guide: %s", bookMetadata.coverItemHref.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bookMetadata.textReferenceHref = opfParser.textReferenceHref;
|
|
||||||
|
|
||||||
if (!opfParser.tocNcxPath.empty()) {
|
|
||||||
tocNcxItem = opfParser.tocNcxPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opfParser.tocNavPath.empty()) {
|
|
||||||
tocNavItem = opfParser.tocNavPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opfParser.cssFiles.empty()) {
|
|
||||||
cssFiles = opfParser.cssFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Successfully parsed content.opf");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::parseTocNcxFile() const {
|
|
||||||
// the ncx file should have been specified in the content.opf file
|
|
||||||
if (tocNcxItem.empty()) {
|
|
||||||
LOG_DBG("EBP", "No ncx file specified");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
|
|
||||||
|
|
||||||
size_t ncxSize;
|
|
||||||
if (!getItemSize(tocNcxItem, &ncxSize)) {
|
|
||||||
LOG_ERR("EBP", "Could not get size of toc ncx file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
|
|
||||||
|
|
||||||
if (!ncxParser.setup()) {
|
|
||||||
LOG_ERR("EBP", "Could not setup toc ncx parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream the decompressed NCX straight into the parser instead of round-tripping
|
|
||||||
// through a temp file on the SD card (decompress -> write -> reopen -> reread -> delete).
|
|
||||||
if (!readItemContentsToStream(tocNcxItem, ncxParser, 1024)) {
|
|
||||||
LOG_ERR("EBP", "Could not read toc ncx file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsed TOC items");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::parseTocNavFile() const {
|
|
||||||
// the nav file should have been specified in the content.opf file (EPUB 3)
|
|
||||||
if (tocNavItem.empty()) {
|
|
||||||
LOG_DBG("EBP", "No nav file specified");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
|
|
||||||
|
|
||||||
size_t navSize;
|
|
||||||
if (!getItemSize(tocNavItem, &navSize)) {
|
|
||||||
LOG_ERR("EBP", "Could not get size of toc nav file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We can't use `contentBasePath` here as the nav file may be in a different folder to the content.opf
|
|
||||||
// and the HTMLX nav file will have hrefs relative to itself
|
|
||||||
const std::string navContentBasePath = tocNavItem.substr(0, tocNavItem.find_last_of('/') + 1);
|
|
||||||
TocNavParser navParser(navContentBasePath, navSize, bookMetadataCache.get());
|
|
||||||
|
|
||||||
if (!navParser.setup()) {
|
|
||||||
LOG_ERR("EBP", "Could not setup toc nav parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream the decompressed nav document straight into the parser instead of round-tripping
|
|
||||||
// through a temp file on the SD card (decompress -> write -> reopen -> reread -> delete).
|
|
||||||
if (!readItemContentsToStream(tocNavItem, navParser, 1024)) {
|
|
||||||
LOG_ERR("EBP", "Could not read toc nav file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsed TOC nav items");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Epub::discoverCssFilesFromZip() {
|
|
||||||
const std::string& opfDir = contentBasePath;
|
|
||||||
ZipFile zf(filepath);
|
|
||||||
|
|
||||||
if (!zf.enumerateFilePaths([&](std::string_view filePath) {
|
|
||||||
if (!opfDir.empty() && filePath.find(opfDir) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FsHelpers::hasCssExtension(filePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::find(cssFiles.begin(), cssFiles.end(), filePath) != cssFiles.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Discovered CSS file via ZIP enumeration: %.*s", (int)filePath.size(), filePath.data());
|
|
||||||
cssFiles.push_back(std::string{filePath});
|
|
||||||
})) {
|
|
||||||
LOG_ERR("EBP", "Failed to enumerate ZIP file paths for CSS discovery");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Epub::parseCssFiles() const {
|
|
||||||
// Maximum CSS file size we'll attempt to parse (uncompressed)
|
|
||||||
// Larger files risk memory exhaustion on ESP32
|
|
||||||
constexpr size_t MAX_CSS_FILE_SIZE = 128 * 1024; // 128KB
|
|
||||||
// Minimum heap required before attempting CSS parsing
|
|
||||||
constexpr size_t MIN_HEAP_FOR_CSS_PARSING = 64 * 1024; // 64KB
|
|
||||||
|
|
||||||
if (cssFiles.empty()) {
|
|
||||||
LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "CSS files to parse: %zu", cssFiles.size());
|
|
||||||
|
|
||||||
// See if we have a cached version of the CSS rules
|
|
||||||
if (cssParser->hasCache()) {
|
|
||||||
LOG_DBG("EBP", "CSS cache exists, skipping parseCssFiles");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No cache yet - parse CSS files
|
|
||||||
for (const auto& cssPath : cssFiles) {
|
|
||||||
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
|
|
||||||
|
|
||||||
// Check heap before parsing - CSS parsing allocates heavily
|
|
||||||
const uint32_t freeHeap = ESP.getFreeHeap();
|
|
||||||
if (freeHeap < MIN_HEAP_FOR_CSS_PARSING) {
|
|
||||||
LOG_ERR("EBP", "Insufficient heap for CSS parsing (%u bytes free, need %zu), skipping: %s", freeHeap,
|
|
||||||
MIN_HEAP_FOR_CSS_PARSING, cssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check CSS file size before decompressing - skip files that are too large
|
|
||||||
size_t cssFileSize = 0;
|
|
||||||
if (getItemSize(cssPath, &cssFileSize)) {
|
|
||||||
if (cssFileSize > MAX_CSS_FILE_SIZE) {
|
|
||||||
LOG_ERR("EBP", "CSS file too large (%zu bytes > %zu max), skipping: %s", cssFileSize, MAX_CSS_FILE_SIZE,
|
|
||||||
cssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract CSS file to temp location
|
|
||||||
const auto tmpCssPath = getCachePath() + "/.tmp.css";
|
|
||||||
HalFile tempCssFile;
|
|
||||||
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
|
|
||||||
LOG_ERR("EBP", "Could not create temp CSS file");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
|
|
||||||
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
|
|
||||||
// Explicitly close() file before calling Storage.remove()
|
|
||||||
tempCssFile.close();
|
|
||||||
Storage.remove(tmpCssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Explicitly close() file before reopening for reading
|
|
||||||
tempCssFile.close();
|
|
||||||
|
|
||||||
// Parse the CSS file
|
|
||||||
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
|
|
||||||
LOG_ERR("EBP", "Could not open temp CSS file for reading");
|
|
||||||
Storage.remove(tmpCssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cssParser->loadFromStream(tempCssFile);
|
|
||||||
// Explicitly close() file before calling Storage.remove()
|
|
||||||
tempCssFile.close();
|
|
||||||
Storage.remove(tmpCssPath.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to cache for next time
|
|
||||||
if (!cssParser->saveToCache()) {
|
|
||||||
LOG_ERR("EBP", "Failed to save CSS rules to cache");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
|
|
||||||
cssParser->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// load in the meta data for the epub file
|
|
||||||
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|
||||||
LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str());
|
|
||||||
|
|
||||||
// Initialize spine/TOC cache
|
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
|
||||||
// Always create CssParser - needed for inline style parsing even without CSS files
|
|
||||||
cssParser.reset(new CssParser(cachePath));
|
|
||||||
|
|
||||||
// Try to load existing cache first
|
|
||||||
if (bookMetadataCache->load()) {
|
|
||||||
if (!skipLoadingCss) {
|
|
||||||
// Rebuild CSS cache when missing or when cache version changed (loadFromCache removes stale file)
|
|
||||||
if (!cssParser->hasCache() || !cssParser->loadFromCache()) {
|
|
||||||
LOG_DBG("EBP", "CSS rules cache missing or stale, attempting to parse CSS files");
|
|
||||||
cssParser->deleteCache();
|
|
||||||
|
|
||||||
BookMetadataCache::BookMetadata cachedMetadata = bookMetadataCache->coreMetadata;
|
|
||||||
if (!parseContentOpf(cachedMetadata, /*writeSpineEntries=*/false)) {
|
|
||||||
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
|
|
||||||
// continue anyway - book will work without CSS and we'll still load any inline style CSS
|
|
||||||
} else {
|
|
||||||
discoverCssFilesFromZip();
|
|
||||||
}
|
|
||||||
bookMetadataCache.reset();
|
|
||||||
parseCssFiles();
|
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
|
||||||
if (!bookMetadataCache->load()) {
|
|
||||||
LOG_ERR("EBP", "Failed to reload cache after CSS rebuild");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Invalidate section caches so they are rebuilt with the new CSS
|
|
||||||
Storage.removeDir((cachePath + "/sections").c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Release the resolved CSS rule map: it is only needed transiently while building
|
|
||||||
// section caches, and createSectionFile reloads it from cache on demand. Holding it
|
|
||||||
// resident pins tens of KB for the whole reading session (more on warm resume into
|
|
||||||
// an already-cached chapter, where createSectionFile never runs to clear it).
|
|
||||||
cssParser->clear();
|
|
||||||
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't load from cache above and we aren't allowed to build, fail now
|
|
||||||
if (!buildIfMissing) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache doesn't exist or is invalid, build it
|
|
||||||
LOG_DBG("EBP", "Cache not found, building spine/TOC cache");
|
|
||||||
setupCacheDir();
|
|
||||||
|
|
||||||
const uint32_t indexingStart = millis();
|
|
||||||
|
|
||||||
// Begin building cache - stream entries to disk immediately
|
|
||||||
if (!bookMetadataCache->beginWrite()) {
|
|
||||||
LOG_ERR("EBP", "Could not begin writing cache");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPF Pass
|
|
||||||
const uint32_t opfStart = millis();
|
|
||||||
BookMetadataCache::BookMetadata bookMetadata;
|
|
||||||
if (!bookMetadataCache->beginContentOpfPass()) {
|
|
||||||
LOG_ERR("EBP", "Could not begin writing content.opf pass");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!parseContentOpf(bookMetadata)) {
|
|
||||||
LOG_ERR("EBP", "Could not parse content.opf");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
discoverCssFilesFromZip();
|
|
||||||
if (!bookMetadataCache->endContentOpfPass()) {
|
|
||||||
LOG_ERR("EBP", "Could not end writing content.opf pass");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "OPF pass completed in %lu ms", millis() - opfStart);
|
|
||||||
|
|
||||||
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
|
||||||
const uint32_t tocStart = millis();
|
|
||||||
if (!bookMetadataCache->beginTocPass()) {
|
|
||||||
LOG_ERR("EBP", "Could not begin writing toc pass");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tocParsed = false;
|
|
||||||
|
|
||||||
// Try EPUB 3 nav document first (preferred)
|
|
||||||
if (!tocNavItem.empty()) {
|
|
||||||
LOG_DBG("EBP", "Attempting to parse EPUB 3 nav document");
|
|
||||||
tocParsed = parseTocNavFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to NCX if nav parsing failed or wasn't available
|
|
||||||
if (!tocParsed && !tocNcxItem.empty()) {
|
|
||||||
LOG_DBG("EBP", "Falling back to NCX TOC");
|
|
||||||
tocParsed = parseTocNcxFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tocParsed) {
|
|
||||||
LOG_ERR("EBP", "Warning: Could not parse any TOC format");
|
|
||||||
// Continue anyway - book will work without TOC
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bookMetadataCache->endTocPass()) {
|
|
||||||
LOG_ERR("EBP", "Could not end writing toc pass");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "TOC pass completed in %lu ms", millis() - tocStart);
|
|
||||||
|
|
||||||
// Close the cache files
|
|
||||||
if (!bookMetadataCache->endWrite()) {
|
|
||||||
LOG_ERR("EBP", "Could not end writing cache");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build final book.bin
|
|
||||||
const uint32_t buildStart = millis();
|
|
||||||
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
|
|
||||||
LOG_ERR("EBP", "Could not update mappings and sizes");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "buildBookBin completed in %lu ms", millis() - buildStart);
|
|
||||||
LOG_DBG("EBP", "Total indexing completed in %lu ms", millis() - indexingStart);
|
|
||||||
|
|
||||||
if (!bookMetadataCache->cleanupTmpFiles()) {
|
|
||||||
LOG_DBG("EBP", "Could not cleanup tmp files - ignoring");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skipLoadingCss) {
|
|
||||||
// Parse CSS before reloading book.bin to leave more heap for CSS rule-table growth.
|
|
||||||
bookMetadataCache.reset();
|
|
||||||
parseCssFiles();
|
|
||||||
Storage.removeDir((cachePath + "/sections").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the cache from disk so it's in the correct state
|
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
|
||||||
if (!bookMetadataCache->load()) {
|
|
||||||
LOG_ERR("EBP", "Failed to reload cache after writing");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::clearCache() const {
|
|
||||||
if (!Storage.exists(cachePath.c_str())) {
|
|
||||||
LOG_DBG("EPB", "Cache does not exist, no action needed");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.removeDir(cachePath.c_str())) {
|
|
||||||
LOG_ERR("EPB", "Failed to clear cache");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("EPB", "Cache cleared successfully");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Epub::setupCacheDir() const {
|
|
||||||
if (Storage.exists(cachePath.c_str())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage.mkdir(cachePath.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& Epub::getCachePath() const { return cachePath; }
|
|
||||||
|
|
||||||
const std::string& Epub::getPath() const { return filepath; }
|
|
||||||
|
|
||||||
const std::string& Epub::getTitle() const {
|
|
||||||
static std::string blank;
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
return blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->coreMetadata.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& Epub::getAuthor() const {
|
|
||||||
static std::string blank;
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
return blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->coreMetadata.author;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& Epub::getLanguage() const {
|
|
||||||
static std::string blank;
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
return blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->coreMetadata.language;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Epub::getCoverBmpPath(bool cropped) const {
|
|
||||||
const auto coverFileName = std::string("cover") + (cropped ? "_crop" : "");
|
|
||||||
return cachePath + "/" + coverFileName + ".bmp";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::generateCoverBmp(bool cropped) const {
|
|
||||||
// Already generated, return true
|
|
||||||
if (Storage.exists(getCoverBmpPath(cropped).c_str())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_ERR("EBP", "Cannot generate cover BMP, cache not loaded");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
|
||||||
if (coverImageHref.empty()) {
|
|
||||||
LOG_ERR("EBP", "No known cover image");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FsHelpers::hasJpgExtension(coverImageHref)) {
|
|
||||||
LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit");
|
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
|
||||||
|
|
||||||
HalFile coverJpg;
|
|
||||||
if (!Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readItemContentsToStream(coverImageHref, coverJpg, 1024);
|
|
||||||
// Explicitly close() file before reopening for reading
|
|
||||||
coverJpg.close();
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile coverBmp;
|
|
||||||
if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp, cropped);
|
|
||||||
// Explicitly close() files before calling Storage.remove()
|
|
||||||
coverJpg.close();
|
|
||||||
coverBmp.close();
|
|
||||||
Storage.remove(coverJpgTempPath.c_str());
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
LOG_ERR("EBP", "Failed to generate BMP from cover image");
|
|
||||||
Storage.remove(getCoverBmpPath(cropped).c_str());
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "Generated BMP from JPG cover image, success: %s", success ? "yes" : "no");
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FsHelpers::hasPngExtension(coverImageHref)) {
|
|
||||||
LOG_DBG("EBP", "Generating BMP from PNG cover image (%s mode)", cropped ? "cropped" : "fit");
|
|
||||||
const auto coverPngTempPath = getCachePath() + "/.cover.png";
|
|
||||||
|
|
||||||
HalFile coverPng;
|
|
||||||
if (!Storage.openFileForWrite("EBP", coverPngTempPath, coverPng)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readItemContentsToStream(coverImageHref, coverPng, 1024);
|
|
||||||
// Explicitly close() file before reopening for reading
|
|
||||||
coverPng.close();
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("EBP", coverPngTempPath, coverPng)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile coverBmp;
|
|
||||||
if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const bool success = PngToBmpConverter::pngFileToBmpStream(coverPng, coverBmp, cropped);
|
|
||||||
// Explicitly close() files before calling Storage.remove()
|
|
||||||
coverPng.close();
|
|
||||||
coverBmp.close();
|
|
||||||
Storage.remove(coverPngTempPath.c_str());
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
LOG_ERR("EBP", "Failed to generate BMP from PNG cover image");
|
|
||||||
Storage.remove(getCoverBmpPath(cropped).c_str());
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "Generated BMP from PNG cover image, success: %s", success ? "yes" : "no");
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_ERR("EBP", "Cover image is not a supported format, skipping");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Epub::getThumbBmpPath() const { return cachePath + "/thumb_[HEIGHT].bmp"; }
|
|
||||||
std::string Epub::getThumbBmpPath(int height) const { return cachePath + "/thumb_" + std::to_string(height) + ".bmp"; }
|
|
||||||
|
|
||||||
bool Epub::generateThumbBmp(int height) const {
|
|
||||||
// Already generated, return true
|
|
||||||
if (Storage.exists(getThumbBmpPath(height).c_str())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_ERR("EBP", "Cannot generate thumb BMP, cache not loaded");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
|
||||||
if (coverImageHref.empty()) {
|
|
||||||
LOG_DBG("EBP", "No known cover image for thumbnail");
|
|
||||||
} else if (FsHelpers::hasJpgExtension(coverImageHref)) {
|
|
||||||
LOG_DBG("EBP", "Generating thumb BMP from JPG cover image");
|
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
|
||||||
|
|
||||||
HalFile coverJpg;
|
|
||||||
if (!Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readItemContentsToStream(coverImageHref, coverJpg, 1024);
|
|
||||||
// Explicitly close() file before reopening for reading
|
|
||||||
coverJpg.close();
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile thumbBmp;
|
|
||||||
if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Use smaller target size for Continue Reading card (half of screen: 240x400)
|
|
||||||
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
|
||||||
int THUMB_TARGET_WIDTH = height * 0.6;
|
|
||||||
int THUMB_TARGET_HEIGHT = height;
|
|
||||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
|
|
||||||
THUMB_TARGET_HEIGHT);
|
|
||||||
// Explicitly close() files before calling Storage.remove()
|
|
||||||
coverJpg.close();
|
|
||||||
thumbBmp.close();
|
|
||||||
Storage.remove(coverJpgTempPath.c_str());
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
LOG_ERR("EBP", "Failed to generate thumb BMP from JPG cover image");
|
|
||||||
Storage.remove(getThumbBmpPath(height).c_str());
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "Generated thumb BMP from JPG cover image, success: %s", success ? "yes" : "no");
|
|
||||||
return success;
|
|
||||||
} else if (FsHelpers::hasPngExtension(coverImageHref)) {
|
|
||||||
LOG_DBG("EBP", "Generating thumb BMP from PNG cover image");
|
|
||||||
const auto coverPngTempPath = getCachePath() + "/.cover.png";
|
|
||||||
|
|
||||||
HalFile coverPng;
|
|
||||||
if (!Storage.openFileForWrite("EBP", coverPngTempPath, coverPng)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readItemContentsToStream(coverImageHref, coverPng, 1024);
|
|
||||||
// Explicitly close() file before reopening for reading
|
|
||||||
coverPng.close();
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("EBP", coverPngTempPath, coverPng)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile thumbBmp;
|
|
||||||
if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int THUMB_TARGET_WIDTH = height * 0.6;
|
|
||||||
int THUMB_TARGET_HEIGHT = height;
|
|
||||||
const bool success =
|
|
||||||
PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH, THUMB_TARGET_HEIGHT);
|
|
||||||
// Explicitly close() files before calling Storage.remove()
|
|
||||||
coverPng.close();
|
|
||||||
thumbBmp.close();
|
|
||||||
Storage.remove(coverPngTempPath.c_str());
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
LOG_ERR("EBP", "Failed to generate thumb BMP from PNG cover image");
|
|
||||||
Storage.remove(getThumbBmpPath(height).c_str());
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "Generated thumb BMP from PNG cover image, success: %s", success ? "yes" : "no");
|
|
||||||
return success;
|
|
||||||
} else {
|
|
||||||
LOG_ERR("EBP", "Cover image is not a supported format, skipping thumbnail");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write an empty bmp file to avoid generation attempts in the future
|
|
||||||
HalFile thumbBmp;
|
|
||||||
Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const {
|
|
||||||
if (itemHref.empty()) {
|
|
||||||
LOG_DBG("EBP", "Failed to read item, empty href");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string path = FsHelpers::normalisePath(itemHref);
|
|
||||||
|
|
||||||
const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte);
|
|
||||||
if (!content) {
|
|
||||||
LOG_DBG("EBP", "Failed to read item %s", path.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
|
|
||||||
if (itemHref.empty()) {
|
|
||||||
LOG_DBG("EBP", "Failed to read item, empty href");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string path = FsHelpers::normalisePath(itemHref);
|
|
||||||
return ZipFile(filepath).readFileToStream(path.c_str(), out, chunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
|
|
||||||
const std::string path = FsHelpers::normalisePath(itemHref);
|
|
||||||
return ZipFile(filepath).getInflatedFileSize(path.c_str(), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Epub::getSpineItemsCount() const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return bookMetadataCache->getSpineCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return getSpineItem(spineIndex).cumulativeSize; }
|
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_ERR("EBP", "getSpineItem called but cache not loaded");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) {
|
|
||||||
LOG_ERR("EBP", "getSpineItem index:%d is out of range", spineIndex);
|
|
||||||
return bookMetadataCache->getSpineEntry(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->getSpineEntry(spineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_DBG("EBP", "getTocItem called but cache not loaded");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
|
||||||
LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->getTocEntry(tocIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Epub::getTocItemsCount() const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bookMetadataCache->getTocCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
// work out the section index for a toc index
|
|
||||||
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_ERR("EBP", "getSpineIndexForTocIndex called but cache not loaded");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
|
||||||
LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int spineIndex = bookMetadataCache->getTocEntry(tocIndex).spineIndex;
|
|
||||||
if (spineIndex < 0) {
|
|
||||||
LOG_DBG("EBP", "Section not found for TOC index %d", tocIndex);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return spineIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Epub::getTocIndexForSpineIndex(const int spineIndex) const { return getSpineItem(spineIndex).tocIndex; }
|
|
||||||
|
|
||||||
size_t Epub::getBookSize() const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded() || bookMetadataCache->getSpineCount() == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return getCumulativeSpineItemSize(getSpineItemsCount() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Epub::getSpineIndexForTextReference() const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
|
||||||
LOG_ERR("EBP", "getSpineIndexForTextReference called but cache not loaded");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
LOG_DBG("EBP", "Core Metadata: cover(%d)=%s, textReference(%d)=%s",
|
|
||||||
bookMetadataCache->coreMetadata.coverItemHref.size(), bookMetadataCache->coreMetadata.coverItemHref.c_str(),
|
|
||||||
bookMetadataCache->coreMetadata.textReferenceHref.size(),
|
|
||||||
bookMetadataCache->coreMetadata.textReferenceHref.c_str());
|
|
||||||
|
|
||||||
if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) {
|
|
||||||
// there was no textReference in epub, so we return 0 (the first chapter)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through spine items to get the correct index matching the text href
|
|
||||||
for (size_t i = 0; i < getSpineItemsCount(); i++) {
|
|
||||||
if (getSpineItem(i).href == bookMetadataCache->coreMetadata.textReferenceHref) {
|
|
||||||
LOG_DBG("EBP", "Text reference %s found at index %d", bookMetadataCache->coreMetadata.textReferenceHref.c_str(),
|
|
||||||
i);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This should not happen, as we checked for empty textReferenceHref earlier
|
|
||||||
LOG_DBG("EBP", "Section not found for text reference");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate progress in book (returns 0.0-1.0)
|
|
||||||
float Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) const {
|
|
||||||
const size_t bookSize = getBookSize();
|
|
||||||
if (bookSize == 0) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
const size_t prevChapterSize = (currentSpineIndex >= 1) ? getCumulativeSpineItemSize(currentSpineIndex - 1) : 0;
|
|
||||||
const size_t curChapterSize = getCumulativeSpineItemSize(currentSpineIndex) - prevChapterSize;
|
|
||||||
const float sectionProgSize = currentSpineRead * static_cast<float>(curChapterSize);
|
|
||||||
const float totalProgress = static_cast<float>(prevChapterSize) + sectionProgSize;
|
|
||||||
return totalProgress / static_cast<float>(bookSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Epub::resolveHrefToSpineIndex(const std::string& href) const {
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return -1;
|
|
||||||
|
|
||||||
// Split before decoding so escaped '#' characters in filenames stay part of the path.
|
|
||||||
const size_t hashPos = href.find('#');
|
|
||||||
const std::string rawTarget = hashPos != std::string::npos ? href.substr(0, hashPos) : href;
|
|
||||||
const std::string target = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(rawTarget));
|
|
||||||
|
|
||||||
// Same-file reference (anchor-only)
|
|
||||||
if (target.empty()) return -1;
|
|
||||||
|
|
||||||
// Extract just the filename for comparison
|
|
||||||
size_t targetSlash = target.find_last_of('/');
|
|
||||||
std::string targetFilename = (targetSlash != std::string::npos) ? target.substr(targetSlash + 1) : target;
|
|
||||||
|
|
||||||
for (int i = 0; i < getSpineItemsCount(); i++) {
|
|
||||||
const auto& spineHref = getSpineItem(i).href;
|
|
||||||
// Try exact match first
|
|
||||||
if (spineHref == target) return i;
|
|
||||||
// Then filename-only match
|
|
||||||
size_t spineSlash = spineHref.find_last_of('/');
|
|
||||||
std::string spineFilename = (spineSlash != std::string::npos) ? spineHref.substr(spineSlash + 1) : spineHref;
|
|
||||||
if (spineFilename == targetFilename) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Epub/BookMetadataCache.h"
|
|
||||||
#include "Epub/css/CssParser.h"
|
|
||||||
|
|
||||||
class ZipFile;
|
|
||||||
|
|
||||||
class Epub {
|
|
||||||
// the ncx file (EPUB 2)
|
|
||||||
std::string tocNcxItem;
|
|
||||||
// the nav file (EPUB 3)
|
|
||||||
std::string tocNavItem;
|
|
||||||
// where is the EPUBfile?
|
|
||||||
std::string filepath;
|
|
||||||
// the base path for items in the EPUB file
|
|
||||||
std::string contentBasePath;
|
|
||||||
// Uniq cache key based on filepath
|
|
||||||
std::string cachePath;
|
|
||||||
// Spine and TOC cache
|
|
||||||
std::unique_ptr<BookMetadataCache> bookMetadataCache;
|
|
||||||
// CSS parser for styling
|
|
||||||
std::unique_ptr<CssParser> cssParser;
|
|
||||||
// CSS files
|
|
||||||
std::vector<std::string> cssFiles;
|
|
||||||
|
|
||||||
bool findContentOpfFile(std::string* contentOpfFile) const;
|
|
||||||
bool parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata, bool writeSpineEntries = true);
|
|
||||||
bool parseTocNcxFile() const;
|
|
||||||
bool parseTocNavFile() const;
|
|
||||||
void discoverCssFilesFromZip();
|
|
||||||
void parseCssFiles() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
|
||||||
// create a cache key based on the filepath
|
|
||||||
cachePath = cacheDir + "/epub_" + std::to_string(std::hash<std::string>{}(this->filepath));
|
|
||||||
}
|
|
||||||
~Epub() = default;
|
|
||||||
std::string& getBasePath() { return contentBasePath; }
|
|
||||||
// MEMFIX-PORT: epub resident-bytes audit accessor; portable
|
|
||||||
// Approximate resident heap of the open book (audit): path strings, the CSS
|
|
||||||
// file list, and the parsed stylesheet. BookMetadataCache is file-backed
|
|
||||||
// (counts + HalFile handles) and contributes little.
|
|
||||||
size_t residentBytes() const {
|
|
||||||
size_t total = sizeof(Epub) + tocNcxItem.capacity() + tocNavItem.capacity() + filepath.capacity() +
|
|
||||||
contentBasePath.capacity() + cachePath.capacity();
|
|
||||||
for (const auto& f : cssFiles) total += sizeof(f) + (f.capacity() > 15 ? f.capacity() : 0);
|
|
||||||
if (cssParser) total += cssParser->residentBytes();
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
size_t cssRuleCount() const { return cssParser ? cssParser->ruleCount() : 0; }
|
|
||||||
bool load(bool buildIfMissing = true, bool skipLoadingCss = false);
|
|
||||||
bool clearCache() const;
|
|
||||||
void setupCacheDir() const;
|
|
||||||
const std::string& getCachePath() const;
|
|
||||||
const std::string& getPath() const;
|
|
||||||
const std::string& getTitle() const;
|
|
||||||
const std::string& getAuthor() const;
|
|
||||||
const std::string& getLanguage() const;
|
|
||||||
std::string getCoverBmpPath(bool cropped = false) const;
|
|
||||||
bool generateCoverBmp(bool cropped = false) const;
|
|
||||||
std::string getThumbBmpPath() const;
|
|
||||||
std::string getThumbBmpPath(int height) const;
|
|
||||||
bool generateThumbBmp(int height) const;
|
|
||||||
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
|
||||||
bool trailingNullByte = false) const;
|
|
||||||
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
|
||||||
bool getItemSize(const std::string& itemHref, size_t* size) const;
|
|
||||||
BookMetadataCache::SpineEntry getSpineItem(int spineIndex) const;
|
|
||||||
BookMetadataCache::TocEntry getTocItem(int tocIndex) const;
|
|
||||||
int getSpineItemsCount() const;
|
|
||||||
int getTocItemsCount() const;
|
|
||||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
|
||||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
|
||||||
size_t getCumulativeSpineItemSize(int spineIndex) const;
|
|
||||||
int getSpineIndexForTextReference() const;
|
|
||||||
|
|
||||||
size_t getBookSize() const;
|
|
||||||
float calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
|
||||||
CssParser* getCssParser() const { return cssParser.get(); }
|
|
||||||
int resolveHrefToSpineIndex(const std::string& href) const;
|
|
||||||
};
|
|
||||||
@@ -1,531 +0,0 @@
|
|||||||
#include "BookMetadataCache.h"
|
|
||||||
|
|
||||||
#include <BufferedFile.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
#include <Utf8.h>
|
|
||||||
#include <ZipFile.h>
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
#include "FsHelpers.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr uint8_t BOOK_CACHE_VERSION = 8; // v8: TOC/book titles stored NFC-composed
|
|
||||||
constexpr char bookBinFile[] = "/book.bin";
|
|
||||||
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
|
||||||
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
|
||||||
// Buffer size for the buildBookBin streams. 3 buffers x 4KB, transient (freed on
|
|
||||||
// return); 4KB = 8 SD sectors per transfer, enough to stop the sector-cache thrash.
|
|
||||||
constexpr size_t BUILD_IO_BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
// Entry (de)serializers, templated so they run over HalFile and the Buffered*
|
|
||||||
// wrappers alike (two instantiations each -- a few hundred bytes of flash, in
|
|
||||||
// exchange for the build path streaming at SD speed instead of per-pod).
|
|
||||||
template <typename F>
|
|
||||||
uint32_t writeSpineEntryTo(F& file, const BookMetadataCache::SpineEntry& entry) {
|
|
||||||
const uint32_t pos = file.position();
|
|
||||||
serialization::writeString(file, entry.href);
|
|
||||||
serialization::writePod(file, entry.cumulativeSize);
|
|
||||||
serialization::writePod(file, entry.tocIndex);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
uint32_t writeTocEntryTo(F& file, const BookMetadataCache::TocEntry& entry) {
|
|
||||||
const uint32_t pos = file.position();
|
|
||||||
serialization::writeString(file, entry.title);
|
|
||||||
serialization::writeString(file, entry.href);
|
|
||||||
serialization::writeString(file, entry.anchor);
|
|
||||||
serialization::writePod(file, entry.level);
|
|
||||||
serialization::writePod(file, entry.spineIndex);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
BookMetadataCache::SpineEntry readSpineEntryFrom(F& file) {
|
|
||||||
BookMetadataCache::SpineEntry entry;
|
|
||||||
serialization::readString(file, entry.href);
|
|
||||||
serialization::readPod(file, entry.cumulativeSize);
|
|
||||||
serialization::readPod(file, entry.tocIndex);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
BookMetadataCache::TocEntry readTocEntryFrom(F& file) {
|
|
||||||
BookMetadataCache::TocEntry entry;
|
|
||||||
serialization::readString(file, entry.title);
|
|
||||||
serialization::readString(file, entry.href);
|
|
||||||
serialization::readString(file, entry.anchor);
|
|
||||||
serialization::readPod(file, entry.level);
|
|
||||||
serialization::readPod(file, entry.spineIndex);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
|
||||||
|
|
||||||
bool BookMetadataCache::beginWrite() {
|
|
||||||
buildMode = true;
|
|
||||||
spineCount = 0;
|
|
||||||
tocCount = 0;
|
|
||||||
LOG_DBG("BMC", "Entering write mode");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::beginContentOpfPass() {
|
|
||||||
LOG_DBG("BMC", "Beginning content opf pass");
|
|
||||||
|
|
||||||
// Open spine file for writing
|
|
||||||
if (!Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Wrapper OOM is fine: createSpineEntry falls back to unbuffered writes.
|
|
||||||
passOut = makeUniqueNoThrow<serialization::BufferedFileWriter>(spineFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::endContentOpfPass() {
|
|
||||||
const bool flushed = !passOut || passOut->flush();
|
|
||||||
passOut.reset();
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
spineFile.close();
|
|
||||||
if (!flushed) {
|
|
||||||
LOG_ERR("BMC", "Failed writing spine tmp file");
|
|
||||||
}
|
|
||||||
return flushed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::beginTocPass() {
|
|
||||||
LOG_DBG("BMC", "Beginning toc pass");
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Storage.openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) {
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
spineFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
|
||||||
spineHrefIndex.clear();
|
|
||||||
spineHrefIndex.resize(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<uint16_t>(entry.href.size());
|
|
||||||
idx.spineIndex = static_cast<int16_t>(i);
|
|
||||||
spineHrefIndex[i] = 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;
|
|
||||||
LOG_DBG("BMC", "Using fast index for %d spine items", spineCount);
|
|
||||||
} else {
|
|
||||||
useSpineHrefIndex = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper OOM is fine: createTocEntry falls back to unbuffered writes.
|
|
||||||
passOut = makeUniqueNoThrow<serialization::BufferedFileWriter>(tocFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::endTocPass() {
|
|
||||||
const bool flushed = !passOut || passOut->flush();
|
|
||||||
passOut.reset();
|
|
||||||
if (!flushed) {
|
|
||||||
LOG_ERR("BMC", "Failed writing toc tmp file");
|
|
||||||
}
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
|
||||||
tocFile.close();
|
|
||||||
spineFile.close();
|
|
||||||
|
|
||||||
spineHrefIndex.clear();
|
|
||||||
spineHrefIndex.shrink_to_fit();
|
|
||||||
useSpineHrefIndex = false;
|
|
||||||
|
|
||||||
return flushed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::endWrite() {
|
|
||||||
if (!buildMode) {
|
|
||||||
LOG_DBG("BMC", "endWrite called but not in build mode");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildMode = false;
|
|
||||||
LOG_DBG("BMC", "Wrote %d spine, %d TOC entries", spineCount, tocCount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMetadata& metadata) {
|
|
||||||
// Open all three files, writing to meta, reading from spine and toc
|
|
||||||
if (!Storage.openFileForWrite("BMC", cachePath + bookBinFile, bookFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
bookFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) {
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
|
||||||
bookFile.close();
|
|
||||||
spineFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffered streams for the whole build: every access below is sequential per
|
|
||||||
// file, but interleaved ACROSS files, which thrashes SdFat's single shared
|
|
||||||
// sector cache when unbuffered (one 512B SD transaction per 4-byte pod --
|
|
||||||
// measured 31s for a 1,732-spine omnibus). Three 4KB buffers, freed on return.
|
|
||||||
serialization::BufferedFileWriter bookOut(bookFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
serialization::BufferedFileReader spineIn(spineFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
serialization::BufferedFileReader tocIn(tocFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
|
|
||||||
constexpr uint32_t headerASize =
|
|
||||||
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
|
||||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() +
|
|
||||||
metadata.coverItemHref.size() + metadata.textReferenceHref.size() +
|
|
||||||
sizeof(uint32_t) * 5;
|
|
||||||
const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
|
|
||||||
const uint32_t lutOffset = headerASize + metadataSize;
|
|
||||||
|
|
||||||
// Header A
|
|
||||||
serialization::writePod(bookOut, BOOK_CACHE_VERSION);
|
|
||||||
serialization::writePod(bookOut, lutOffset);
|
|
||||||
serialization::writePod(bookOut, spineCount);
|
|
||||||
serialization::writePod(bookOut, tocCount);
|
|
||||||
// Metadata
|
|
||||||
serialization::writeString(bookOut, metadata.title);
|
|
||||||
serialization::writeString(bookOut, metadata.author);
|
|
||||||
serialization::writeString(bookOut, metadata.language);
|
|
||||||
serialization::writeString(bookOut, metadata.coverItemHref);
|
|
||||||
serialization::writeString(bookOut, metadata.textReferenceHref);
|
|
||||||
|
|
||||||
// Loop through spine entries, writing LUT positions
|
|
||||||
spineIn.seek(0);
|
|
||||||
for (int i = 0; i < spineCount; i++) {
|
|
||||||
const uint32_t pos = spineIn.position();
|
|
||||||
readSpineEntryFrom(spineIn);
|
|
||||||
serialization::writePod(bookOut, pos + lutOffset + lutSize);
|
|
||||||
}
|
|
||||||
// Total size of the spine tmp file: entries land in book.bin after the toc LUT
|
|
||||||
// and the full spine block, so toc LUT positions are offset by it.
|
|
||||||
const auto spineBytes = static_cast<uint32_t>(spineIn.position());
|
|
||||||
|
|
||||||
// Loop through toc entries, writing LUT positions
|
|
||||||
tocIn.seek(0);
|
|
||||||
for (int i = 0; i < tocCount; i++) {
|
|
||||||
const uint32_t pos = tocIn.position();
|
|
||||||
readTocEntryFrom(tocIn);
|
|
||||||
serialization::writePod(bookOut, pos + lutOffset + lutSize + spineBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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::deque<int16_t> spineToTocIndex(spineCount, -1);
|
|
||||||
tocIn.seek(0);
|
|
||||||
for (int j = 0; j < tocCount; j++) {
|
|
||||||
auto tocEntry = readTocEntryFrom(tocIn);
|
|
||||||
if (tocEntry.spineIndex >= 0 && tocEntry.spineIndex < spineCount) {
|
|
||||||
if (spineToTocIndex[tocEntry.spineIndex] == -1) {
|
|
||||||
spineToTocIndex[tocEntry.spineIndex] = static_cast<int16_t>(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ZipFile zip(epubPath);
|
|
||||||
// Pre-open zip file to speed up size calculations
|
|
||||||
if (!zip.open()) {
|
|
||||||
LOG_ERR("BMC", "Could not open EPUB zip for size calculations");
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
|
||||||
bookFile.close();
|
|
||||||
spineFile.close();
|
|
||||||
tocFile.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::deque<uint32_t> spineSizes;
|
|
||||||
bool useBatchSizes = false;
|
|
||||||
|
|
||||||
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
|
||||||
LOG_DBG("BMC", "Using batch size lookup for %d spine items", spineCount);
|
|
||||||
|
|
||||||
std::deque<ZipFile::SizeTarget> targets;
|
|
||||||
targets.resize(spineCount);
|
|
||||||
|
|
||||||
spineIn.seek(0);
|
|
||||||
for (int i = 0; i < spineCount; i++) {
|
|
||||||
auto entry = readSpineEntryFrom(spineIn);
|
|
||||||
std::string path = FsHelpers::normalisePath(entry.href);
|
|
||||||
|
|
||||||
ZipFile::SizeTarget t;
|
|
||||||
t.hash = ZipFile::fnvHash64(path.c_str(), path.size());
|
|
||||||
t.len = static_cast<uint16_t>(path.size());
|
|
||||||
t.index = static_cast<uint16_t>(i);
|
|
||||||
targets[i] = 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);
|
|
||||||
LOG_DBG("BMC", "Batch lookup matched %d/%d spine items", matched, spineCount);
|
|
||||||
|
|
||||||
targets.clear();
|
|
||||||
targets.shrink_to_fit();
|
|
||||||
|
|
||||||
useBatchSizes = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cumSize = 0;
|
|
||||||
spineIn.seek(0);
|
|
||||||
int lastSpineTocIndex = -1;
|
|
||||||
for (int i = 0; i < spineCount; i++) {
|
|
||||||
auto spineEntry = readSpineEntryFrom(spineIn);
|
|
||||||
|
|
||||||
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
|
|
||||||
if (spineEntry.tocIndex == -1) {
|
|
||||||
LOG_DBG("BMC", "Warning: Could not find TOC entry for spine item %d: %s, using title from last section", i,
|
|
||||||
spineEntry.href.c_str());
|
|
||||||
spineEntry.tocIndex = lastSpineTocIndex;
|
|
||||||
}
|
|
||||||
lastSpineTocIndex = spineEntry.tocIndex;
|
|
||||||
|
|
||||||
size_t itemSize = 0;
|
|
||||||
if (useBatchSizes) {
|
|
||||||
itemSize = spineSizes[i];
|
|
||||||
if (itemSize == 0) {
|
|
||||||
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
|
||||||
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
|
||||||
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
|
||||||
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
|
||||||
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cumSize += itemSize;
|
|
||||||
spineEntry.cumulativeSize = cumSize;
|
|
||||||
|
|
||||||
// Write out spine data to book.bin
|
|
||||||
writeSpineEntryTo(bookOut, spineEntry);
|
|
||||||
}
|
|
||||||
// Close opened zip file
|
|
||||||
zip.close();
|
|
||||||
|
|
||||||
// Loop through toc entries from toc file writing to book.bin
|
|
||||||
tocIn.seek(0);
|
|
||||||
for (int i = 0; i < tocCount; i++) {
|
|
||||||
auto tocEntry = readTocEntryFrom(tocIn);
|
|
||||||
writeTocEntryTo(bookOut, tocEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool written = bookOut.flush();
|
|
||||||
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
|
||||||
bookFile.close();
|
|
||||||
spineFile.close();
|
|
||||||
tocFile.close();
|
|
||||||
|
|
||||||
if (!written) {
|
|
||||||
// A short write (card full/removed) would leave a truncated book.bin that
|
|
||||||
// still passes the version check on load; remove it so the next open rebuilds.
|
|
||||||
LOG_ERR("BMC", "Failed writing book.bin, removing truncated file");
|
|
||||||
Storage.remove((cachePath + bookBinFile).c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("BMC", "Successfully built book.bin");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BookMetadataCache::cleanupTmpFiles() const {
|
|
||||||
const auto spineBinFile = cachePath + tmpSpineBinFile;
|
|
||||||
if (Storage.exists(spineBinFile.c_str())) {
|
|
||||||
Storage.remove(spineBinFile.c_str());
|
|
||||||
}
|
|
||||||
const auto tocBinFile = cachePath + tmpTocBinFile;
|
|
||||||
if (Storage.exists(tocBinFile.c_str())) {
|
|
||||||
Storage.remove(tocBinFile.c_str());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t BookMetadataCache::writeSpineEntry(HalFile& file, const SpineEntry& entry) const {
|
|
||||||
return writeSpineEntryTo(file, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t BookMetadataCache::writeTocEntry(HalFile& file, const TocEntry& entry) const {
|
|
||||||
return writeTocEntryTo(file, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: for the LUT to be accurate, this **MUST** be called for all spine items before `addTocEntry` is ever called
|
|
||||||
// this is because in this function we're marking positions of the items
|
|
||||||
void BookMetadataCache::createSpineEntry(const std::string& href) {
|
|
||||||
if (!buildMode || !spineFile) {
|
|
||||||
LOG_DBG("BMC", "createSpineEntry called but not in build mode");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SpineEntry entry(href, 0, -1);
|
|
||||||
if (passOut) {
|
|
||||||
writeSpineEntryTo(*passOut, entry);
|
|
||||||
} else {
|
|
||||||
writeSpineEntry(spineFile, entry);
|
|
||||||
}
|
|
||||||
spineCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BookMetadataCache::createTocEntry(const std::string& title, const std::string& href, const std::string& anchor,
|
|
||||||
const uint8_t level) {
|
|
||||||
if (!buildMode || !tocFile || !spineFile) {
|
|
||||||
LOG_DBG("BMC", "createTocEntry called but not in build mode");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t spineIndex = -1;
|
|
||||||
|
|
||||||
if (useSpineHrefIndex) {
|
|
||||||
uint64_t targetHash = fnvHash64(href);
|
|
||||||
uint16_t targetLen = static_cast<uint16_t>(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) {
|
|
||||||
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", 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<int16_t>(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spineIndex == -1) {
|
|
||||||
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compose the title to NFC at index time so the cache stores precomposed glyphs;
|
|
||||||
// device fonts have no combining-mark positioning, so NFD titles render broken.
|
|
||||||
const TocEntry entry(utf8ComposeNfc(title), href, anchor, level, spineIndex);
|
|
||||||
if (passOut) {
|
|
||||||
writeTocEntryTo(*passOut, entry);
|
|
||||||
} else {
|
|
||||||
writeTocEntry(tocFile, entry);
|
|
||||||
}
|
|
||||||
tocCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============= READING / LOADING FUNCTIONS ================ */
|
|
||||||
|
|
||||||
bool BookMetadataCache::load() {
|
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t version;
|
|
||||||
serialization::readPod(bookFile, version);
|
|
||||||
if (version != BOOK_CACHE_VERSION) {
|
|
||||||
LOG_DBG("BMC", "Cache version mismatch: expected %d, got %d", BOOK_CACHE_VERSION, version);
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
bookFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialization::readPod(bookFile, lutOffset);
|
|
||||||
serialization::readPod(bookFile, spineCount);
|
|
||||||
serialization::readPod(bookFile, tocCount);
|
|
||||||
|
|
||||||
serialization::readString(bookFile, coreMetadata.title);
|
|
||||||
serialization::readString(bookFile, coreMetadata.author);
|
|
||||||
serialization::readString(bookFile, coreMetadata.language);
|
|
||||||
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
|
||||||
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
|
||||||
|
|
||||||
loaded = true;
|
|
||||||
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
|
|
||||||
if (!loaded) {
|
|
||||||
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0 || index >= static_cast<int>(spineCount)) {
|
|
||||||
LOG_ERR("BMC", "getSpineEntry index %d out of range", index);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek to spine LUT item, read from LUT and get out data
|
|
||||||
bookFile.seek(lutOffset + sizeof(uint32_t) * index);
|
|
||||||
uint32_t spineEntryPos;
|
|
||||||
serialization::readPod(bookFile, spineEntryPos);
|
|
||||||
bookFile.seek(spineEntryPos);
|
|
||||||
return readSpineEntry(bookFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
|
||||||
if (!loaded) {
|
|
||||||
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0 || index >= static_cast<int>(tocCount)) {
|
|
||||||
LOG_ERR("BMC", "getTocEntry index %d out of range", index);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek to TOC LUT item, read from LUT and get out data
|
|
||||||
bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index);
|
|
||||||
uint32_t tocEntryPos;
|
|
||||||
serialization::readPod(bookFile, tocEntryPos);
|
|
||||||
bookFile.seek(tocEntryPos);
|
|
||||||
return readTocEntry(bookFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
|
||||||
return readSpineEntryFrom(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const { return readTocEntryFrom(file); }
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <BufferedFile.h>
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class BookMetadataCache {
|
|
||||||
public:
|
|
||||||
struct BookMetadata {
|
|
||||||
std::string title;
|
|
||||||
std::string author;
|
|
||||||
std::string language;
|
|
||||||
std::string coverItemHref;
|
|
||||||
std::string textReferenceHref;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpineEntry {
|
|
||||||
std::string href;
|
|
||||||
uint32_t cumulativeSize;
|
|
||||||
int16_t tocIndex;
|
|
||||||
|
|
||||||
SpineEntry() : cumulativeSize(0), tocIndex(-1) {}
|
|
||||||
SpineEntry(std::string href, const uint32_t cumulativeSize, const int16_t tocIndex)
|
|
||||||
: href(std::move(href)), cumulativeSize(cumulativeSize), tocIndex(tocIndex) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TocEntry {
|
|
||||||
std::string title;
|
|
||||||
std::string href;
|
|
||||||
std::string anchor;
|
|
||||||
uint8_t level;
|
|
||||||
int16_t spineIndex;
|
|
||||||
|
|
||||||
TocEntry() : level(0), spineIndex(-1) {}
|
|
||||||
TocEntry(std::string title, std::string href, std::string anchor, const uint8_t level, const int16_t spineIndex)
|
|
||||||
: title(std::move(title)),
|
|
||||||
href(std::move(href)),
|
|
||||||
anchor(std::move(anchor)),
|
|
||||||
level(level),
|
|
||||||
spineIndex(spineIndex) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string cachePath;
|
|
||||||
uint32_t lutOffset;
|
|
||||||
uint16_t spineCount;
|
|
||||||
uint16_t tocCount;
|
|
||||||
bool loaded;
|
|
||||||
bool buildMode;
|
|
||||||
|
|
||||||
HalFile bookFile;
|
|
||||||
// Temp file handles during build
|
|
||||||
HalFile spineFile;
|
|
||||||
HalFile tocFile;
|
|
||||||
// Buffers the per-entry tmp-file writes during the OPF/TOC passes: those
|
|
||||||
// writes interleave with zip-inflate SD reads, and unbuffered they thrash
|
|
||||||
// SdFat's shared sector cache (one 512B transaction per 4-byte pod). One
|
|
||||||
// wrapper serves whichever pass is active (spine, then toc).
|
|
||||||
std::unique_ptr<serialization::BufferedFileWriter> passOut;
|
|
||||||
|
|
||||||
// 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::deque<SpineHrefIndexEntry> 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<uint8_t>(c);
|
|
||||||
hash *= 1099511628211ull;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t writeSpineEntry(HalFile& file, const SpineEntry& entry) const;
|
|
||||||
uint32_t writeTocEntry(HalFile& file, const TocEntry& entry) const;
|
|
||||||
SpineEntry readSpineEntry(HalFile& file) const;
|
|
||||||
TocEntry readTocEntry(HalFile& file) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
BookMetadata coreMetadata;
|
|
||||||
|
|
||||||
explicit BookMetadataCache(std::string cachePath)
|
|
||||||
: cachePath(std::move(cachePath)), lutOffset(0), spineCount(0), tocCount(0), loaded(false), buildMode(false) {}
|
|
||||||
~BookMetadataCache() = default;
|
|
||||||
|
|
||||||
// Building phase (stream to disk immediately)
|
|
||||||
bool beginWrite();
|
|
||||||
bool beginContentOpfPass();
|
|
||||||
void createSpineEntry(const std::string& href);
|
|
||||||
bool endContentOpfPass();
|
|
||||||
bool beginTocPass();
|
|
||||||
void createTocEntry(const std::string& title, const std::string& href, const std::string& anchor, uint8_t level);
|
|
||||||
bool endTocPass();
|
|
||||||
bool endWrite();
|
|
||||||
bool cleanupTmpFiles() const;
|
|
||||||
|
|
||||||
// Post-processing to update mappings and sizes
|
|
||||||
bool buildBookBin(const std::string& epubPath, const BookMetadata& metadata);
|
|
||||||
|
|
||||||
// Reading phase (read mode)
|
|
||||||
bool load();
|
|
||||||
SpineEntry getSpineEntry(int index);
|
|
||||||
TocEntry getTocEntry(int index);
|
|
||||||
int getSpineCount() const { return spineCount; }
|
|
||||||
int getTocCount() const { return tocCount; }
|
|
||||||
bool isLoaded() const { return loaded; }
|
|
||||||
};
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
#include "Page.h"
|
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
|
|
||||||
#include <new>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
template <typename Predicate>
|
|
||||||
void renderFilteredPageElements(const std::vector<std::shared_ptr<PageElement>>& elements, GfxRenderer& renderer,
|
|
||||||
const int fontId, const int xOffset, const int yOffset, Predicate&& predicate) {
|
|
||||||
for (const auto& element : elements) {
|
|
||||||
if (predicate(*element)) {
|
|
||||||
element->render(renderer, fontId, xOffset, yOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
|
||||||
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PageLine::serialize(HalFile& file) {
|
|
||||||
serialization::writePod(file, xPos);
|
|
||||||
serialization::writePod(file, yPos);
|
|
||||||
|
|
||||||
// serialize TextBlock pointed to by PageLine
|
|
||||||
return block->serialize(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PageLine> PageLine::deserialize(HalFile& file) {
|
|
||||||
int16_t xPos;
|
|
||||||
int16_t yPos;
|
|
||||||
serialization::readPod(file, xPos);
|
|
||||||
serialization::readPod(file, yPos);
|
|
||||||
|
|
||||||
auto tb = TextBlock::deserialize(file);
|
|
||||||
if (!tb) {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: null TextBlock");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* line = new (std::nothrow) PageLine(std::move(tb), xPos, yPos);
|
|
||||||
if (!line) {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: could not allocate PageLine");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return std::unique_ptr<PageLine>(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
|
||||||
// Images don't use fontId or text rendering
|
|
||||||
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PageImage::renderPlaceholder(GfxRenderer& renderer, const int xOffset, const int yOffset) const {
|
|
||||||
imageBlock->renderPlaceholder(renderer, xPos + xOffset, yPos + yOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PageImage::serialize(HalFile& file) {
|
|
||||||
serialization::writePod(file, xPos);
|
|
||||||
serialization::writePod(file, yPos);
|
|
||||||
|
|
||||||
// serialize ImageBlock
|
|
||||||
return imageBlock->serialize(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PageImage> PageImage::deserialize(HalFile& file) {
|
|
||||||
int16_t xPos;
|
|
||||||
int16_t yPos;
|
|
||||||
serialization::readPod(file, xPos);
|
|
||||||
serialization::readPod(file, yPos);
|
|
||||||
|
|
||||||
auto ib = ImageBlock::deserialize(file);
|
|
||||||
return std::unique_ptr<PageImage>(new PageImage(std::move(ib), xPos, yPos));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PageHorizontalRule::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
|
||||||
(void)fontId;
|
|
||||||
if (width == 0 || thickness == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.drawLine(xPos + xOffset, yPos + yOffset, xPos + xOffset + width - 1, yPos + yOffset, thickness, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PageHorizontalRule::serialize(HalFile& file) {
|
|
||||||
serialization::writePod(file, xPos);
|
|
||||||
serialization::writePod(file, yPos);
|
|
||||||
serialization::writePod(file, width);
|
|
||||||
serialization::writePod(file, thickness);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PageHorizontalRule> PageHorizontalRule::deserialize(HalFile& file) {
|
|
||||||
int16_t xPos = 0;
|
|
||||||
int16_t yPos = 0;
|
|
||||||
uint16_t width = 0;
|
|
||||||
uint8_t thickness = 0;
|
|
||||||
serialization::readPod(file, xPos);
|
|
||||||
serialization::readPod(file, yPos);
|
|
||||||
serialization::readPod(file, width);
|
|
||||||
serialization::readPod(file, thickness);
|
|
||||||
|
|
||||||
if (width == 0 || thickness == 0) {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: invalid horizontal rule metadata (width=%u thickness=%u)", width,
|
|
||||||
thickness);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* rule = new (std::nothrow) PageHorizontalRule(width, thickness, xPos, yPos);
|
|
||||||
if (!rule) {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: could not allocate PageHorizontalRule");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return std::unique_ptr<PageHorizontalRule>(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
|
|
||||||
renderFilteredPageElements(elements, renderer, fontId, xOffset, yOffset, [](const PageElement&) { return true; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void Page::renderImages(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
|
|
||||||
renderFilteredPageElements(elements, renderer, fontId, xOffset, yOffset,
|
|
||||||
[](const PageElement& element) { return element.getTag() == TAG_PageImage; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void Page::renderWithImagePlaceholders(GfxRenderer& renderer, const int fontId, const int xOffset,
|
|
||||||
const int yOffset) const {
|
|
||||||
for (const auto& element : elements) {
|
|
||||||
if (element->getTag() == TAG_PageImage) {
|
|
||||||
static_cast<const PageImage&>(*element).renderPlaceholder(renderer, xOffset, yOffset);
|
|
||||||
} else {
|
|
||||||
element->render(renderer, fontId, xOffset, yOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Page::serialize(HalFile& file) const {
|
|
||||||
const uint16_t count = elements.size();
|
|
||||||
serialization::writePod(file, count);
|
|
||||||
|
|
||||||
for (const auto& el : elements) {
|
|
||||||
// Use getTag() method to determine type
|
|
||||||
serialization::writePod(file, static_cast<uint8_t>(el->getTag()));
|
|
||||||
|
|
||||||
if (!el->serialize(file)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize footnotes (clamp to MAX_FOOTNOTES_PER_PAGE to match addFootnote/deserialize limits)
|
|
||||||
const uint16_t fnCount = std::min<uint16_t>(footnotes.size(), MAX_FOOTNOTES_PER_PAGE);
|
|
||||||
serialization::writePod(file, fnCount);
|
|
||||||
for (uint16_t i = 0; i < fnCount; i++) {
|
|
||||||
const auto& fn = footnotes[i];
|
|
||||||
if (file.write(fn.number, sizeof(fn.number)) != sizeof(fn.number) ||
|
|
||||||
file.write(fn.href, sizeof(fn.href)) != sizeof(fn.href)) {
|
|
||||||
LOG_ERR("PGE", "Failed to write footnote");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Page> Page::deserialize(HalFile& file) {
|
|
||||||
auto page = std::unique_ptr<Page>(new Page());
|
|
||||||
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(file, count);
|
|
||||||
// Reserve up front: growth-by-doubling needs old + new capacity live at once and
|
|
||||||
// reallocates repeatedly — a field crash (bad_alloc -> abort under -fno-exceptions)
|
|
||||||
// hit exactly this append path on a heavily fragmented heap.
|
|
||||||
page->elements.reserve(count);
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < count; i++) {
|
|
||||||
uint8_t tag;
|
|
||||||
serialization::readPod(file, tag);
|
|
||||||
|
|
||||||
if (tag == TAG_PageLine) {
|
|
||||||
auto pl = PageLine::deserialize(file);
|
|
||||||
if (!pl) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->elements.push_back(std::move(pl));
|
|
||||||
} else if (tag == TAG_PageImage) {
|
|
||||||
auto pi = PageImage::deserialize(file);
|
|
||||||
if (!pi) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->elements.push_back(std::move(pi));
|
|
||||||
} else if (tag == TAG_PageHorizontalRule) {
|
|
||||||
auto rule = PageHorizontalRule::deserialize(file);
|
|
||||||
if (!rule) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->elements.push_back(std::move(rule));
|
|
||||||
} else {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize footnotes
|
|
||||||
uint16_t fnCount;
|
|
||||||
serialization::readPod(file, fnCount);
|
|
||||||
if (fnCount > MAX_FOOTNOTES_PER_PAGE) {
|
|
||||||
LOG_ERR("PGE", "Invalid footnote count %u", fnCount);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->footnotes.resize(fnCount);
|
|
||||||
for (uint16_t i = 0; i < fnCount; i++) {
|
|
||||||
auto& entry = page->footnotes[i];
|
|
||||||
if (file.read(entry.number, sizeof(entry.number)) != sizeof(entry.number) ||
|
|
||||||
file.read(entry.href, sizeof(entry.href)) != sizeof(entry.href)) {
|
|
||||||
LOG_ERR("PGE", "Failed to read footnote %u", i);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
entry.number[sizeof(entry.number) - 1] = '\0';
|
|
||||||
entry.href[sizeof(entry.href) - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "FootnoteEntry.h"
|
|
||||||
#include "blocks/ImageBlock.h"
|
|
||||||
#include "blocks/TextBlock.h"
|
|
||||||
|
|
||||||
enum PageElementTag : uint8_t {
|
|
||||||
TAG_PageLine = 1,
|
|
||||||
TAG_PageImage = 2,
|
|
||||||
TAG_PageHorizontalRule = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
// represents something that has been added to a page
|
|
||||||
class PageElement {
|
|
||||||
public:
|
|
||||||
int16_t xPos;
|
|
||||||
int16_t yPos;
|
|
||||||
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
|
||||||
virtual ~PageElement() = default;
|
|
||||||
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0;
|
|
||||||
virtual bool serialize(HalFile& file) = 0;
|
|
||||||
virtual PageElementTag getTag() const = 0; // Add type identification
|
|
||||||
};
|
|
||||||
|
|
||||||
// a line from a block element
|
|
||||||
class PageLine final : public PageElement {
|
|
||||||
std::shared_ptr<TextBlock> block;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
|
||||||
: PageElement(xPos, yPos), block(std::move(block)) {}
|
|
||||||
const std::shared_ptr<TextBlock>& getBlock() const { return block; }
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
|
||||||
bool serialize(HalFile& file) override;
|
|
||||||
PageElementTag getTag() const override { return TAG_PageLine; }
|
|
||||||
static std::unique_ptr<PageLine> deserialize(HalFile& file);
|
|
||||||
};
|
|
||||||
|
|
||||||
// New PageImage class
|
|
||||||
class PageImage final : public PageElement {
|
|
||||||
std::shared_ptr<ImageBlock> imageBlock;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
|
||||||
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
|
||||||
void renderPlaceholder(GfxRenderer& renderer, int xOffset, int yOffset) const;
|
|
||||||
bool serialize(HalFile& file) override;
|
|
||||||
PageElementTag getTag() const override { return TAG_PageImage; }
|
|
||||||
static std::unique_ptr<PageImage> deserialize(HalFile& file);
|
|
||||||
const ImageBlock& getImageBlock() const { return *imageBlock; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class PageHorizontalRule final : public PageElement {
|
|
||||||
uint16_t width;
|
|
||||||
uint8_t thickness;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PageHorizontalRule(uint16_t width, uint8_t thickness, const int16_t xPos, const int16_t yPos)
|
|
||||||
: PageElement(xPos, yPos), width(width), thickness(thickness) {}
|
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
|
||||||
bool serialize(HalFile& file) override;
|
|
||||||
PageElementTag getTag() const override { return TAG_PageHorizontalRule; }
|
|
||||||
static std::unique_ptr<PageHorizontalRule> deserialize(HalFile& file);
|
|
||||||
};
|
|
||||||
|
|
||||||
class Page {
|
|
||||||
public:
|
|
||||||
// the list of block index and line numbers on this page
|
|
||||||
std::vector<std::shared_ptr<PageElement>> elements;
|
|
||||||
std::vector<FootnoteEntry> footnotes;
|
|
||||||
static constexpr uint16_t MAX_FOOTNOTES_PER_PAGE = 16;
|
|
||||||
|
|
||||||
void addFootnote(const char* number, const char* href) {
|
|
||||||
if (footnotes.size() >= MAX_FOOTNOTES_PER_PAGE) return; // Cap per-page footnotes
|
|
||||||
FootnoteEntry entry;
|
|
||||||
strncpy(entry.number, number, sizeof(entry.number) - 1);
|
|
||||||
entry.number[sizeof(entry.number) - 1] = '\0';
|
|
||||||
strncpy(entry.href, href, sizeof(entry.href) - 1);
|
|
||||||
entry.href[sizeof(entry.href) - 1] = '\0';
|
|
||||||
footnotes.push_back(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
|
||||||
void renderImages(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
|
||||||
void renderWithImagePlaceholders(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
|
||||||
bool serialize(HalFile& file) const;
|
|
||||||
static std::unique_ptr<Page> deserialize(HalFile& file);
|
|
||||||
|
|
||||||
// Check if page contains any images (used to force full refresh)
|
|
||||||
bool hasImages() const {
|
|
||||||
return std::any_of(elements.begin(), elements.end(),
|
|
||||||
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasImagesNeedingDecode() const {
|
|
||||||
return std::any_of(elements.begin(), elements.end(), [](const std::shared_ptr<PageElement>& element) {
|
|
||||||
return element->getTag() == TAG_PageImage &&
|
|
||||||
static_cast<const PageImage&>(*element).getImageBlock().needsDecode();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bounding box of all images on the page (union of image rects)
|
|
||||||
// Returns false if no images. Coordinates are relative to page origin.
|
|
||||||
bool getImageBoundingBox(int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const {
|
|
||||||
bool found = false;
|
|
||||||
int16_t minX = INT16_MAX, minY = INT16_MAX, maxX = INT16_MIN, maxY = INT16_MIN;
|
|
||||||
for (const auto& el : elements) {
|
|
||||||
if (el->getTag() == TAG_PageImage) {
|
|
||||||
const auto& img = static_cast<const PageImage&>(*el);
|
|
||||||
int16_t x = img.xPos;
|
|
||||||
int16_t y = img.yPos;
|
|
||||||
int16_t right = x + img.getImageBlock().getWidth();
|
|
||||||
int16_t bottom = y + img.getImageBlock().getHeight();
|
|
||||||
minX = std::min(minX, x);
|
|
||||||
minY = std::min(minY, y);
|
|
||||||
maxX = std::max(maxX, right);
|
|
||||||
maxY = std::max(maxY, bottom);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
outX = minX;
|
|
||||||
outY = minY;
|
|
||||||
outW = maxX - minX;
|
|
||||||
outH = maxY - minY;
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "blocks/BlockStyle.h"
|
|
||||||
#include "blocks/TextBlock.h"
|
|
||||||
|
|
||||||
class GfxRenderer;
|
|
||||||
|
|
||||||
class ParsedText {
|
|
||||||
std::vector<std::string> words;
|
|
||||||
std::vector<EpdFontFamily::Style> wordStyles;
|
|
||||||
std::vector<bool> wordContinues; // true = word attaches to previous with no break
|
|
||||||
std::vector<bool> wordNoSpaceBefore; // true = may break before token, but no synthetic space when joined
|
|
||||||
std::vector<bool> wordIsFocusSuffix; // true = token is the regular tail of a focus bold-prefix split
|
|
||||||
BlockStyle blockStyle;
|
|
||||||
bool extraParagraphSpacing;
|
|
||||||
bool hyphenationEnabled;
|
|
||||||
bool focusReadingEnabled;
|
|
||||||
bool isNaturalAlign;
|
|
||||||
bool hasRtlWord;
|
|
||||||
std::vector<std::string> reorderedWordsScratch;
|
|
||||||
std::vector<EpdFontFamily::Style> reorderedStylesScratch;
|
|
||||||
std::vector<uint16_t> reorderedWidthsScratch;
|
|
||||||
std::vector<bool> reorderedContinuesScratch;
|
|
||||||
std::vector<bool> reorderedNoSpaceBeforeScratch;
|
|
||||||
std::vector<bool> reorderedFocusSuffixScratch;
|
|
||||||
std::vector<uint16_t> visualOrderScratch;
|
|
||||||
|
|
||||||
int resolveFirstLineIndent(bool isFirstLine, const GfxRenderer& renderer, int fontId) const;
|
|
||||||
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
|
||||||
std::vector<bool>& noSpaceBeforeVec);
|
|
||||||
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
|
||||||
std::vector<bool>& noSpaceBeforeVec);
|
|
||||||
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
|
|
||||||
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks);
|
|
||||||
void extractLine(size_t breakIndex, int pageWidth, const std::vector<uint16_t>& wordWidths,
|
|
||||||
const std::vector<bool>& continuesVec, const std::vector<bool>& noSpaceBeforeVec,
|
|
||||||
const std::vector<size_t>& lineBreakIndices,
|
|
||||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine, const GfxRenderer& renderer,
|
|
||||||
int fontId);
|
|
||||||
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ParsedText(const bool extraParagraphSpacing, const bool hyphenationEnabled = false,
|
|
||||||
const bool focusReadingEnabled = false, const BlockStyle& blockStyle = BlockStyle())
|
|
||||||
: blockStyle(blockStyle),
|
|
||||||
extraParagraphSpacing(extraParagraphSpacing),
|
|
||||||
hyphenationEnabled(hyphenationEnabled),
|
|
||||||
focusReadingEnabled(focusReadingEnabled),
|
|
||||||
isNaturalAlign(false),
|
|
||||||
hasRtlWord(false) {}
|
|
||||||
~ParsedText() = default;
|
|
||||||
|
|
||||||
void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false, bool attachToPrevious = false);
|
|
||||||
void setBlockStyle(const BlockStyle& blockStyle) { this->blockStyle = blockStyle; }
|
|
||||||
BlockStyle& getBlockStyle() { return blockStyle; }
|
|
||||||
size_t size() const { return words.size(); }
|
|
||||||
bool isEmpty() const { return words.empty(); }
|
|
||||||
void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, uint16_t viewportWidth,
|
|
||||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
|
|
||||||
bool includeLastLine = true);
|
|
||||||
};
|
|
||||||
@@ -1,930 +0,0 @@
|
|||||||
#include "Section.h"
|
|
||||||
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Memory.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
|
|
||||||
#include "Epub/css/CssParser.h"
|
|
||||||
#include "Page.h"
|
|
||||||
#include "hyphenation/Hyphenator.h"
|
|
||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// v28: text decoration bits now include line-through in serialized wordStyles.
|
|
||||||
// v29: TextBlock word data stored as one flat arena (offset table + NUL-terminated
|
|
||||||
// text blob) instead of length-prefixed strings and per-field arrays.
|
|
||||||
// v30: Arabic shaping changed both drawing and measurement (getTextAdvanceX now
|
|
||||||
// measures the shaped visual text); cached word positions from v29 no longer
|
|
||||||
// match what drawText renders.
|
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 30;
|
|
||||||
// Written into the version field while a build is in progress; patched to
|
|
||||||
// SECTION_FILE_VERSION only when the build is finalized. An abandoned /
|
|
||||||
// crash-interrupted .bin therefore carries version 0, which loadSectionFile rejects
|
|
||||||
// as unknown and clears -- so an incomplete file is never mistaken for a valid one.
|
|
||||||
constexpr uint8_t SECTION_FILE_INCOMPLETE_VERSION = 0;
|
|
||||||
// Written when a build is suspended partway (reader exited or device slept mid-build).
|
|
||||||
// The file carries valid pages 0..pageCount-1, all LUTs, and a trailer with the parse
|
|
||||||
// watermark (bytesConsumed, totalBytes) appended after the li LUT. loadSectionFile
|
|
||||||
// accepts it so a resume shows those pages instantly; the reader extends it by
|
|
||||||
// rebuilding in the background. Uses the same header layout as SECTION_FILE_VERSION,
|
|
||||||
// so finalized files are untouched by this feature; older firmware treats the sentinel
|
|
||||||
// as an unknown version and rebuilds, which is a safe downgrade.
|
|
||||||
// MUST change in lockstep with SECTION_FILE_VERSION: the sentinel IS the partial's
|
|
||||||
// format version, so a stale-format partial otherwise passes the header check and
|
|
||||||
// only fails (noisily, via the block-decode error path) when a page is loaded.
|
|
||||||
// Derived so the pairing can't be forgotten: 0xFE for v28, 0xFD for v29, ...
|
|
||||||
constexpr uint8_t SECTION_FILE_PARTIAL_VERSION = 0xFE - (SECTION_FILE_VERSION - 28);
|
|
||||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
|
|
||||||
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
|
|
||||||
sizeof(uint8_t) + sizeof(bool) + sizeof(uint32_t) + sizeof(uint32_t) +
|
|
||||||
sizeof(uint32_t) + sizeof(uint32_t);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// Out-of-line so the unique_ptr<ChapterHtmlSlimParser> in BuildContext can be
|
|
||||||
// constructed/destroyed where the parser's full definition is visible.
|
|
||||||
Section::Section(const std::shared_ptr<Epub>& epub, const int spineIndex, GfxRenderer& renderer)
|
|
||||||
: epub(epub),
|
|
||||||
spineIndex(spineIndex),
|
|
||||||
renderer(renderer),
|
|
||||||
filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {}
|
|
||||||
|
|
||||||
// Suspend any in-progress build so every section.reset() / navigation / sleep path
|
|
||||||
// persists the pages already laid out as a partial .bin instead of discarding them
|
|
||||||
// (no-op once a build has completed or never started).
|
|
||||||
Section::~Section() { suspendBuild(); }
|
|
||||||
|
|
||||||
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
|
||||||
if (!file) {
|
|
||||||
LOG_ERR("SCT", "File not open for writing page %d", builtPageCount_);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t position = file.position();
|
|
||||||
if (!page->serialize(file)) {
|
|
||||||
LOG_ERR("SCT", "Failed to serialize page %d", builtPageCount_);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
LOG_DBG("SCT", "Page %d processed", builtPageCount_);
|
|
||||||
|
|
||||||
builtPageCount_++;
|
|
||||||
// pageCount is the pages available to read: a rebuild over a partial only raises it
|
|
||||||
// once it has laid out more pages than the partial already covers.
|
|
||||||
if (builtPageCount_ > pageCount) {
|
|
||||||
pageCount = builtPageCount_;
|
|
||||||
}
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
|
||||||
const bool embeddedStyle, const uint8_t imageRendering,
|
|
||||||
const bool focusReadingEnabled) {
|
|
||||||
if (!file) {
|
|
||||||
LOG_DBG("SCT", "File not open for writing header");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
|
||||||
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
|
|
||||||
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) +
|
|
||||||
sizeof(embeddedStyle) + sizeof(imageRendering) + sizeof(focusReadingEnabled) +
|
|
||||||
sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t),
|
|
||||||
"Header size mismatch");
|
|
||||||
// Written as the incomplete sentinel; finalizeBuild() patches it to
|
|
||||||
// SECTION_FILE_VERSION as the last step, committing the file.
|
|
||||||
serialization::writePod(file, SECTION_FILE_INCOMPLETE_VERSION);
|
|
||||||
serialization::writePod(file, fontId);
|
|
||||||
serialization::writePod(file, lineCompression);
|
|
||||||
serialization::writePod(file, extraParagraphSpacing);
|
|
||||||
serialization::writePod(file, paragraphAlignment);
|
|
||||||
serialization::writePod(file, viewportWidth);
|
|
||||||
serialization::writePod(file, viewportHeight);
|
|
||||||
serialization::writePod(file, hyphenationEnabled);
|
|
||||||
serialization::writePod(file, embeddedStyle);
|
|
||||||
serialization::writePod(file, imageRendering);
|
|
||||||
serialization::writePod(file, focusReadingEnabled);
|
|
||||||
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0, patched later)
|
|
||||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset (patched later)
|
|
||||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for anchor map offset (patched later)
|
|
||||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for paragraph LUT offset (patched later)
|
|
||||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for li LUT offset (patched later)
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
|
||||||
const uint8_t imageRendering, const bool focusReadingEnabled) {
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, file)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match parameters
|
|
||||||
bool filePartial = false;
|
|
||||||
{
|
|
||||||
uint8_t version;
|
|
||||||
serialization::readPod(file, version);
|
|
||||||
if (version != SECTION_FILE_VERSION && version != SECTION_FILE_PARTIAL_VERSION) {
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
file.close();
|
|
||||||
LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version);
|
|
||||||
clearCache();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
filePartial = (version == SECTION_FILE_PARTIAL_VERSION);
|
|
||||||
|
|
||||||
int fileFontId;
|
|
||||||
uint16_t fileViewportWidth, fileViewportHeight;
|
|
||||||
float fileLineCompression;
|
|
||||||
bool fileExtraParagraphSpacing;
|
|
||||||
uint8_t fileParagraphAlignment;
|
|
||||||
bool fileHyphenationEnabled;
|
|
||||||
bool fileEmbeddedStyle;
|
|
||||||
uint8_t fileImageRendering;
|
|
||||||
bool fileFocusReadingEnabled;
|
|
||||||
serialization::readPod(file, fileFontId);
|
|
||||||
serialization::readPod(file, fileLineCompression);
|
|
||||||
serialization::readPod(file, fileExtraParagraphSpacing);
|
|
||||||
serialization::readPod(file, fileParagraphAlignment);
|
|
||||||
serialization::readPod(file, fileViewportWidth);
|
|
||||||
serialization::readPod(file, fileViewportHeight);
|
|
||||||
serialization::readPod(file, fileHyphenationEnabled);
|
|
||||||
serialization::readPod(file, fileEmbeddedStyle);
|
|
||||||
serialization::readPod(file, fileImageRendering);
|
|
||||||
serialization::readPod(file, fileFocusReadingEnabled);
|
|
||||||
|
|
||||||
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
|
||||||
extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment ||
|
|
||||||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
|
||||||
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle ||
|
|
||||||
imageRendering != fileImageRendering || focusReadingEnabled != fileFocusReadingEnabled) {
|
|
||||||
file.close();
|
|
||||||
LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
|
|
||||||
clearCache();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serialization::readPod(file, pageCount);
|
|
||||||
|
|
||||||
if (filePartial) {
|
|
||||||
// A partial's pageCount is the watermark of a suspended build. Read the watermark
|
|
||||||
// trailer (appended after the li LUT) so estimatedTotalPages can extrapolate.
|
|
||||||
uint32_t liLutOffset = 0;
|
|
||||||
file.seek(HEADER_SIZE - sizeof(uint32_t));
|
|
||||||
serialization::readPod(file, liLutOffset);
|
|
||||||
const uint32_t trailerOffset = liLutOffset + static_cast<uint32_t>(pageCount) * sizeof(uint16_t);
|
|
||||||
const bool trailerValid =
|
|
||||||
pageCount > 0 && liLutOffset >= HEADER_SIZE && trailerOffset + 2 * sizeof(uint32_t) <= file.size();
|
|
||||||
if (!trailerValid) {
|
|
||||||
file.close();
|
|
||||||
LOG_ERR("SCT", "Deserialization failed: malformed partial section");
|
|
||||||
clearCache();
|
|
||||||
pageCount = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
file.seek(trailerOffset);
|
|
||||||
serialization::readPod(file, partialBytesConsumed_);
|
|
||||||
serialization::readPod(file, partialTotalBytes_);
|
|
||||||
partial_ = true;
|
|
||||||
partialPageCount_ = pageCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
file.close();
|
|
||||||
LOG_DBG("SCT", "Deserialization succeeded: %d pages%s", pageCount, filePartial ? " (partial)" : "");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
|
||||||
bool Section::clearCache() const {
|
|
||||||
const std::string tmpBin = binTmpPath();
|
|
||||||
if (Storage.exists(tmpBin.c_str())) {
|
|
||||||
Storage.remove(tmpBin.c_str());
|
|
||||||
}
|
|
||||||
if (!Storage.exists(filePath.c_str())) {
|
|
||||||
LOG_DBG("SCT", "Cache does not exist, no action needed");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.remove(filePath.c_str())) {
|
|
||||||
LOG_ERR("SCT", "Failed to clear cache");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("SCT", "Cache cleared successfully");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
|
||||||
const uint8_t imageRendering, const bool focusReadingEnabled,
|
|
||||||
const std::function<void()>& popupFn) {
|
|
||||||
// One-shot build: start, then lay out the whole section in a single pass.
|
|
||||||
if (!startBuild(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, viewportHeight,
|
|
||||||
hyphenationEnabled, embeddedStyle, imageRendering, focusReadingEnabled, popupFn)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!buildSomeMore(0)) { // 0 = build to completion
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return buildComplete_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::startBuild(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint16_t viewportHeight,
|
|
||||||
const bool hyphenationEnabled, const bool embeddedStyle, const uint8_t imageRendering,
|
|
||||||
const bool focusReadingEnabled, const std::function<void()>& popupFn) {
|
|
||||||
if (build_) {
|
|
||||||
LOG_ERR("SCT", "startBuild called while a build is already active");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buildComplete_ = false;
|
|
||||||
builtPageCount_ = 0;
|
|
||||||
// Pages from a loaded partial stay readable (from filePath) while this build writes
|
|
||||||
// to the tmp .bin, so availability never drops below the partial's watermark.
|
|
||||||
pageCount = partial_ ? partialPageCount_ : 0;
|
|
||||||
|
|
||||||
// Remove a stale tmp .bin from a crash-interrupted build; this build recreates it.
|
|
||||||
{
|
|
||||||
const std::string staleTmp = binTmpPath();
|
|
||||||
if (Storage.exists(staleTmp.c_str())) {
|
|
||||||
Storage.remove(staleTmp.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto localPath = epub->getSpineItem(spineIndex).href;
|
|
||||||
const auto htmlDir = epub->getCachePath() + "/html";
|
|
||||||
const auto htmlPath = htmlDir + "/" + std::to_string(spineIndex) + ".html";
|
|
||||||
const auto tmpHtmlPath = htmlDir + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
|
||||||
|
|
||||||
// Create cache directory if it doesn't exist
|
|
||||||
{
|
|
||||||
const auto sectionsDir = epub->getCachePath() + "/sections";
|
|
||||||
Storage.mkdir(sectionsDir.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reuse the previously unzipped HTML if we already have it. The unzipped HTML is keyed only on the
|
|
||||||
// book (it lives in the per-book cache dir), not on render settings, so it survives the invalidation
|
|
||||||
// that wipes the layout (.bin) caches when font/margin/orientation change -- rebuilds then skip zip
|
|
||||||
// inflation entirely. It's promoted by an atomic rename as soon as the inflate succeeds (below), so
|
|
||||||
// even a window-only giant spine -- whose .bin never finalizes -- still caches its HTML, letting a
|
|
||||||
// reopen skip the multi-second inflate. If htmlPath exists it is known-complete.
|
|
||||||
const bool reusedHtml = Storage.exists(htmlPath.c_str());
|
|
||||||
bool htmlCached = reusedHtml;
|
|
||||||
if (reusedHtml) {
|
|
||||||
LOG_DBG("SCT", "Reusing cached HTML %s", htmlPath.c_str());
|
|
||||||
} else {
|
|
||||||
Storage.mkdir(htmlDir.c_str());
|
|
||||||
|
|
||||||
// Retry logic for SD card timing issues
|
|
||||||
bool streamed = false;
|
|
||||||
uint32_t fileSize = 0;
|
|
||||||
for (int attempt = 0; attempt < 3 && !streamed; attempt++) {
|
|
||||||
if (attempt > 0) {
|
|
||||||
LOG_DBG("SCT", "Retrying stream (attempt %d)...", attempt + 1);
|
|
||||||
delay(50); // Brief delay before retry
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any incomplete file from previous attempt before retrying
|
|
||||||
if (Storage.exists(tmpHtmlPath.c_str())) {
|
|
||||||
Storage.remove(tmpHtmlPath.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile tmpHtml;
|
|
||||||
if (!Storage.openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Larger chunks mean far fewer SD writes inflating the HTML; a 1KB chunk turned a 584KB
|
|
||||||
// single-spine novel into ~570 tiny writes (multi-second). 8KB keeps the transient buffers
|
|
||||||
// small while cutting the write count 8x.
|
|
||||||
streamed = epub->readItemContentsToStream(localPath, tmpHtml, 8192);
|
|
||||||
fileSize = tmpHtml.size();
|
|
||||||
// Explicitly close() file before calling Storage.remove()
|
|
||||||
tmpHtml.close();
|
|
||||||
|
|
||||||
// If streaming failed, remove the incomplete file immediately
|
|
||||||
if (!streamed && Storage.exists(tmpHtmlPath.c_str())) {
|
|
||||||
Storage.remove(tmpHtmlPath.c_str());
|
|
||||||
LOG_DBG("SCT", "Removed incomplete temp file after failed attempt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!streamed) {
|
|
||||||
LOG_ERR("SCT", "Failed to stream item contents to temp file after retries");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("SCT", "Streamed temp HTML to %s (%d bytes)", tmpHtmlPath.c_str(), fileSize);
|
|
||||||
|
|
||||||
// Promote to the persistent HTML cache immediately -- the inflate is complete and the bytes are
|
|
||||||
// valid regardless of whether the layout build finishes, so reopening (even a window-only spine
|
|
||||||
// that never finalizes its .bin) skips re-inflation. If the rename fails we just parse the temp.
|
|
||||||
if (Storage.rename(tmpHtmlPath.c_str(), htmlPath.c_str())) {
|
|
||||||
htmlCached = true;
|
|
||||||
} else {
|
|
||||||
LOG_DBG("SCT", "Failed to promote HTML cache; parsing from temp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Storage.openFileForWrite("SCT", binTmpPath(), file)) {
|
|
||||||
if (!reusedHtml) Storage.remove(tmpHtmlPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Header is written with the incomplete-version sentinel; finalizeBuild() commits it.
|
|
||||||
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
|
|
||||||
viewportHeight, hyphenationEnabled, embeddedStyle, imageRendering, focusReadingEnabled);
|
|
||||||
|
|
||||||
auto ctx = makeUniqueNoThrow<BuildContext>();
|
|
||||||
if (!ctx) {
|
|
||||||
LOG_ERR("SCT", "OOM: BuildContext");
|
|
||||||
file.close();
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
if (!reusedHtml) Storage.remove(tmpHtmlPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// htmlCached == "htmlPath is the live cache" (reused, or just promoted). finalizeBuild/abandonBuild
|
|
||||||
// then leave the cached HTML alone; only an un-promoted temp (rename failed) is theirs to clean up.
|
|
||||||
ctx->reusedHtml = htmlCached;
|
|
||||||
ctx->htmlPath = htmlPath;
|
|
||||||
ctx->tmpHtmlPath = tmpHtmlPath;
|
|
||||||
ctx->parsePath = htmlCached ? htmlPath : tmpHtmlPath;
|
|
||||||
|
|
||||||
// Derive the content base directory and image cache path prefix for the parser
|
|
||||||
const size_t lastSlash = localPath.find_last_of('/');
|
|
||||||
ctx->contentBase = (lastSlash != std::string::npos) ? localPath.substr(0, lastSlash + 1) : "";
|
|
||||||
ctx->imageBasePath = epub->getCachePath() + "/img_" + std::to_string(spineIndex) + "_";
|
|
||||||
|
|
||||||
if (embeddedStyle) {
|
|
||||||
ctx->cssParser = epub->getCssParser();
|
|
||||||
if (ctx->cssParser && !ctx->cssParser->loadFromCache()) {
|
|
||||||
LOG_ERR("SCT", "Failed to load CSS from cache");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect TOC anchors for this spine so the parser can insert page breaks at chapter boundaries
|
|
||||||
std::vector<std::string> tocAnchors;
|
|
||||||
const int startTocIndex = epub->getTocIndexForSpineIndex(spineIndex);
|
|
||||||
if (startTocIndex >= 0) {
|
|
||||||
for (int i = startTocIndex; i < epub->getTocItemsCount(); i++) {
|
|
||||||
auto entry = epub->getTocItem(i);
|
|
||||||
if (entry.spineIndex != spineIndex) break;
|
|
||||||
if (!entry.anchor.empty()) {
|
|
||||||
tocAnchors.push_back(std::move(entry.anchor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The parser stores the path/contentBase/imageBasePath by reference, so they must
|
|
||||||
// live in the BuildContext (which outlives the parser). The page-complete callback
|
|
||||||
// captures the BuildContext pointer to append to its in-RAM LUT; build_ owns the
|
|
||||||
// context for the parser's whole lifetime.
|
|
||||||
BuildContext* ctxPtr = ctx.get();
|
|
||||||
ctx->parser = makeUniqueNoThrow<ChapterHtmlSlimParser>(
|
|
||||||
epub, ctxPtr->parsePath, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment,
|
|
||||||
viewportWidth, viewportHeight, hyphenationEnabled, focusReadingEnabled,
|
|
||||||
[this, ctxPtr](std::unique_ptr<Page> page, const uint16_t paragraphIndex, const uint16_t listItemIndex) {
|
|
||||||
ctxPtr->lut.push_back({this->onPageComplete(std::move(page)), paragraphIndex, listItemIndex});
|
|
||||||
},
|
|
||||||
embeddedStyle, ctxPtr->contentBase, ctxPtr->imageBasePath, imageRendering, std::move(tocAnchors), popupFn,
|
|
||||||
ctxPtr->cssParser);
|
|
||||||
if (!ctx->parser) {
|
|
||||||
LOG_ERR("SCT", "OOM: ChapterHtmlSlimParser");
|
|
||||||
if (ctx->cssParser) ctx->cssParser->clear();
|
|
||||||
file.close();
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
if (!reusedHtml) Storage.remove(tmpHtmlPath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Hyphenator::setPreferredLanguage(epub->getLanguage());
|
|
||||||
build_ = std::move(ctx);
|
|
||||||
|
|
||||||
if (!build_->parser->beginParse()) {
|
|
||||||
LOG_ERR("SCT", "Failed to begin parse");
|
|
||||||
abandonBuild();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
build_->totalBytes = build_->parser->parseTotalBytes();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::buildSomeMore(const int maxPages) {
|
|
||||||
if (!build_ || !build_->parser) {
|
|
||||||
LOG_ERR("SCT", "buildSomeMore with no active build");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Pace on pages laid out by THIS build, not pageCount: during a rebuild over a partial,
|
|
||||||
// pageCount stays pinned at the partial's watermark until the build passes it, which
|
|
||||||
// would otherwise turn one "small" chunk into a blocking rebuild of the whole watermark.
|
|
||||||
const int startCount = builtPageCount_;
|
|
||||||
for (;;) {
|
|
||||||
const auto status = build_->parser->parseStep();
|
|
||||||
if (status == ChapterHtmlSlimParser::ParseStatus::Error) {
|
|
||||||
LOG_ERR("SCT", "Parse error during incremental build");
|
|
||||||
abandonBuild();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (status == ChapterHtmlSlimParser::ParseStatus::Done) {
|
|
||||||
return finalizeBuild();
|
|
||||||
}
|
|
||||||
// ParseStatus::More: yield once we've laid out the requested number of pages.
|
|
||||||
if (maxPages > 0 && (builtPageCount_ - startCount) >= maxPages) {
|
|
||||||
build_->bytesConsumed = build_->parser->parseBytesConsumed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::hasHtmlCache() const {
|
|
||||||
const std::string htmlPath = epub->getCachePath() + "/html/" + std::to_string(spineIndex) + ".html";
|
|
||||||
return Storage.exists(htmlPath.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::findAnchorDuringBuild(const std::string& anchor) const {
|
|
||||||
if (!build_ || !build_->parser) return std::nullopt;
|
|
||||||
for (const auto& [key, page] : build_->parser->getAnchors()) {
|
|
||||||
if (key == anchor) return page;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::findAnchor(const std::string& anchor) const {
|
|
||||||
if (const auto page = findAnchorDuringBuild(anchor)) {
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
// Fall back to the on-disk anchor map: a finalized section, or a partial whose map
|
|
||||||
// covers everything up to its watermark (nullopt past it -- build further and retry).
|
|
||||||
return getPageForAnchor(anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t Section::estimatedTotalPages() const {
|
|
||||||
// Extrapolation from a suspended session's watermark trailer. A static snapshot, so no EMA
|
|
||||||
// damping is needed. Also the best guess while a rebuild is running but hasn't laid out
|
|
||||||
// enough pages yet to extrapolate from its own progress.
|
|
||||||
const auto partialEstimate = [this]() -> uint16_t {
|
|
||||||
if (!partial_ || partialBytesConsumed_ == 0 || partialTotalBytes_ <= partialBytesConsumed_) {
|
|
||||||
return pageCount;
|
|
||||||
}
|
|
||||||
const uint64_t est = static_cast<uint64_t>(partialPageCount_) * partialTotalBytes_ / partialBytesConsumed_;
|
|
||||||
if (est <= pageCount) return pageCount;
|
|
||||||
return est > 60000 ? 60000 : static_cast<uint16_t>(est);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!build_) {
|
|
||||||
return partial_ ? partialEstimate() : pageCount; // partial -> extrapolate, finalized -> exact
|
|
||||||
}
|
|
||||||
const uint32_t consumed = build_->bytesConsumed;
|
|
||||||
const uint32_t total = build_->totalBytes;
|
|
||||||
if (builtPageCount_ == 0 || consumed == 0 || total <= consumed) return partialEstimate();
|
|
||||||
|
|
||||||
// Raw extrapolation: scale the pages built so far by the fraction of HTML still unparsed. This
|
|
||||||
// re-derives from a growing, non-uniform sample, so it jitters up and down as the build crosses
|
|
||||||
// dense vs sparse regions of the chapter.
|
|
||||||
const uint64_t raw = static_cast<uint64_t>(builtPageCount_) * total / consumed;
|
|
||||||
|
|
||||||
// Damp that jitter with an exponential moving average. Step it once per build advance (keyed on
|
|
||||||
// bytesConsumed) rather than per status-bar redraw, so the smoothing rate doesn't depend on how
|
|
||||||
// often we repaint. As the build nears the end, consumed -> total and raw -> the built count, so
|
|
||||||
// the average settles onto the true count (and finalizeBuild then returns the exact pageCount).
|
|
||||||
constexpr float ALPHA = 0.25f; // weight of each new sample; lower = steadier but slower to settle
|
|
||||||
if (build_->smoothedEstimate <= 0) {
|
|
||||||
build_->smoothedEstimate = static_cast<float>(raw); // seed on the first estimate
|
|
||||||
} else if (consumed != build_->smoothedAtConsumed) {
|
|
||||||
build_->smoothedEstimate += ALPHA * (static_cast<float>(raw) - build_->smoothedEstimate);
|
|
||||||
}
|
|
||||||
build_->smoothedAtConsumed = consumed;
|
|
||||||
|
|
||||||
const uint64_t est = static_cast<uint64_t>(build_->smoothedEstimate + 0.5f);
|
|
||||||
if (est <= pageCount) return pageCount; // never fewer than the pages already available
|
|
||||||
return est > 60000 ? 60000 : static_cast<uint16_t>(est);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the LUTs and anchor map into the open tmp .bin, patch the header with the built
|
|
||||||
// page count and table offsets, stamp `version` as the commit point, then swap the tmp
|
|
||||||
// file over filePath. For SECTION_FILE_PARTIAL_VERSION a watermark trailer
|
|
||||||
// (bytesConsumed, totalBytes) is appended after the li LUT so a later open can estimate
|
|
||||||
// the total page count. The parser must still be alive (anchors are read from it).
|
|
||||||
// On failure the tmp is removed and any pre-existing file at filePath is left intact.
|
|
||||||
bool Section::commitBuildFile(const uint8_t version, const uint32_t bytesConsumed, const uint32_t totalBytes) {
|
|
||||||
const bool asPartial = (version == SECTION_FILE_PARTIAL_VERSION);
|
|
||||||
|
|
||||||
const auto failCommit = [this]() {
|
|
||||||
// Explicit close() required before remove (member variable, O_RDWR handle).
|
|
||||||
file.close();
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const uint32_t lutOffset = file.position();
|
|
||||||
for (const auto& entry : build_->lut) {
|
|
||||||
if (entry.fileOffset == 0) {
|
|
||||||
LOG_ERR("SCT", "Failed to write LUT due to invalid page positions");
|
|
||||||
return failCommit();
|
|
||||||
}
|
|
||||||
serialization::writePod(file, entry.fileOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write anchor-to-page map for fragment navigation (e.g. footnote targets). For a
|
|
||||||
// partial, skip anchors that landed on the incomplete trailing page the suspend drops.
|
|
||||||
const uint32_t anchorMapOffset = file.position();
|
|
||||||
const auto& anchors = build_->parser->getAnchors();
|
|
||||||
uint16_t anchorCount = 0;
|
|
||||||
for (const auto& [anchor, page] : anchors) {
|
|
||||||
if (!asPartial || page < builtPageCount_) anchorCount++;
|
|
||||||
}
|
|
||||||
serialization::writePod(file, anchorCount);
|
|
||||||
for (const auto& [anchor, page] : anchors) {
|
|
||||||
if (asPartial && page >= builtPageCount_) continue;
|
|
||||||
serialization::writeString(file, anchor);
|
|
||||||
serialization::writePod(file, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t paragraphLutOffset = file.position();
|
|
||||||
serialization::writePod(file, static_cast<uint16_t>(build_->lut.size()));
|
|
||||||
for (const auto& entry : build_->lut) {
|
|
||||||
serialization::writePod(file, entry.paragraphIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t liLutFileOffset = static_cast<uint32_t>(file.position());
|
|
||||||
for (const auto& entry : build_->lut) {
|
|
||||||
serialization::writePod(file, entry.listItemIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asPartial) {
|
|
||||||
// Watermark trailer, located on load as liLutOffset + pageCount * sizeof(uint16_t).
|
|
||||||
serialization::writePod(file, bytesConsumed);
|
|
||||||
serialization::writePod(file, totalBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch header with the built page count and section offsets...
|
|
||||||
file.seek(HEADER_SIZE - sizeof(uint32_t) * 4 - sizeof(builtPageCount_));
|
|
||||||
serialization::writePod(file, builtPageCount_);
|
|
||||||
serialization::writePod(file, lutOffset);
|
|
||||||
serialization::writePod(file, anchorMapOffset);
|
|
||||||
serialization::writePod(file, paragraphLutOffset);
|
|
||||||
serialization::writePod(file, liLutFileOffset);
|
|
||||||
// ...then commit by overwriting the sentinel version with the real one. Writing the
|
|
||||||
// version last makes it the commit point: a crash before here leaves version 0.
|
|
||||||
file.seek(0);
|
|
||||||
serialization::writePod(file, version);
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
// Swap into place. A crash between remove and rename loses the old file but keeps a
|
|
||||||
// fully-committed tmp; the next build just removes it and rebuilds.
|
|
||||||
if (Storage.exists(filePath.c_str())) {
|
|
||||||
Storage.remove(filePath.c_str());
|
|
||||||
}
|
|
||||||
if (!Storage.rename(binTmpPath().c_str(), filePath.c_str())) {
|
|
||||||
LOG_ERR("SCT", "Failed to move built section into place");
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Section::finalizeBuild() {
|
|
||||||
// Flush the trailing page (emits the last page via the completePageFn into the LUT).
|
|
||||||
build_->parser->finishParse();
|
|
||||||
|
|
||||||
if (!build_->reusedHtml) {
|
|
||||||
// Parse succeeded: promote the freshly unzipped HTML to the persistent cache so future
|
|
||||||
// rebuilds skip zip inflation. If promotion fails, drop the temp -- the build still succeeded.
|
|
||||||
if (!Storage.rename(build_->tmpHtmlPath.c_str(), build_->htmlPath.c_str())) {
|
|
||||||
LOG_DBG("SCT", "Failed to promote HTML cache, removing temp");
|
|
||||||
Storage.remove(build_->tmpHtmlPath.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool committed = commitBuildFile(SECTION_FILE_VERSION, 0, 0);
|
|
||||||
if (build_->cssParser) build_->cssParser->clear();
|
|
||||||
build_.reset();
|
|
||||||
if (!committed) {
|
|
||||||
// commitBuildFile removed filePath before the failed swap, so nothing valid remains.
|
|
||||||
partial_ = false;
|
|
||||||
partialPageCount_ = 0;
|
|
||||||
pageCount = 0;
|
|
||||||
builtPageCount_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buildComplete_ = true;
|
|
||||||
partial_ = false;
|
|
||||||
partialPageCount_ = 0;
|
|
||||||
pageCount = builtPageCount_;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Section::suspendBuild() {
|
|
||||||
if (!build_) return;
|
|
||||||
|
|
||||||
// Only worth persisting if this build produced pages a pre-existing partial doesn't
|
|
||||||
// already cover; otherwise keep the older (bigger) partial and just drop the tmp.
|
|
||||||
const bool worthKeeping = builtPageCount_ > 0 && (!partial_ || builtPageCount_ > partialPageCount_);
|
|
||||||
|
|
||||||
bool committed = false;
|
|
||||||
if (worthKeeping) {
|
|
||||||
// Capture the parse watermark and commit before tearing the parser down (the anchor
|
|
||||||
// map is read from it). The incomplete trailing page is intentionally not flushed:
|
|
||||||
// only fully laid-out pages are persisted, and the rebuild re-derives the rest.
|
|
||||||
const uint32_t consumed = static_cast<uint32_t>(build_->parser->parseBytesConsumed());
|
|
||||||
committed = commitBuildFile(SECTION_FILE_PARTIAL_VERSION, consumed, build_->totalBytes);
|
|
||||||
if (committed) {
|
|
||||||
partial_ = true;
|
|
||||||
partialPageCount_ = builtPageCount_;
|
|
||||||
partialBytesConsumed_ = consumed;
|
|
||||||
partialTotalBytes_ = build_->totalBytes;
|
|
||||||
LOG_INF("SCT", "Suspended build: %u pages persisted", builtPageCount_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (build_->parser) build_->parser->abortParse();
|
|
||||||
if (build_->cssParser) build_->cssParser->clear();
|
|
||||||
if (!committed && file) {
|
|
||||||
// Explicit close() required before remove (member variable, O_RDWR handle).
|
|
||||||
file.close();
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
}
|
|
||||||
if (!build_->reusedHtml && Storage.exists(build_->tmpHtmlPath.c_str())) {
|
|
||||||
Storage.remove(build_->tmpHtmlPath.c_str());
|
|
||||||
}
|
|
||||||
build_.reset();
|
|
||||||
buildComplete_ = false;
|
|
||||||
pageCount = partial_ ? partialPageCount_ : 0;
|
|
||||||
builtPageCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Section::abandonBuild() {
|
|
||||||
if (!build_) return;
|
|
||||||
if (build_->parser) build_->parser->abortParse();
|
|
||||||
if (build_->cssParser) build_->cssParser->clear();
|
|
||||||
if (file) {
|
|
||||||
// Explicit close() required before remove (member variable, O_RDWR handle).
|
|
||||||
file.close();
|
|
||||||
Storage.remove(binTmpPath().c_str());
|
|
||||||
}
|
|
||||||
// A parse error would recur against the same HTML, so drop any partial too -- resuming
|
|
||||||
// from it would just re-enter the failing build every open.
|
|
||||||
if (Storage.exists(filePath.c_str())) {
|
|
||||||
Storage.remove(filePath.c_str());
|
|
||||||
}
|
|
||||||
if (!build_->reusedHtml && Storage.exists(build_->tmpHtmlPath.c_str())) {
|
|
||||||
Storage.remove(build_->tmpHtmlPath.c_str());
|
|
||||||
}
|
|
||||||
build_.reset();
|
|
||||||
buildComplete_ = false;
|
|
||||||
partial_ = false;
|
|
||||||
partialPageCount_ = 0;
|
|
||||||
pageCount = 0;
|
|
||||||
builtPageCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Page> Section::loadPageDuringBuild(const int page) {
|
|
||||||
if (!build_ || page < 0 || page >= static_cast<int>(build_->lut.size()) || !file) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
const uint32_t pos = build_->lut[page].fileOffset;
|
|
||||||
if (pos == 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
// The .bin is open O_RDWR for the build. Read the already-written page, then restore
|
|
||||||
// the write cursor so the next onPageComplete keeps appending where it left off.
|
|
||||||
const uint32_t writePos = file.position();
|
|
||||||
file.seek(pos);
|
|
||||||
auto p = Page::deserialize(file);
|
|
||||||
file.seek(writePos);
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read a page from the committed file at filePath (finalized section or partial from a
|
|
||||||
// previous session). Uses a local handle so it is safe while a build holds the member
|
|
||||||
// `file` open on the tmp .bin.
|
|
||||||
std::unique_ptr<Page> Section::loadPageAt(const int page) const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 4);
|
|
||||||
uint32_t lutOffset;
|
|
||||||
serialization::readPod(f, lutOffset);
|
|
||||||
f.seek(lutOffset + sizeof(uint32_t) * page);
|
|
||||||
uint32_t pagePos;
|
|
||||||
serialization::readPod(f, pagePos);
|
|
||||||
f.seek(pagePos);
|
|
||||||
|
|
||||||
return Page::deserialize(f);
|
|
||||||
// No f.close() needed -- DESTRUCTOR_CLOSES_FILE=1 handles it at scope exit
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Page> Section::loadPage(const int page) {
|
|
||||||
if (page < 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (build_ && page < static_cast<int>(build_->lut.size())) {
|
|
||||||
return loadPageDuringBuild(page);
|
|
||||||
}
|
|
||||||
// Not (yet) in the active build: serve from the file on disk -- a finalized section,
|
|
||||||
// or a partial from a previous session whose pages the rebuild hasn't reached again.
|
|
||||||
const int onDisk = partial_ ? partialPageCount_ : (build_ ? 0 : pageCount);
|
|
||||||
if (page >= onDisk) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return loadPageAt(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Section::getTextFromSectionFile() {
|
|
||||||
std::string fullText;
|
|
||||||
auto p = loadPage(currentPage);
|
|
||||||
if (p) {
|
|
||||||
for (const auto& el : p->elements) {
|
|
||||||
if (el->getTag() == TAG_PageLine) {
|
|
||||||
const auto& line = static_cast<const PageLine&>(*el);
|
|
||||||
if (line.getBlock()) {
|
|
||||||
const auto& block = *line.getBlock();
|
|
||||||
for (uint16_t i = 0; i < block.wordCount(); i++) {
|
|
||||||
if (!fullText.empty()) fullText += " ";
|
|
||||||
fullText += block.wordText(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fullText;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::getCachedPageCount() const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t fileSize = f.size();
|
|
||||||
if (fileSize < HEADER_SIZE) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only a finalized section's count is the chapter total; a partial's count is just the
|
|
||||||
// suspended build's watermark, which would skew progress mapping. Callers fall back to
|
|
||||||
// their own estimates.
|
|
||||||
uint8_t version;
|
|
||||||
serialization::readPod(f, version);
|
|
||||||
if (version != SECTION_FILE_VERSION) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 4 - sizeof(uint16_t));
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(f, count);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::getPageForAnchor(const std::string& anchor) const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t fileSize = f.size();
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 3);
|
|
||||||
uint32_t anchorMapOffset;
|
|
||||||
serialization::readPod(f, anchorMapOffset);
|
|
||||||
if (anchorMapOffset == 0 || anchorMapOffset >= fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(anchorMapOffset);
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(f, count);
|
|
||||||
for (uint16_t i = 0; i < count; i++) {
|
|
||||||
std::string key;
|
|
||||||
uint16_t page;
|
|
||||||
serialization::readString(f, key);
|
|
||||||
serialization::readPod(f, page);
|
|
||||||
if (key == anchor) {
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::getPageForParagraphIndex(const uint16_t pIndex) const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t fileSize = f.size();
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 2);
|
|
||||||
uint32_t paragraphLutOffset;
|
|
||||||
serialization::readPod(f, paragraphLutOffset);
|
|
||||||
if (paragraphLutOffset == 0 || paragraphLutOffset >= fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(paragraphLutOffset);
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(f, count);
|
|
||||||
if (count == 0) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t lutEnd = paragraphLutOffset + sizeof(uint16_t) + count * sizeof(uint16_t);
|
|
||||||
if (lutEnd > fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t resultPage = count - 1;
|
|
||||||
for (uint16_t i = 0; i < count; i++) {
|
|
||||||
uint16_t pagePIdx;
|
|
||||||
serialization::readPod(f, pagePIdx);
|
|
||||||
if (pagePIdx >= pIndex) {
|
|
||||||
resultPage = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::getParagraphIndexForPage(const uint16_t page) const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t fileSize = f.size();
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 2);
|
|
||||||
uint32_t paragraphLutOffset;
|
|
||||||
serialization::readPod(f, paragraphLutOffset);
|
|
||||||
if (paragraphLutOffset == 0 || paragraphLutOffset >= fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(paragraphLutOffset);
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(f, count);
|
|
||||||
if (count == 0 || page >= count) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t entryEnd = paragraphLutOffset + sizeof(uint16_t) + (page + 1) * sizeof(uint16_t);
|
|
||||||
if (entryEnd > fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(paragraphLutOffset + sizeof(uint16_t) + page * sizeof(uint16_t));
|
|
||||||
uint16_t pIdx;
|
|
||||||
serialization::readPod(f, pIdx);
|
|
||||||
return pIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<uint16_t> Section::getPageForListItemIndex(const uint16_t liIndex) const {
|
|
||||||
HalFile f;
|
|
||||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t fileSize = f.size();
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t));
|
|
||||||
uint32_t liLutOffset;
|
|
||||||
serialization::readPod(f, liLutOffset);
|
|
||||||
if (liLutOffset == 0 || liLutOffset >= fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The li LUT shares count with the paragraph LUT; read count from paragraphLutOffset
|
|
||||||
f.seek(HEADER_SIZE - sizeof(uint32_t) * 2);
|
|
||||||
uint32_t paragraphLutOffset;
|
|
||||||
serialization::readPod(f, paragraphLutOffset);
|
|
||||||
if (paragraphLutOffset == 0 || paragraphLutOffset >= fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(paragraphLutOffset);
|
|
||||||
uint16_t count;
|
|
||||||
serialization::readPod(f, count);
|
|
||||||
if (count == 0) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t lutEnd = liLutOffset + count * sizeof(uint16_t);
|
|
||||||
if (lutEnd > fileSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(liLutOffset);
|
|
||||||
uint16_t resultPage = count - 1;
|
|
||||||
for (uint16_t i = 0; i < count; i++) {
|
|
||||||
uint16_t pageLiIdx;
|
|
||||||
serialization::readPod(f, pageLiIdx);
|
|
||||||
if (pageLiIdx >= liIndex) {
|
|
||||||
resultPage = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultPage;
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Epub.h"
|
|
||||||
|
|
||||||
class Page;
|
|
||||||
class GfxRenderer;
|
|
||||||
class ChapterHtmlSlimParser;
|
|
||||||
class CssParser;
|
|
||||||
|
|
||||||
class Section {
|
|
||||||
std::shared_ptr<Epub> epub;
|
|
||||||
const int spineIndex;
|
|
||||||
GfxRenderer& renderer;
|
|
||||||
std::string filePath;
|
|
||||||
HalFile file;
|
|
||||||
|
|
||||||
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
|
|
||||||
bool embeddedStyle, uint8_t imageRendering, bool focusReadingEnabled);
|
|
||||||
uint32_t onPageComplete(std::unique_ptr<Page> page);
|
|
||||||
|
|
||||||
// Page-offset table entry, kept in RAM while an incremental build is running so
|
|
||||||
// already-built pages can be located in the partially-written .bin.
|
|
||||||
struct PageLutEntry {
|
|
||||||
uint32_t fileOffset;
|
|
||||||
uint16_t paragraphIndex;
|
|
||||||
uint16_t listItemIndex;
|
|
||||||
};
|
|
||||||
// Held only while an incremental build is in progress (see startBuild). Carries the
|
|
||||||
// live parser plus the strings it references (the parser stores them by reference)
|
|
||||||
// and the in-RAM page-offset table.
|
|
||||||
struct BuildContext {
|
|
||||||
std::unique_ptr<ChapterHtmlSlimParser> parser;
|
|
||||||
std::vector<PageLutEntry> lut;
|
|
||||||
std::string parsePath;
|
|
||||||
std::string contentBase;
|
|
||||||
std::string imageBasePath;
|
|
||||||
std::string htmlPath;
|
|
||||||
std::string tmpHtmlPath;
|
|
||||||
bool reusedHtml = false;
|
|
||||||
CssParser* cssParser = nullptr;
|
|
||||||
// HTML byte progress, for estimating the section's total page count while it's still building.
|
|
||||||
uint32_t bytesConsumed = 0;
|
|
||||||
uint32_t totalBytes = 0;
|
|
||||||
// Exponentially-smoothed page-count estimate (0 = not yet seeded) and the bytesConsumed at its
|
|
||||||
// last update. The raw byte-ratio estimate jitters as the build crosses dense/sparse regions;
|
|
||||||
// the EMA is stepped once per build advance (not per redraw) to damp that wobble.
|
|
||||||
float smoothedEstimate = 0;
|
|
||||||
uint32_t smoothedAtConsumed = 0;
|
|
||||||
};
|
|
||||||
std::unique_ptr<BuildContext> build_;
|
|
||||||
bool buildComplete_ = false;
|
|
||||||
// Pages laid out by the active build (== build_->lut.size()). Distinct from pageCount,
|
|
||||||
// which is the pages *available to read* and also counts a loaded partial file's pages.
|
|
||||||
uint16_t builtPageCount_ = 0;
|
|
||||||
// A partial section file (suspended build from a previous session) is loaded at filePath.
|
|
||||||
// Its pages 0..partialPageCount_-1 are readable while a rebuild extends past them.
|
|
||||||
bool partial_ = false;
|
|
||||||
uint16_t partialPageCount_ = 0;
|
|
||||||
// Parse watermark from the partial's trailer, for estimating the total page count.
|
|
||||||
uint32_t partialBytesConsumed_ = 0;
|
|
||||||
uint32_t partialTotalBytes_ = 0;
|
|
||||||
bool finalizeBuild();
|
|
||||||
// Write the LUTs/anchor map (and, for a partial, the watermark trailer), patch the
|
|
||||||
// header, stamp the version byte, and swap the tmp .bin over filePath.
|
|
||||||
bool commitBuildFile(uint8_t version, uint32_t bytesConsumed, uint32_t totalBytes);
|
|
||||||
// Builds write here and are swapped over filePath only on commit, so a prior
|
|
||||||
// partial/finalized file stays readable while a rebuild is in progress.
|
|
||||||
std::string binTmpPath() const { return filePath + ".part"; }
|
|
||||||
|
|
||||||
std::unique_ptr<Page> loadPageAt(int page) const;
|
|
||||||
// Read a page already laid out by the in-progress build (page < build LUT size), from
|
|
||||||
// the partially-written tmp .bin without disturbing the build's write cursor.
|
|
||||||
std::unique_ptr<Page> loadPageDuringBuild(int page);
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint16_t pageCount = 0;
|
|
||||||
int currentPage = 0;
|
|
||||||
|
|
||||||
// Constructor and destructor are out-of-line: BuildContext holds a unique_ptr to the
|
|
||||||
// forward-declared ChapterHtmlSlimParser, whose full definition is only visible in the .cpp.
|
|
||||||
explicit Section(const std::shared_ptr<Epub>& epub, int spineIndex, GfxRenderer& renderer);
|
|
||||||
~Section();
|
|
||||||
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
|
||||||
uint8_t imageRendering, bool focusReadingEnabled);
|
|
||||||
bool clearCache() const;
|
|
||||||
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
|
||||||
uint8_t imageRendering, bool focusReadingEnabled,
|
|
||||||
const std::function<void()>& popupFn = nullptr);
|
|
||||||
|
|
||||||
// Incremental build: lay out the section a few pages at a time so a large chapter
|
|
||||||
// can show its first page immediately and keep the UI responsive while the rest
|
|
||||||
// builds. createSectionFile() above is the one-shot wrapper over these.
|
|
||||||
// if (!startBuild(...)) fail;
|
|
||||||
// each tick: buildSomeMore(N); render up to pageCount; when isBuildComplete() stop.
|
|
||||||
bool startBuild(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
|
||||||
uint8_t imageRendering, bool focusReadingEnabled, const std::function<void()>& popupFn = nullptr);
|
|
||||||
// Lay out up to maxPages more pages (maxPages <= 0 = build to completion). Returns
|
|
||||||
// false on error (the build is abandoned). Sets isBuildComplete() when finished.
|
|
||||||
bool buildSomeMore(int maxPages);
|
|
||||||
bool isBuilding() const { return static_cast<bool>(build_); }
|
|
||||||
bool isBuildComplete() const { return buildComplete_; }
|
|
||||||
// Best-known total page count: the exact pageCount once finalized, or a smoothed byte-based
|
|
||||||
// estimate (pages so far scaled by totalBytes/bytesConsumed, damped by an EMA) while a giant spine
|
|
||||||
// is still building, so "page X of Y" / progress don't read off the small build watermark.
|
|
||||||
uint16_t estimatedTotalPages() const;
|
|
||||||
void abandonBuild();
|
|
||||||
// Persist an in-progress build as a partial section file (version sentinel + LUTs +
|
|
||||||
// watermark trailer) instead of discarding it, so the next open of this spine can show
|
|
||||||
// its pages instantly and only rebuild in the background. Called by the destructor, so
|
|
||||||
// any teardown path (exit, sleep, navigation) keeps the work already done. Keeps a
|
|
||||||
// pre-existing partial when it covers more pages than this build reached.
|
|
||||||
void suspendBuild();
|
|
||||||
// True when a partial file was loaded: pageCount is a watermark, not the chapter total.
|
|
||||||
bool isPartial() const { return partial_; }
|
|
||||||
|
|
||||||
// Unified page read: from the active build if it has reached the page, otherwise from
|
|
||||||
// the on-disk file (finalized section, or a partial the rebuild hasn't caught up to).
|
|
||||||
std::unique_ptr<Page> loadPage(int page);
|
|
||||||
|
|
||||||
std::string getTextFromSectionFile();
|
|
||||||
|
|
||||||
// Resolve an anchor from the in-progress build first, then the on-disk anchor map
|
|
||||||
// (covers finalized sections and partials from a previous session).
|
|
||||||
std::optional<uint16_t> findAnchor(const std::string& anchor) const;
|
|
||||||
|
|
||||||
// MEMFIX-PORT: section resident-bytes audit accessor; portable
|
|
||||||
// Approximate resident heap for the audit log. Steady state (no build) a
|
|
||||||
// Section holds little beyond itself; during a build the page LUT and path
|
|
||||||
// strings dominate (the parser's internal footprint is not walked here).
|
|
||||||
size_t residentBytes() const {
|
|
||||||
size_t total = sizeof(Section) + filePath.capacity();
|
|
||||||
if (build_) {
|
|
||||||
total += sizeof(BuildContext) + build_->lut.capacity() * sizeof(PageLutEntry) +
|
|
||||||
build_->parsePath.capacity() + build_->contentBase.capacity() + build_->imageBasePath.capacity() +
|
|
||||||
build_->htmlPath.capacity() + build_->tmpHtmlPath.capacity();
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// True if this spine's unzipped HTML is already cached, so a build won't pay the (multi-second on a
|
|
||||||
// giant spine) zip inflation. Lets the reader skip the indexing popup on a fast reopen/rebuild.
|
|
||||||
bool hasHtmlCache() const;
|
|
||||||
|
|
||||||
// Look up the page number for an anchor id from the section cache file.
|
|
||||||
std::optional<uint16_t> getPageForAnchor(const std::string& anchor) const;
|
|
||||||
|
|
||||||
// Look up an anchor among the pages built so far by the in-progress build, so an anchor jump
|
|
||||||
// (TOC / chapter select, usually the chapter top = page 0) can resolve without laying out the
|
|
||||||
// whole chapter. Returns nullopt if the anchor hasn't been reached yet (build more) or no build.
|
|
||||||
std::optional<uint16_t> findAnchorDuringBuild(const std::string& anchor) const;
|
|
||||||
|
|
||||||
// Get the page count from the section cache file without fully loading it.
|
|
||||||
std::optional<uint16_t> getCachedPageCount() const;
|
|
||||||
|
|
||||||
// Look up the page number for a synthetic paragraph index from XPath p[N].
|
|
||||||
std::optional<uint16_t> getPageForParagraphIndex(uint16_t pIndex) const;
|
|
||||||
|
|
||||||
// Look up the page number for a running list-item index from the li LUT.
|
|
||||||
std::optional<uint16_t> getPageForListItemIndex(uint16_t liIndex) const;
|
|
||||||
|
|
||||||
// Look up the synthetic paragraph index for the given rendered page.
|
|
||||||
std::optional<uint16_t> getParagraphIndexForPage(uint16_t page) const;
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class GfxRenderer;
|
|
||||||
|
|
||||||
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
|
||||||
|
|
||||||
// a block of content in the html - either a paragraph or an image
|
|
||||||
class Block {
|
|
||||||
public:
|
|
||||||
virtual ~Block() = default;
|
|
||||||
|
|
||||||
virtual BlockType getType() = 0;
|
|
||||||
virtual bool isEmpty() = 0;
|
|
||||||
virtual void finish() {}
|
|
||||||
};
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/css/CssStyle.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BlockStyle - Block-level styling properties
|
|
||||||
*/
|
|
||||||
struct BlockStyle {
|
|
||||||
// Upper bound (in em) for any single side's horizontal margin or padding.
|
|
||||||
// Some EPUBs apply huge em-based insets to chapter-opener classes; without a
|
|
||||||
// cap, effectiveWidth collapses to 1-2 words per line and justification dumps
|
|
||||||
// the remaining space into a single gap.
|
|
||||||
static constexpr float MAX_HORIZONTAL_INSET_EM = 2.0f;
|
|
||||||
|
|
||||||
CssTextAlign alignment = CssTextAlign::Justify;
|
|
||||||
|
|
||||||
// Spacing (in pixels)
|
|
||||||
int16_t marginTop = 0;
|
|
||||||
int16_t marginBottom = 0;
|
|
||||||
int16_t marginLeft = 0;
|
|
||||||
int16_t marginRight = 0;
|
|
||||||
int16_t paddingTop = 0; // treated same as margin for rendering
|
|
||||||
int16_t paddingBottom = 0; // treated same as margin for rendering
|
|
||||||
int16_t paddingLeft = 0; // treated same as margin for rendering
|
|
||||||
int16_t paddingRight = 0; // treated same as margin for rendering
|
|
||||||
int16_t textIndent = 0;
|
|
||||||
bool textIndentDefined = false; // true if text-indent was explicitly set in CSS
|
|
||||||
bool textAlignDefined = false; // true if text-align was explicitly set in CSS
|
|
||||||
bool isRtl = false; // true if resolved direction is RTL
|
|
||||||
bool directionDefined = false; // true if direction was explicitly set in CSS/HTML
|
|
||||||
|
|
||||||
// Set when this block was created by a <br> element. Used by startNewTextBlock to inject
|
|
||||||
// a full line-height gap when the <br> block stays empty (section-break use case).
|
|
||||||
// NOT propagated through getCombinedBlockStyle so it can't leak into sibling blocks.
|
|
||||||
bool fromBrElement = false;
|
|
||||||
|
|
||||||
// Combined insets (margin + padding)
|
|
||||||
[[nodiscard]] int16_t leftInset() const { return marginLeft + paddingLeft; }
|
|
||||||
[[nodiscard]] int16_t rightInset() const { return marginRight + paddingRight; }
|
|
||||||
[[nodiscard]] int16_t totalHorizontalInset() const { return leftInset() + rightInset(); }
|
|
||||||
[[nodiscard]] int16_t topInset() const { return marginTop + paddingTop; }
|
|
||||||
[[nodiscard]] int16_t bottomInset() const { return marginBottom + paddingBottom; }
|
|
||||||
|
|
||||||
// Return a copy with bottom margins/padding zeroed out.
|
|
||||||
[[nodiscard]] BlockStyle withoutBottom() const {
|
|
||||||
BlockStyle result = *this;
|
|
||||||
result.marginBottom = 0;
|
|
||||||
result.paddingBottom = 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a copy with bottom margins/padding collapsed (max) with the source's.
|
|
||||||
// Uses CSS margin collapsing: adjacent parent-child margins resolve to the larger value.
|
|
||||||
[[nodiscard]] BlockStyle addBottom(const BlockStyle& source) const {
|
|
||||||
BlockStyle result = *this;
|
|
||||||
result.marginBottom = std::max(marginBottom, source.marginBottom);
|
|
||||||
result.paddingBottom = static_cast<int16_t>(paddingBottom + source.paddingBottom);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class CombineAxis : uint8_t {
|
|
||||||
Horizontal = 1, // margins left/right, padding left/right, text-align, text-indent
|
|
||||||
Vertical = 2, // margins top/bottom, padding top/bottom
|
|
||||||
};
|
|
||||||
|
|
||||||
// Combine this style's properties with a child style along the specified axis.
|
|
||||||
// Properties on the other axis are kept from the child unchanged.
|
|
||||||
[[nodiscard]] BlockStyle getCombinedBlockStyle(const BlockStyle& child, CombineAxis axis) const {
|
|
||||||
BlockStyle result = child;
|
|
||||||
|
|
||||||
if (axis == CombineAxis::Horizontal) {
|
|
||||||
result.marginLeft = static_cast<int16_t>(child.marginLeft + marginLeft);
|
|
||||||
result.marginRight = static_cast<int16_t>(child.marginRight + marginRight);
|
|
||||||
result.paddingLeft = static_cast<int16_t>(child.paddingLeft + paddingLeft);
|
|
||||||
result.paddingRight = static_cast<int16_t>(child.paddingRight + paddingRight);
|
|
||||||
if (!child.textIndentDefined && textIndentDefined) {
|
|
||||||
result.textIndent = textIndent;
|
|
||||||
result.textIndentDefined = true;
|
|
||||||
}
|
|
||||||
if (!child.textAlignDefined && textAlignDefined) {
|
|
||||||
result.alignment = alignment;
|
|
||||||
result.textAlignDefined = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.marginTop = std::max(child.marginTop, marginTop);
|
|
||||||
result.marginBottom = std::max(child.marginBottom, marginBottom);
|
|
||||||
result.paddingTop = static_cast<int16_t>(child.paddingTop + paddingTop);
|
|
||||||
result.paddingBottom = static_cast<int16_t>(child.paddingBottom + paddingBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direction is not axis-specific. Inherit from parent when child doesn't define it.
|
|
||||||
if (!child.directionDefined && directionDefined) {
|
|
||||||
result.isRtl = isRtl;
|
|
||||||
result.directionDefined = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fromBrElement is consumed by startNewTextBlock when an empty <br> block
|
|
||||||
// is merged with the following paragraph; never propagate it further.
|
|
||||||
result.fromBrElement = false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a BlockStyle from CSS style properties, resolving CssLength values to pixels
|
|
||||||
// emSize is the current font line height, used for em/rem unit conversion
|
|
||||||
// paragraphAlignment is the user's paragraphAlignment setting preference
|
|
||||||
static BlockStyle fromCssStyle(const CssStyle& cssStyle, const float emSize, const CssTextAlign paragraphAlignment,
|
|
||||||
const uint16_t viewportWidth = 0) {
|
|
||||||
BlockStyle blockStyle;
|
|
||||||
const float vw = viewportWidth;
|
|
||||||
const auto maxHorizontalInsetPx = static_cast<int16_t>(emSize * MAX_HORIZONTAL_INSET_EM);
|
|
||||||
// Resolve all CssLength values to pixels using the current font's em size and viewport width
|
|
||||||
blockStyle.marginTop = cssStyle.marginTop.toPixelsInt16(emSize, vw);
|
|
||||||
blockStyle.marginBottom = cssStyle.marginBottom.toPixelsInt16(emSize, vw);
|
|
||||||
blockStyle.marginLeft = std::min(cssStyle.marginLeft.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
|
||||||
blockStyle.marginRight = std::min(cssStyle.marginRight.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
|
||||||
|
|
||||||
blockStyle.paddingTop = cssStyle.paddingTop.toPixelsInt16(emSize, vw);
|
|
||||||
blockStyle.paddingBottom = cssStyle.paddingBottom.toPixelsInt16(emSize, vw);
|
|
||||||
blockStyle.paddingLeft = std::min(cssStyle.paddingLeft.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
|
||||||
blockStyle.paddingRight = std::min(cssStyle.paddingRight.toPixelsInt16(emSize, vw), maxHorizontalInsetPx);
|
|
||||||
|
|
||||||
// For textIndent: if it's a percentage we can't resolve (no viewport width),
|
|
||||||
// leave textIndentDefined=false so the space-width fallback in resolveFirstLineIndent() is used
|
|
||||||
if (cssStyle.hasTextIndent() && cssStyle.textIndent.isResolvable(vw)) {
|
|
||||||
blockStyle.textIndent = cssStyle.textIndent.toPixelsInt16(emSize, vw);
|
|
||||||
blockStyle.textIndentDefined = true;
|
|
||||||
}
|
|
||||||
blockStyle.textAlignDefined = cssStyle.hasTextAlign();
|
|
||||||
// User setting overrides CSS, unless "Book's Style" alignment setting is selected
|
|
||||||
if (paragraphAlignment == CssTextAlign::None) {
|
|
||||||
blockStyle.alignment = blockStyle.textAlignDefined ? cssStyle.textAlign : CssTextAlign::Justify;
|
|
||||||
} else {
|
|
||||||
blockStyle.alignment = paragraphAlignment;
|
|
||||||
}
|
|
||||||
// RTL direction from CSS/HTML
|
|
||||||
if (cssStyle.hasDirection()) {
|
|
||||||
blockStyle.isRtl = (cssStyle.direction == CssTextDirection::Rtl);
|
|
||||||
blockStyle.directionDefined = true;
|
|
||||||
}
|
|
||||||
return blockStyle;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
#include "ImageBlock.h"
|
|
||||||
|
|
||||||
#include <FontCacheManager.h>
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "Epub/converters/DirectPixelWriter.h"
|
|
||||||
#include "Epub/converters/ImageDecoderFactory.h"
|
|
||||||
|
|
||||||
// Cache file format:
|
|
||||||
// - uint16_t width
|
|
||||||
// - uint16_t height
|
|
||||||
// - uint8_t pixels[...] - 2 bits per pixel, packed (4 pixels per byte), row-major order
|
|
||||||
|
|
||||||
ImageBlock::ImageBlock(const std::string& imagePath, int16_t width, int16_t height)
|
|
||||||
: imagePath(imagePath), width(width), height(height) {}
|
|
||||||
|
|
||||||
bool ImageBlock::imageExists() const { return Storage.exists(imagePath.c_str()); }
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::string getCachePath(const std::string& imagePath) {
|
|
||||||
// Replace extension with .pxc (pixel cache)
|
|
||||||
size_t dotPos = imagePath.rfind('.');
|
|
||||||
if (dotPos != std::string::npos) {
|
|
||||||
return imagePath.substr(0, dotPos) + ".pxc";
|
|
||||||
}
|
|
||||||
return imagePath + ".pxc";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readValidCacheHeader(HalFile& cacheFile, const int expectedWidth, const int expectedHeight, uint16_t& cachedWidth,
|
|
||||||
uint16_t& cachedHeight) {
|
|
||||||
if (cacheFile.read(&cachedWidth, 2) != 2 || cacheFile.read(&cachedHeight, 2) != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int widthDiff = abs(cachedWidth - expectedWidth);
|
|
||||||
const int heightDiff = abs(cachedHeight - expectedHeight);
|
|
||||||
if (widthDiff > 1 || heightDiff > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t bytesPerRow = (cachedWidth + 3) / 4;
|
|
||||||
const size_t expectedSize = 4 + bytesPerRow * cachedHeight;
|
|
||||||
return cacheFile.size() >= expectedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pages are deserialized afresh on each visit. Keep a bounded, allocation-free
|
|
||||||
// record so an image that failed renders its placeholder directly for the rest
|
|
||||||
// of the reader session instead of paying another placeholder refresh and
|
|
||||||
// decode. The reader clears this on entry so transient memory/storage failures
|
|
||||||
// are retried.
|
|
||||||
constexpr size_t MAX_SESSION_IMAGE_FAILURES = 16;
|
|
||||||
uint64_t failedImageHashes[MAX_SESSION_IMAGE_FAILURES];
|
|
||||||
size_t failedImageCount = 0;
|
|
||||||
|
|
||||||
uint64_t imagePathHash(const std::string& path) {
|
|
||||||
uint64_t hash = 14695981039346656037ull;
|
|
||||||
for (const char c : path) {
|
|
||||||
hash ^= static_cast<uint8_t>(c);
|
|
||||||
hash *= 1099511628211ull;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool imageFailedThisSession(const std::string& path) {
|
|
||||||
const uint64_t hash = imagePathHash(path);
|
|
||||||
for (size_t i = 0; i < failedImageCount; i++) {
|
|
||||||
if (failedImageHashes[i] == hash) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rememberImageFailure(const std::string& path) {
|
|
||||||
if (failedImageCount == MAX_SESSION_IMAGE_FAILURES || imageFailedThisSession(path)) return;
|
|
||||||
failedImageHashes[failedImageCount++] = imagePathHash(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
|
||||||
int expectedHeight) {
|
|
||||||
HalFile cacheFile;
|
|
||||||
if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t cachedWidth, cachedHeight;
|
|
||||||
if (!readValidCacheHeader(cacheFile, expectedWidth, expectedHeight, cachedWidth, cachedHeight)) {
|
|
||||||
LOG_ERR("IMG", "Invalid image cache: %s", cachePath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use cached dimensions for rendering (they're the actual decoded size)
|
|
||||||
expectedWidth = cachedWidth;
|
|
||||||
expectedHeight = cachedHeight;
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Loading from cache: %s (%dx%d)", cachePath.c_str(), cachedWidth, cachedHeight);
|
|
||||||
|
|
||||||
// Read several rows per SD access. A full-page image is re-rendered on every
|
|
||||||
// grayscale strip pass (~14x per page), and a one-row-per-read loop here means
|
|
||||||
// cachedHeight (~728) tiny reads through the storage mutex + SdFat each time —
|
|
||||||
// the dominant cost of displaying an image page. Batching rows into a ~4KB
|
|
||||||
// buffer cuts that to ~20 reads per pass without holding the whole image.
|
|
||||||
const int bytesPerRow = (cachedWidth + 3) / 4; // 2 bits per pixel, 4 pixels per byte
|
|
||||||
int rowsPerRead = 4096 / bytesPerRow;
|
|
||||||
if (rowsPerRead < 1) rowsPerRead = 1;
|
|
||||||
if (rowsPerRead > cachedHeight) rowsPerRead = cachedHeight;
|
|
||||||
uint8_t* readBuffer = (uint8_t*)malloc((size_t)rowsPerRead * bytesPerRow);
|
|
||||||
if (!readBuffer) {
|
|
||||||
// Fall back to a single-row buffer under memory pressure.
|
|
||||||
rowsPerRead = 1;
|
|
||||||
readBuffer = (uint8_t*)malloc(bytesPerRow);
|
|
||||||
}
|
|
||||||
if (!readBuffer) {
|
|
||||||
LOG_ERR("IMG", "Failed to allocate row buffer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectPixelWriter pw;
|
|
||||||
pw.init(renderer);
|
|
||||||
|
|
||||||
int rowsInBuffer = 0;
|
|
||||||
int bufferRow = 0;
|
|
||||||
for (int row = 0; row < cachedHeight; row++) {
|
|
||||||
if (bufferRow >= rowsInBuffer) {
|
|
||||||
const int toRead = (cachedHeight - row < rowsPerRead) ? (cachedHeight - row) : rowsPerRead;
|
|
||||||
const size_t bytes = (size_t)toRead * bytesPerRow;
|
|
||||||
if (cacheFile.read(readBuffer, bytes) != static_cast<int>(bytes)) {
|
|
||||||
LOG_ERR("IMG", "Cache read error at row %d", row);
|
|
||||||
free(readBuffer);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rowsInBuffer = toRead;
|
|
||||||
bufferRow = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* rowBuffer = readBuffer + (size_t)bufferRow * bytesPerRow;
|
|
||||||
bufferRow++;
|
|
||||||
|
|
||||||
const int destY = y + row;
|
|
||||||
pw.beginRow(destY);
|
|
||||||
// On a grayscale strip pass only a narrow column window of the image is in
|
|
||||||
// the active band; skip the rest instead of unpacking+clipping every pixel.
|
|
||||||
int colStart, colEnd;
|
|
||||||
pw.bandColRange(x, cachedWidth, colStart, colEnd);
|
|
||||||
for (int col = colStart; col < colEnd; col++) {
|
|
||||||
const int byteIdx = col >> 2; // col / 4
|
|
||||||
const int bitShift = 6 - (col & 3) * 2; // MSB first within byte
|
|
||||||
uint8_t pixelValue = (rowBuffer[byteIdx] >> bitShift) & 0x03;
|
|
||||||
|
|
||||||
pw.writePixel(x + col, pixelValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(readBuffer);
|
|
||||||
LOG_DBG("IMG", "Cache render complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool ImageBlock::hasValidCache() const {
|
|
||||||
const auto cachePath = getCachePath(imagePath);
|
|
||||||
HalFile cacheFile;
|
|
||||||
if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t cachedWidth, cachedHeight;
|
|
||||||
return readValidCacheHeader(cacheFile, width, height, cachedWidth, cachedHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImageBlock::needsDecode() const { return !imageFailedThisSession(imagePath) && !hasValidCache(); }
|
|
||||||
|
|
||||||
void ImageBlock::clearSessionRenderFailures() { failedImageCount = 0; }
|
|
||||||
|
|
||||||
void ImageBlock::renderPlaceholder(GfxRenderer& renderer, const int x, const int y) const {
|
|
||||||
renderer.fillRect(x, y, width, height, true);
|
|
||||||
if (width > 2 && height > 2) {
|
|
||||||
renderer.fillRect(x + 1, y + 1, width - 2, height - 2, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|
||||||
// The font-prewarm scan pass only accumulates glyphs; an image contributes
|
|
||||||
// none, and its DirectPixelWriter output bypasses the renderer's scan-mode
|
|
||||||
// suppression, so it would otherwise do a full (discarded) cache render every
|
|
||||||
// page view. Skip it here. The image still draws in the real BW/grayscale
|
|
||||||
// passes; on first view this just moves the one-time decode to the BW pass.
|
|
||||||
FontCacheManager* fcm = renderer.getFontCacheManager();
|
|
||||||
if (fcm && fcm->isScanning()) return;
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Rendering image at %d,%d: %s (%dx%d)", x, y, imagePath.c_str(), width, height);
|
|
||||||
|
|
||||||
const int screenWidth = renderer.getScreenWidth();
|
|
||||||
const int screenHeight = renderer.getScreenHeight();
|
|
||||||
|
|
||||||
// Bounds check render position using logical screen dimensions
|
|
||||||
if (x < 0 || y < 0 || x + width > screenWidth || y + height > screenHeight) {
|
|
||||||
LOG_ERR("IMG", "Invalid render position: (%d,%d) size (%dx%d) screen (%dx%d)", x, y, width, height, screenWidth,
|
|
||||||
screenHeight);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tiled grayscale (#2190): skip the whole image when it doesn't touch the
|
|
||||||
// active band. The per-pixel writer already clips off-band pixels, but without
|
|
||||||
// this each of the ~7 bands per plane re-ran the full cache load / pixel walk
|
|
||||||
// and discarded the result — the dominant cost of AA on image pages. The check
|
|
||||||
// is orientation-aware and returns true when no strip is active, so the BW
|
|
||||||
// pass and non-tiled controllers render the image exactly as before.
|
|
||||||
if (!renderer.glyphIntersectsStrip(x, y, x + width - 1, y + height - 1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageFailedThisSession(imagePath)) {
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to render from cache first
|
|
||||||
std::string cachePath = getCachePath(imagePath);
|
|
||||||
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
|
|
||||||
return; // Successfully rendered from cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// No cache - need to decode the image
|
|
||||||
// Check if image file exists
|
|
||||||
HalFile file;
|
|
||||||
if (!Storage.openFileForRead("IMG", imagePath, file)) {
|
|
||||||
LOG_ERR("IMG", "Image file not found: %s", imagePath.c_str());
|
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
size_t fileSize = file.size();
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (fileSize == 0) {
|
|
||||||
LOG_ERR("IMG", "Image file is empty: %s", imagePath.c_str());
|
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Decoding and caching: %s", imagePath.c_str());
|
|
||||||
|
|
||||||
RenderConfig config;
|
|
||||||
config.x = x;
|
|
||||||
config.y = y;
|
|
||||||
config.maxWidth = width;
|
|
||||||
config.maxHeight = height;
|
|
||||||
config.useGrayscale = true;
|
|
||||||
config.useDithering = true;
|
|
||||||
config.performanceMode = false;
|
|
||||||
config.useExactDimensions = true; // Use pre-calculated dimensions to avoid rounding mismatches
|
|
||||||
config.cachePath = cachePath; // Enable caching during decode
|
|
||||||
|
|
||||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(imagePath);
|
|
||||||
if (!decoder) {
|
|
||||||
LOG_ERR("IMG", "No decoder found for image: %s", imagePath.c_str());
|
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Using %s decoder", decoder->getFormatName());
|
|
||||||
|
|
||||||
bool success = decoder->decodeToFramebuffer(imagePath, renderer, config);
|
|
||||||
if (!success) {
|
|
||||||
LOG_ERR("IMG", "Failed to decode image: %s", imagePath.c_str());
|
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Decode successful");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImageBlock::serialize(HalFile& file) {
|
|
||||||
serialization::writeString(file, imagePath);
|
|
||||||
serialization::writePod(file, width);
|
|
||||||
serialization::writePod(file, height);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ImageBlock> ImageBlock::deserialize(HalFile& file) {
|
|
||||||
std::string path;
|
|
||||||
serialization::readString(file, path);
|
|
||||||
int16_t w, h;
|
|
||||||
serialization::readPod(file, w);
|
|
||||||
serialization::readPod(file, h);
|
|
||||||
return std::unique_ptr<ImageBlock>(new ImageBlock(path, w, h));
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "Block.h"
|
|
||||||
|
|
||||||
class ImageBlock final : public Block {
|
|
||||||
public:
|
|
||||||
ImageBlock(const std::string& imagePath, int16_t width, int16_t height);
|
|
||||||
~ImageBlock() override = default;
|
|
||||||
|
|
||||||
const std::string& getImagePath() const { return imagePath; }
|
|
||||||
int16_t getWidth() const { return width; }
|
|
||||||
int16_t getHeight() const { return height; }
|
|
||||||
|
|
||||||
bool imageExists() const;
|
|
||||||
bool hasValidCache() const;
|
|
||||||
bool needsDecode() const;
|
|
||||||
void renderPlaceholder(GfxRenderer& renderer, int x, int y) const;
|
|
||||||
static void clearSessionRenderFailures();
|
|
||||||
|
|
||||||
BlockType getType() override { return IMAGE_BLOCK; }
|
|
||||||
bool isEmpty() override { return false; }
|
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, const int x, const int y);
|
|
||||||
bool serialize(HalFile& file);
|
|
||||||
static std::unique_ptr<ImageBlock> deserialize(HalFile& file);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string imagePath;
|
|
||||||
int16_t width;
|
|
||||||
int16_t height;
|
|
||||||
};
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
#include "TextBlock.h"
|
|
||||||
|
|
||||||
#include <BidiUtils.h>
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Memory.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
size_t TextBlock::arenaSize(const uint16_t wordCount, const bool hasFocus, const uint16_t textBytes) {
|
|
||||||
// Layout documented in TextBlock.h: 16-bit arrays first, then 8-bit arrays, then text.
|
|
||||||
size_t size = static_cast<size_t>(wordCount) * (sizeof(uint16_t) + sizeof(int16_t) + sizeof(uint8_t));
|
|
||||||
if (hasFocus) {
|
|
||||||
size += static_cast<size_t>(wordCount) * (sizeof(uint16_t) + sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
return size + textBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextBlock::bindArenaPointers() {
|
|
||||||
uint8_t* base = arena.get();
|
|
||||||
const size_t wc = numWords;
|
|
||||||
textOffArr = reinterpret_cast<const uint16_t*>(base);
|
|
||||||
xposArr = reinterpret_cast<const int16_t*>(base + wc * 2);
|
|
||||||
size_t off = wc * 4;
|
|
||||||
if (focusPresent) {
|
|
||||||
focusSuffixXArr = reinterpret_cast<const uint16_t*>(base + off);
|
|
||||||
off += wc * 2;
|
|
||||||
}
|
|
||||||
stylesArr = base + off;
|
|
||||||
off += wc;
|
|
||||||
if (focusPresent) {
|
|
||||||
focusBoundaryArr = base + off;
|
|
||||||
off += wc;
|
|
||||||
}
|
|
||||||
textArr = reinterpret_cast<const char*>(base + off);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextBlock::TextBlock(const std::vector<std::string>& words, const std::vector<int16_t>& wordXpos,
|
|
||||||
const std::vector<EpdFontFamily::Style>& wordStyles, const std::vector<uint8_t>& focusBoundary,
|
|
||||||
const std::vector<uint16_t>& focusSuffixX, const BlockStyle& blockStyle)
|
|
||||||
: blockStyle(blockStyle) {
|
|
||||||
// Focus annotations are optional: empty vectors mean no word in this block has a split.
|
|
||||||
// When present, they must be sized in lockstep with words[].
|
|
||||||
const bool hasFocus = !focusBoundary.empty();
|
|
||||||
if (words.size() != wordXpos.size() || words.size() != wordStyles.size() || words.size() > 10000 ||
|
|
||||||
(hasFocus && (words.size() != focusBoundary.size() || words.size() != focusSuffixX.size()))) {
|
|
||||||
LOG_ERR("TXB", "Construction failed: size mismatch (words=%u, xpos=%u, styles=%u, boundary=%u, suffixX=%u)",
|
|
||||||
static_cast<uint32_t>(words.size()), static_cast<uint32_t>(wordXpos.size()),
|
|
||||||
static_cast<uint32_t>(wordStyles.size()), static_cast<uint32_t>(focusBoundary.size()),
|
|
||||||
static_cast<uint32_t>(focusSuffixX.size()));
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
numWords = static_cast<uint16_t>(words.size());
|
|
||||||
focusPresent = hasFocus;
|
|
||||||
if (numWords == 0) {
|
|
||||||
return; // valid empty block, no arena
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 1: total text size, one NUL per word. A line is at most a physical
|
|
||||||
// row of the page, so uint16_t offsets are ample; reject anything larger.
|
|
||||||
size_t totalText = 0;
|
|
||||||
for (const auto& w : words) totalText += w.size() + 1;
|
|
||||||
if (totalText > UINT16_MAX) {
|
|
||||||
LOG_ERR("TXB", "Construction failed: text size %u exceeds arena limit", static_cast<uint32_t>(totalText));
|
|
||||||
numWords = 0;
|
|
||||||
focusPresent = false;
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
textBytes = static_cast<uint16_t>(totalText);
|
|
||||||
|
|
||||||
const size_t size = arenaSize(numWords, focusPresent, textBytes);
|
|
||||||
arena = makeUniqueNoThrow<uint8_t[]>(size);
|
|
||||||
if (!arena) {
|
|
||||||
LOG_ERR("TXB", "OOM: arena %u bytes", static_cast<uint32_t>(size));
|
|
||||||
numWords = 0;
|
|
||||||
textBytes = 0;
|
|
||||||
focusPresent = false;
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bindArenaPointers();
|
|
||||||
|
|
||||||
// Pass 2: fill. Mutable aliases of the const views bound above.
|
|
||||||
auto* textOff = const_cast<uint16_t*>(textOffArr);
|
|
||||||
auto* xpos = const_cast<int16_t*>(xposArr);
|
|
||||||
auto* styles = const_cast<uint8_t*>(stylesArr);
|
|
||||||
auto* text = const_cast<char*>(textArr);
|
|
||||||
uint16_t off = 0;
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
|
||||||
textOff[i] = off;
|
|
||||||
xpos[i] = wordXpos[i];
|
|
||||||
styles[i] = static_cast<uint8_t>(wordStyles[i]);
|
|
||||||
memcpy(text + off, words[i].data(), words[i].size());
|
|
||||||
off += static_cast<uint16_t>(words[i].size());
|
|
||||||
text[off++] = '\0';
|
|
||||||
}
|
|
||||||
if (focusPresent) {
|
|
||||||
auto* suffixX = const_cast<uint16_t*>(focusSuffixXArr);
|
|
||||||
auto* boundary = const_cast<uint8_t*>(focusBoundaryArr);
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
|
||||||
suffixX[i] = focusSuffixX[i];
|
|
||||||
boundary[i] = focusBoundary[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
|
||||||
if (!isValid) {
|
|
||||||
LOG_ERR("TXB", "Render skipped: invalid block");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool scanning = renderer.isFontCacheScanning();
|
|
||||||
const int ascender = renderer.getFontAscenderSize(fontId);
|
|
||||||
|
|
||||||
struct DecorationLineTracker {
|
|
||||||
EpdFontFamily::Style style;
|
|
||||||
int yOffset;
|
|
||||||
int startX = -1;
|
|
||||||
int endX = -1;
|
|
||||||
int yPos = 0;
|
|
||||||
|
|
||||||
bool active() const { return startX != -1; }
|
|
||||||
void reset() {
|
|
||||||
startX = -1;
|
|
||||||
endX = -1;
|
|
||||||
yPos = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DecorationLineTracker decorationLines[] = {
|
|
||||||
{EpdFontFamily::UNDERLINE, ascender + 2},
|
|
||||||
{EpdFontFamily::STRIKETHROUGH, ascender * 4 / 5},
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto flushDecoration = [&](DecorationLineTracker& line) {
|
|
||||||
if (line.active()) {
|
|
||||||
renderer.drawLine(line.startX, line.yPos, line.endX, line.yPos, 2, true);
|
|
||||||
line.reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const auto flushDecorations = [&]() {
|
|
||||||
for (auto& line : decorationLines) {
|
|
||||||
flushDecoration(line);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
|
||||||
const char* word = wordText(i);
|
|
||||||
const int wordX = xposArr[i] + x;
|
|
||||||
const EpdFontFamily::Style currentStyle = wordStyle(i);
|
|
||||||
const auto baseDir =
|
|
||||||
static_cast<BidiUtils::BidiBaseDir>(BidiUtils::detectParagraphLevel(word, blockStyle.isRtl ? 1 : 0));
|
|
||||||
const uint8_t boundary = focusBoundary(i);
|
|
||||||
|
|
||||||
// SUP/SUB shift the baseline passed to drawText; the glyph is also scaled 50% inside
|
|
||||||
// drawText, so these offsets are chosen relative to the full-size ascender:
|
|
||||||
// SUP: raise by 40% of ascender — sits clearly above the cap-height
|
|
||||||
// SUB: lower by 25% of ascender — descends below baseline without clashing with ascenders below
|
|
||||||
int wordY = y;
|
|
||||||
if ((currentStyle & EpdFontFamily::SUP) != 0) {
|
|
||||||
wordY -= ascender * 2 / 5;
|
|
||||||
} else if ((currentStyle & EpdFontFamily::SUB) != 0) {
|
|
||||||
wordY += ascender / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boundary > 0) {
|
|
||||||
// Focus split: draw bold prefix, then the regular suffix at a pre-computed x offset.
|
|
||||||
// The bold prefix is bounded to 9 codepoints by the clamp on targetBoldChars in
|
|
||||||
// ParsedText::addWord; 9 UTF-8 codepoints occupy at most 9 * 4 = 36 bytes, +1 for null = 37.
|
|
||||||
// suffixX is computed at cache-creation time to avoid font metric lookups at render time.
|
|
||||||
static constexpr size_t MAX_FOCUS_PREFIX_BYTES = 9 * 4 + 1;
|
|
||||||
char boldBuf[40];
|
|
||||||
static_assert(sizeof(boldBuf) >= MAX_FOCUS_PREFIX_BYTES,
|
|
||||||
"boldBuf too small for max focus prefix (9 codepoints * 4 UTF-8 bytes + null)");
|
|
||||||
const auto boldStyle = static_cast<EpdFontFamily::Style>(currentStyle | EpdFontFamily::BOLD);
|
|
||||||
const size_t boldLen =
|
|
||||||
std::min<size_t>({static_cast<size_t>(boundary), static_cast<size_t>(wordTextLen(i)), sizeof(boldBuf) - 1});
|
|
||||||
memcpy(boldBuf, word, boldLen);
|
|
||||||
boldBuf[boldLen] = '\0';
|
|
||||||
renderer.drawText(fontId, wordX, wordY, boldBuf, true, boldStyle, baseDir);
|
|
||||||
const int suffixX = wordX + focusSuffixXArr[i];
|
|
||||||
renderer.drawText(fontId, suffixX, wordY, word + boldLen, true, currentStyle, baseDir);
|
|
||||||
} else {
|
|
||||||
renderer.drawText(fontId, wordX, wordY, word, true, currentStyle, baseDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanning) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EpdFontFamily::hasTextDecoration(currentStyle)) {
|
|
||||||
int lineStartX = wordX;
|
|
||||||
int lineWidth = renderer.getTextWidth(fontId, word, currentStyle, baseDir);
|
|
||||||
|
|
||||||
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
|
||||||
lineWidth = (lineWidth + 1) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not decorate the synthetic em-space used for paragraph indentation.
|
|
||||||
if (wordTextLen(i) >= 3 && static_cast<uint8_t>(word[0]) == 0xE2 && static_cast<uint8_t>(word[1]) == 0x80 &&
|
|
||||||
static_cast<uint8_t>(word[2]) == 0x83) {
|
|
||||||
const char* visibleText = word + 3;
|
|
||||||
lineStartX += renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle);
|
|
||||||
lineWidth = renderer.getTextWidth(fontId, visibleText, currentStyle, baseDir);
|
|
||||||
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
|
||||||
lineWidth = (lineWidth + 1) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& line : decorationLines) {
|
|
||||||
if ((currentStyle & line.style) == 0) {
|
|
||||||
flushDecoration(line);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int lineY = wordY + line.yOffset;
|
|
||||||
if (line.active() && line.yPos != lineY) {
|
|
||||||
flushDecoration(line);
|
|
||||||
}
|
|
||||||
if (!line.active()) {
|
|
||||||
line.startX = lineStartX;
|
|
||||||
line.yPos = lineY;
|
|
||||||
}
|
|
||||||
line.endX = lineStartX + lineWidth;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
flushDecorations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flushDecorations();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextBlock::serialize(HalFile& file) const {
|
|
||||||
if (!isValid) {
|
|
||||||
LOG_ERR("TXB", "Serialization failed: invalid block");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Word data: scalars, then the arena verbatim -- its in-memory layout is
|
|
||||||
// exactly the on-disk layout (see TextBlock.h), so one write covers all
|
|
||||||
// per-word arrays and the text blob.
|
|
||||||
serialization::writePod(file, numWords);
|
|
||||||
serialization::writePod(file, static_cast<uint8_t>(focusPresent ? 1 : 0));
|
|
||||||
serialization::writePod(file, textBytes);
|
|
||||||
if (numWords > 0) {
|
|
||||||
const size_t size = arenaSize(numWords, focusPresent, textBytes);
|
|
||||||
if (file.write(arena.get(), size) != size) {
|
|
||||||
LOG_ERR("TXB", "Serialization failed: arena write (%u bytes)", static_cast<uint32_t>(size));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style (alignment + margins/padding/indent)
|
|
||||||
serialization::writePod(file, blockStyle.alignment);
|
|
||||||
serialization::writePod(file, blockStyle.textAlignDefined);
|
|
||||||
serialization::writePod(file, blockStyle.marginTop);
|
|
||||||
serialization::writePod(file, blockStyle.marginBottom);
|
|
||||||
serialization::writePod(file, blockStyle.marginLeft);
|
|
||||||
serialization::writePod(file, blockStyle.marginRight);
|
|
||||||
serialization::writePod(file, blockStyle.paddingTop);
|
|
||||||
serialization::writePod(file, blockStyle.paddingBottom);
|
|
||||||
serialization::writePod(file, blockStyle.paddingLeft);
|
|
||||||
serialization::writePod(file, blockStyle.paddingRight);
|
|
||||||
serialization::writePod(file, blockStyle.textIndent);
|
|
||||||
serialization::writePod(file, blockStyle.textIndentDefined);
|
|
||||||
serialization::writePod(file, blockStyle.isRtl);
|
|
||||||
serialization::writePod(file, blockStyle.directionDefined);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<TextBlock> TextBlock::deserialize(HalFile& file) {
|
|
||||||
uint16_t wc;
|
|
||||||
uint8_t hasFocus;
|
|
||||||
uint16_t textBytes;
|
|
||||||
serialization::readPod(file, wc);
|
|
||||||
serialization::readPod(file, hasFocus);
|
|
||||||
serialization::readPod(file, textBytes);
|
|
||||||
|
|
||||||
// Sanity checks: cap the arena allocation and reject impossible geometry
|
|
||||||
// (every word carries at least its NUL terminator).
|
|
||||||
if (wc > 10000) {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if ((wc == 0 && textBytes != 0) || (wc > 0 && textBytes < wc)) {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: bad text size %u for %u words", textBytes, wc);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<TextBlock> block(new (std::nothrow) TextBlock());
|
|
||||||
if (!block) {
|
|
||||||
LOG_ERR("TXB", "OOM: TextBlock");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
block->numWords = wc;
|
|
||||||
block->textBytes = textBytes;
|
|
||||||
block->focusPresent = hasFocus != 0;
|
|
||||||
|
|
||||||
if (wc > 0) {
|
|
||||||
const size_t size = arenaSize(wc, block->focusPresent, textBytes);
|
|
||||||
block->arena = makeUniqueNoThrow<uint8_t[]>(size);
|
|
||||||
if (!block->arena) {
|
|
||||||
LOG_ERR("TXB", "OOM: arena %u bytes", static_cast<uint32_t>(size));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (file.read(block->arena.get(), size) != size) {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: arena read (%u bytes)", static_cast<uint32_t>(size));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
block->bindArenaPointers();
|
|
||||||
|
|
||||||
// Validate offsets before anything dereferences wordText(): offset 0 first,
|
|
||||||
// strictly increasing, in bounds, and every word NUL-terminated (word i ends
|
|
||||||
// at the byte before offset i+1; the last word at the last text byte).
|
|
||||||
const uint16_t* textOff = block->textOffArr;
|
|
||||||
const char* text = block->textArr;
|
|
||||||
if (textOff[0] != 0 || text[textBytes - 1] != '\0') {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: corrupt text layout");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
for (uint16_t i = 1; i < wc; i++) {
|
|
||||||
if (textOff[i] <= textOff[i - 1] || textOff[i] >= textBytes || text[textOff[i] - 1] != '\0') {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: corrupt word offset %u", i);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style (alignment + margins/padding/indent)
|
|
||||||
BlockStyle& blockStyle = block->blockStyle;
|
|
||||||
serialization::readPod(file, blockStyle.alignment);
|
|
||||||
serialization::readPod(file, blockStyle.textAlignDefined);
|
|
||||||
serialization::readPod(file, blockStyle.marginTop);
|
|
||||||
serialization::readPod(file, blockStyle.marginBottom);
|
|
||||||
serialization::readPod(file, blockStyle.marginLeft);
|
|
||||||
serialization::readPod(file, blockStyle.marginRight);
|
|
||||||
serialization::readPod(file, blockStyle.paddingTop);
|
|
||||||
serialization::readPod(file, blockStyle.paddingBottom);
|
|
||||||
serialization::readPod(file, blockStyle.paddingLeft);
|
|
||||||
serialization::readPod(file, blockStyle.paddingRight);
|
|
||||||
serialization::readPod(file, blockStyle.textIndent);
|
|
||||||
serialization::readPod(file, blockStyle.textIndentDefined);
|
|
||||||
serialization::readPod(file, blockStyle.isRtl);
|
|
||||||
serialization::readPod(file, blockStyle.directionDefined);
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Block.h"
|
|
||||||
#include "BlockStyle.h"
|
|
||||||
|
|
||||||
// Represents a line of text on a page.
|
|
||||||
//
|
|
||||||
// All per-word data lives in ONE flat heap allocation (the arena) instead of
|
|
||||||
// six parallel vectors: a resident page holds ~25-30 of these blocks, and the
|
|
||||||
// vector-of-string layout cost ~250 throwing allocations per page load, which
|
|
||||||
// was the primary driver of heap fragmentation on the ESP32-C3.
|
|
||||||
//
|
|
||||||
// Arena layout, in order (2-byte alignment holds by construction: all 16-bit
|
|
||||||
// arrays come first and the arena base is allocator-aligned; RISC-V faults on
|
|
||||||
// unaligned multi-byte access):
|
|
||||||
// uint16_t textOff[wordCount] byte offset of word i's text in text[]
|
|
||||||
// int16_t xpos[wordCount]
|
|
||||||
// uint16_t focusSuffixX[wordCount] present only when focusPresent
|
|
||||||
// uint8_t styles[wordCount]
|
|
||||||
// uint8_t focusBoundary[wordCount] present only when focusPresent
|
|
||||||
// char text[textBytes] all words back to back, NUL-terminated
|
|
||||||
//
|
|
||||||
// Each word is stored NUL-terminated so render() can hand `text + textOff[i]`
|
|
||||||
// straight to C APIs (drawText) with no std::string materialization.
|
|
||||||
//
|
|
||||||
// Focus split semantics (unchanged from the vector layout): boundary N > 0
|
|
||||||
// means the first N bytes of word i render bold, the remainder in the base
|
|
||||||
// style. N is bounded to 9 codepoints (<= 36 UTF-8 bytes) by the clamp in
|
|
||||||
// ParsedText::addWord. focusSuffixX is the pre-computed pixel offset from the
|
|
||||||
// word start to the regular suffix. Both arrays are omitted from the arena
|
|
||||||
// entirely when no word on the line has a split (zero per-word RAM cost when
|
|
||||||
// focus reading is disabled).
|
|
||||||
class TextBlock final : public Block {
|
|
||||||
private:
|
|
||||||
BlockStyle blockStyle;
|
|
||||||
uint16_t numWords = 0;
|
|
||||||
uint16_t textBytes = 0; // total size of the text region, including NULs
|
|
||||||
bool focusPresent = false;
|
|
||||||
bool isValid = true;
|
|
||||||
// The ONLY allocation: makeUniqueNoThrow, so OOM yields an invalid block
|
|
||||||
// instead of abort() (bare new is not nothrow with -fno-exceptions).
|
|
||||||
std::unique_ptr<uint8_t[]> arena;
|
|
||||||
// Typed views into the arena, bound once after the arena is filled. All
|
|
||||||
// 16-bit bases sit at even offsets, so direct dereference is alignment-safe.
|
|
||||||
const uint16_t* textOffArr = nullptr;
|
|
||||||
const int16_t* xposArr = nullptr;
|
|
||||||
const uint16_t* focusSuffixXArr = nullptr; // null when !focusPresent
|
|
||||||
const uint8_t* stylesArr = nullptr;
|
|
||||||
const uint8_t* focusBoundaryArr = nullptr; // null when !focusPresent
|
|
||||||
const char* textArr = nullptr;
|
|
||||||
|
|
||||||
TextBlock() = default; // deserialize() fills the fields directly
|
|
||||||
static size_t arenaSize(uint16_t wordCount, bool hasFocus, uint16_t textBytes);
|
|
||||||
void bindArenaPointers();
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Flatten-on-construct: copies the layout-time vectors into the arena; the
|
|
||||||
// vectors die with the caller. On arena OOM the block is empty and valid()
|
|
||||||
// is false -- callers must check and fail the line instead of using it.
|
|
||||||
explicit TextBlock(const std::vector<std::string>& words, const std::vector<int16_t>& wordXpos,
|
|
||||||
const std::vector<EpdFontFamily::Style>& wordStyles, const std::vector<uint8_t>& focusBoundary,
|
|
||||||
const std::vector<uint16_t>& focusSuffixX, const BlockStyle& blockStyle = BlockStyle());
|
|
||||||
~TextBlock() override = default;
|
|
||||||
TextBlock(const TextBlock&) = delete;
|
|
||||||
TextBlock& operator=(const TextBlock&) = delete;
|
|
||||||
|
|
||||||
void setBlockStyle(const BlockStyle& blockStyle) { this->blockStyle = blockStyle; }
|
|
||||||
const BlockStyle& getBlockStyle() const { return blockStyle; }
|
|
||||||
bool isEmpty() override { return numWords == 0; }
|
|
||||||
bool valid() const { return isValid; }
|
|
||||||
uint16_t wordCount() const { return numWords; }
|
|
||||||
// NUL-terminated by construction; safe to pass to C APIs directly.
|
|
||||||
const char* wordText(const uint16_t i) const { return textArr + textOffArr[i]; }
|
|
||||||
uint16_t wordTextLen(const uint16_t i) const {
|
|
||||||
const uint16_t end = (i + 1 < numWords) ? textOffArr[i + 1] : textBytes;
|
|
||||||
return end - textOffArr[i] - 1; // exclude the NUL
|
|
||||||
}
|
|
||||||
int16_t wordXpos(const uint16_t i) const { return xposArr[i]; }
|
|
||||||
EpdFontFamily::Style wordStyle(const uint16_t i) const { return static_cast<EpdFontFamily::Style>(stylesArr[i]); }
|
|
||||||
uint8_t focusBoundary(const uint16_t i) const { return focusPresent ? focusBoundaryArr[i] : 0; }
|
|
||||||
uint16_t focusSuffixX(const uint16_t i) const { return focusPresent ? focusSuffixXArr[i] : 0; }
|
|
||||||
|
|
||||||
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
|
||||||
bool serialize(HalFile& file) const;
|
|
||||||
static std::unique_ptr<TextBlock> deserialize(HalFile& file);
|
|
||||||
};
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <HalDisplay.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// Direct framebuffer writer that eliminates per-pixel overhead from the image
|
|
||||||
// rendering hot path. Pre-computes orientation transform as linear coefficients
|
|
||||||
// and caches render-mode state so the inner loop is: one multiply, one add,
|
|
||||||
// one shift, and one AND per pixel — no branches, no method calls.
|
|
||||||
//
|
|
||||||
// Caller is responsible for ensuring (outX, outY) are within screen bounds.
|
|
||||||
// ImageBlock::render() already validates this before entering the pixel loop,
|
|
||||||
// and the JPEG/PNG callbacks pre-clamp destination ranges to screen bounds.
|
|
||||||
struct DirectPixelWriter {
|
|
||||||
uint8_t* fb;
|
|
||||||
GfxRenderer::RenderMode mode;
|
|
||||||
uint16_t displayWidthBytes; // Runtime framebuffer stride (X4: 100, X3: 99)
|
|
||||||
// Active write target: for tiled grayscale, fb is the band scratch, originY is
|
|
||||||
// the band's top physical row, and clipRows is the band height. Off-band
|
|
||||||
// pixels are dropped. With no strip active these collapse to the full frame
|
|
||||||
// (originY 0, clipRows panelHeight) so the clip doubles as a bounds guard.
|
|
||||||
int originY;
|
|
||||||
int clipRows;
|
|
||||||
|
|
||||||
// Orientation is collapsed into a linear transform:
|
|
||||||
// phyX = phyXBase + x * phyXStepX + y * phyXStepY
|
|
||||||
// phyY = phyYBase + x * phyYStepX + y * phyYStepY
|
|
||||||
int phyXBase, phyYBase;
|
|
||||||
int phyXStepX, phyYStepX; // per logical-X step
|
|
||||||
int phyXStepY, phyYStepY; // per logical-Y step
|
|
||||||
|
|
||||||
// Row-precomputed: the Y-dependent portion of the physical coords
|
|
||||||
int rowPhyXBase, rowPhyYBase;
|
|
||||||
|
|
||||||
void init(GfxRenderer& renderer) {
|
|
||||||
fb = renderer.getWriteTarget();
|
|
||||||
originY = renderer.getWriteOriginY();
|
|
||||||
clipRows = renderer.getWriteRows();
|
|
||||||
mode = renderer.getRenderMode();
|
|
||||||
displayWidthBytes = renderer.getDisplayWidthBytes();
|
|
||||||
|
|
||||||
const int phyW = renderer.getDisplayWidth();
|
|
||||||
const int phyH = renderer.getDisplayHeight();
|
|
||||||
|
|
||||||
switch (renderer.getOrientation()) {
|
|
||||||
case GfxRenderer::Portrait:
|
|
||||||
// phyX = y, phyY = (phyH-1) - x
|
|
||||||
phyXBase = 0;
|
|
||||||
phyYBase = phyH - 1;
|
|
||||||
phyXStepX = 0;
|
|
||||||
phyYStepX = -1;
|
|
||||||
phyXStepY = 1;
|
|
||||||
phyYStepY = 0;
|
|
||||||
break;
|
|
||||||
case GfxRenderer::LandscapeClockwise:
|
|
||||||
// phyX = (phyW-1) - x, phyY = (phyH-1) - y
|
|
||||||
phyXBase = phyW - 1;
|
|
||||||
phyYBase = phyH - 1;
|
|
||||||
phyXStepX = -1;
|
|
||||||
phyYStepX = 0;
|
|
||||||
phyXStepY = 0;
|
|
||||||
phyYStepY = -1;
|
|
||||||
break;
|
|
||||||
case GfxRenderer::PortraitInverted:
|
|
||||||
// phyX = (phyW-1) - y, phyY = x
|
|
||||||
phyXBase = phyW - 1;
|
|
||||||
phyYBase = 0;
|
|
||||||
phyXStepX = 0;
|
|
||||||
phyYStepX = 1;
|
|
||||||
phyXStepY = -1;
|
|
||||||
phyYStepY = 0;
|
|
||||||
break;
|
|
||||||
case GfxRenderer::LandscapeCounterClockwise:
|
|
||||||
// phyX = x, phyY = y
|
|
||||||
phyXBase = 0;
|
|
||||||
phyYBase = 0;
|
|
||||||
phyXStepX = 1;
|
|
||||||
phyYStepX = 0;
|
|
||||||
phyXStepY = 0;
|
|
||||||
phyYStepY = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Fallback to LandscapeCounterClockwise (identity transform)
|
|
||||||
phyXBase = 0;
|
|
||||||
phyYBase = 0;
|
|
||||||
phyXStepX = 1;
|
|
||||||
phyYStepX = 0;
|
|
||||||
phyXStepY = 0;
|
|
||||||
phyYStepY = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call once per row before the column loop.
|
|
||||||
// Pre-computes the Y-dependent portion so writePixel() only needs the X part.
|
|
||||||
inline void beginRow(int logicalY) {
|
|
||||||
rowPhyXBase = phyXBase + logicalY * phyXStepY;
|
|
||||||
rowPhyYBase = phyYBase + logicalY * phyYStepY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the current row (set via beginRow), narrow [colStart, colEnd) to the
|
|
||||||
// columns whose pixels fall inside the active strip band. writePixel() would
|
|
||||||
// clip the rest anyway, but on a strip pass that is most of a full-page image
|
|
||||||
// (only ~one strip-height worth of columns survive in portrait); skipping them
|
|
||||||
// here avoids the per-pixel unpack+transform entirely. For full-frame passes
|
|
||||||
// (clipRows == panel height) the range is unchanged. xBase is the logical X of
|
|
||||||
// column 0; the band test mirrors writePixel(): 0 <= phyY - originY < clipRows.
|
|
||||||
inline void bandColRange(int xBase, int width, int& colStart, int& colEnd) const {
|
|
||||||
// init() only ever sets phyYStepX to 0, +1, or -1; the +1/-1 solve below
|
|
||||||
// relies on that.
|
|
||||||
assert(phyYStepX == 0 || phyYStepX == 1 || phyYStepX == -1);
|
|
||||||
colStart = 0;
|
|
||||||
colEnd = width;
|
|
||||||
if (phyYStepX == 0) {
|
|
||||||
// phyY is constant across the row: the whole row is in-band or out.
|
|
||||||
const int sy = rowPhyYBase - originY;
|
|
||||||
if (static_cast<unsigned>(sy) >= static_cast<unsigned>(clipRows)) colEnd = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// phyY = rowPhyYBase + logicalX * phyYStepX (phyYStepX is +1 or -1).
|
|
||||||
// Solve originY <= phyY <= originY + clipRows - 1 for logicalX.
|
|
||||||
const int loY = originY;
|
|
||||||
const int hiY = originY + clipRows - 1;
|
|
||||||
int xLo, xHi;
|
|
||||||
if (phyYStepX > 0) {
|
|
||||||
xLo = loY - rowPhyYBase;
|
|
||||||
xHi = hiY - rowPhyYBase;
|
|
||||||
} else {
|
|
||||||
xLo = rowPhyYBase - hiY;
|
|
||||||
xHi = rowPhyYBase - loY;
|
|
||||||
}
|
|
||||||
const int cs = xLo - xBase;
|
|
||||||
const int ce = xHi - xBase + 1; // exclusive
|
|
||||||
if (cs > colStart) colStart = cs;
|
|
||||||
if (ce < colEnd) colEnd = ce;
|
|
||||||
if (colStart < 0) colStart = 0;
|
|
||||||
if (colEnd > width) colEnd = width;
|
|
||||||
if (colStart > colEnd) colStart = colEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a single 2-bit dithered pixel value to the framebuffer.
|
|
||||||
// Must be called after beginRow() for the current row.
|
|
||||||
// No bounds checking — caller guarantees coordinates are valid.
|
|
||||||
inline void writePixel(int logicalX, uint8_t pixelValue) const {
|
|
||||||
// Determine whether to draw based on render mode
|
|
||||||
bool draw;
|
|
||||||
bool state;
|
|
||||||
switch (mode) {
|
|
||||||
case GfxRenderer::BW:
|
|
||||||
draw = (pixelValue < 3);
|
|
||||||
state = true;
|
|
||||||
break;
|
|
||||||
case GfxRenderer::GRAYSCALE_MSB:
|
|
||||||
draw = (pixelValue == 1 || pixelValue == 2);
|
|
||||||
state = false;
|
|
||||||
break;
|
|
||||||
case GfxRenderer::GRAYSCALE_LSB:
|
|
||||||
draw = (pixelValue == 1);
|
|
||||||
state = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!draw) return;
|
|
||||||
|
|
||||||
const int phyX = rowPhyXBase + logicalX * phyXStepX;
|
|
||||||
const int phyY = rowPhyYBase + logicalX * phyYStepX;
|
|
||||||
|
|
||||||
// Band-local row. The unsigned compare drops both off-band pixels (strip
|
|
||||||
// mode) and any out-of-frame row (full-frame mode) in one branch.
|
|
||||||
const int sy = phyY - originY;
|
|
||||||
if (static_cast<unsigned>(sy) >= static_cast<unsigned>(clipRows)) return;
|
|
||||||
|
|
||||||
const uint16_t byteIndex = static_cast<uint16_t>(sy * displayWidthBytes + (phyX >> 3));
|
|
||||||
const uint8_t bitMask = 1 << (7 - (phyX & 7));
|
|
||||||
|
|
||||||
if (state) {
|
|
||||||
fb[byteIndex] &= ~bitMask; // Clear bit (draw black)
|
|
||||||
} else {
|
|
||||||
fb[byteIndex] |= bitMask; // Set bit (draw white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Direct cache writer that eliminates per-pixel overhead from PixelCache::setPixel().
|
|
||||||
// Pre-computes row pointer so the inner loop is just byte index + bit manipulation.
|
|
||||||
//
|
|
||||||
// The cache buffer is a small streaming band (e.g. 16 rows), not the full image,
|
|
||||||
// so a band-relative row/column that lands outside it would corrupt adjacent
|
|
||||||
// heap. This writer therefore bounds-checks every access: beginRow() invalidates
|
|
||||||
// the row when it falls outside the band, and writePixel() drops out-of-range
|
|
||||||
// columns. This path only runs during the single decode that populates the
|
|
||||||
// cache, never on the screen render hot path, so the checks are cheap.
|
|
||||||
struct DirectCacheWriter {
|
|
||||||
uint8_t* buffer;
|
|
||||||
int bytesPerRow;
|
|
||||||
int bandRows;
|
|
||||||
int originX;
|
|
||||||
uint8_t* rowPtr; // Pre-computed for current row; nullptr if row is out of band
|
|
||||||
|
|
||||||
void init(uint8_t* cacheBuffer, int cacheBytesPerRow, int cacheBandRows, int cacheOriginX) {
|
|
||||||
buffer = cacheBuffer;
|
|
||||||
bytesPerRow = cacheBytesPerRow;
|
|
||||||
bandRows = cacheBandRows;
|
|
||||||
originX = cacheOriginX;
|
|
||||||
rowPtr = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call once per row before the column loop. Drops rows outside the band.
|
|
||||||
inline void beginRow(int screenY, int cacheOriginY) {
|
|
||||||
const int localRow = screenY - cacheOriginY;
|
|
||||||
rowPtr = (static_cast<unsigned>(localRow) < static_cast<unsigned>(bandRows))
|
|
||||||
? buffer + (size_t)localRow * bytesPerRow
|
|
||||||
: nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a 2-bit pixel value. Drops the write if the row is out of band or the
|
|
||||||
// column is out of range.
|
|
||||||
inline void writePixel(int screenX, uint8_t value) const {
|
|
||||||
if (!rowPtr) return;
|
|
||||||
const int localX = screenX - originX;
|
|
||||||
const int byteIdx = localX >> 2; // localX / 4
|
|
||||||
if (static_cast<unsigned>(byteIdx) >= static_cast<unsigned>(bytesPerRow)) return;
|
|
||||||
const int bitShift = 6 - (localX & 3) * 2; // MSB first: pixel 0 at bits 6-7
|
|
||||||
rowPtr[byteIdx] = (rowPtr[byteIdx] & ~(0x03 << bitShift)) | ((value & 0x03) << bitShift);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// 4x4 Bayer matrix for ordered dithering
|
|
||||||
inline const uint8_t bayer4x4[4][4] = {
|
|
||||||
{0, 8, 2, 10},
|
|
||||||
{12, 4, 14, 6},
|
|
||||||
{3, 11, 1, 9},
|
|
||||||
{15, 7, 13, 5},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply Bayer dithering and quantize to 4 levels (0-3)
|
|
||||||
// Stateless - works correctly with any pixel processing order
|
|
||||||
inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) {
|
|
||||||
int bayer = bayer4x4[y & 3][x & 3];
|
|
||||||
int dither = (bayer - 8) * 5; // Scale to +/-40 (half of quantization step 85)
|
|
||||||
|
|
||||||
int adjusted = gray + dither;
|
|
||||||
if (adjusted < 0) adjusted = 0;
|
|
||||||
if (adjusted > 255) adjusted = 255;
|
|
||||||
|
|
||||||
if (adjusted < 64) return 0;
|
|
||||||
if (adjusted < 128) return 1;
|
|
||||||
if (adjusted < 192) return 2;
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#include "ImageDecoderFactory.h"
|
|
||||||
|
|
||||||
#include <Logging.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "JpegToFramebufferConverter.h"
|
|
||||||
#include "PngToFramebufferConverter.h"
|
|
||||||
|
|
||||||
std::unique_ptr<JpegToFramebufferConverter> ImageDecoderFactory::jpegDecoder = nullptr;
|
|
||||||
std::unique_ptr<PngToFramebufferConverter> ImageDecoderFactory::pngDecoder = nullptr;
|
|
||||||
|
|
||||||
ImageToFramebufferDecoder* ImageDecoderFactory::getDecoder(const std::string& imagePath) {
|
|
||||||
std::string ext = imagePath;
|
|
||||||
size_t dotPos = ext.rfind('.');
|
|
||||||
if (dotPos != std::string::npos) {
|
|
||||||
ext = ext.substr(dotPos);
|
|
||||||
for (auto& c : ext) {
|
|
||||||
c = tolower(c);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ext = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JpegToFramebufferConverter::supportsFormat(ext)) {
|
|
||||||
if (!jpegDecoder) {
|
|
||||||
jpegDecoder.reset(new JpegToFramebufferConverter());
|
|
||||||
}
|
|
||||||
return jpegDecoder.get();
|
|
||||||
} else if (PngToFramebufferConverter::supportsFormat(ext)) {
|
|
||||||
if (!pngDecoder) {
|
|
||||||
pngDecoder.reset(new PngToFramebufferConverter());
|
|
||||||
}
|
|
||||||
return pngDecoder.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_ERR("DEC", "No decoder found for image: %s", imagePath.c_str());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImageDecoderFactory::isFormatSupported(const std::string& imagePath) { return getDecoder(imagePath) != nullptr; }
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "ImageToFramebufferDecoder.h"
|
|
||||||
|
|
||||||
class JpegToFramebufferConverter;
|
|
||||||
class PngToFramebufferConverter;
|
|
||||||
|
|
||||||
class ImageDecoderFactory {
|
|
||||||
public:
|
|
||||||
// Returns non-owning pointer - factory owns the decoder lifetime
|
|
||||||
static ImageToFramebufferDecoder* getDecoder(const std::string& imagePath);
|
|
||||||
static bool isFormatSupported(const std::string& imagePath);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::unique_ptr<JpegToFramebufferConverter> jpegDecoder;
|
|
||||||
static std::unique_ptr<PngToFramebufferConverter> pngDecoder;
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#include "ImageToFramebufferDecoder.h"
|
|
||||||
|
|
||||||
#include <Logging.h>
|
|
||||||
|
|
||||||
bool ImageToFramebufferDecoder::validateImageDimensions(int width, int height, const std::string& format) {
|
|
||||||
if (width * height > MAX_SOURCE_PIXELS) {
|
|
||||||
LOG_ERR("IMG", "Image too large (%dx%d = %d pixels %s), max supported: %d pixels", width, height, width * height,
|
|
||||||
format.c_str(), MAX_SOURCE_PIXELS);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageToFramebufferDecoder::warnUnsupportedFeature(const std::string& feature, const std::string& imagePath) {
|
|
||||||
LOG_ERR("IMG", "Warning: Unsupported feature '%s' in image '%s'. Image may not display correctly.", feature.c_str(),
|
|
||||||
imagePath.c_str());
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class GfxRenderer;
|
|
||||||
|
|
||||||
struct ImageDimensions {
|
|
||||||
int16_t width;
|
|
||||||
int16_t height;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderConfig {
|
|
||||||
int x, y;
|
|
||||||
int maxWidth, maxHeight;
|
|
||||||
bool useGrayscale = true;
|
|
||||||
bool useDithering = true;
|
|
||||||
bool performanceMode = false;
|
|
||||||
bool useExactDimensions = false; // If true, use maxWidth/maxHeight as exact output size (no recalculation)
|
|
||||||
std::string cachePath; // If non-empty, decoder will write pixel cache to this path
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageToFramebufferDecoder {
|
|
||||||
public:
|
|
||||||
virtual ~ImageToFramebufferDecoder() = default;
|
|
||||||
|
|
||||||
virtual bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) = 0;
|
|
||||||
|
|
||||||
virtual bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const = 0;
|
|
||||||
|
|
||||||
virtual const char* getFormatName() const = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Size validation helpers
|
|
||||||
static constexpr int MAX_SOURCE_PIXELS = 3145728; // 2048 * 1536
|
|
||||||
|
|
||||||
bool validateImageDimensions(int width, int height, const std::string& format);
|
|
||||||
void warnUnsupportedFeature(const std::string& feature, const std::string& imagePath);
|
|
||||||
};
|
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
#include "JpegToFramebufferConverter.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <JPEGDEC.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Memory.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <memory>
|
|
||||||
#include <new>
|
|
||||||
|
|
||||||
#include "DirectPixelWriter.h"
|
|
||||||
#include "DitherUtils.h"
|
|
||||||
#include "PixelCache.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Context struct passed through JPEGDEC callbacks to avoid global mutable state.
|
|
||||||
// The draw callback receives this via pDraw->pUser (set by setUserPointer()).
|
|
||||||
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by jpegOpen()).
|
|
||||||
struct JpegContext {
|
|
||||||
GfxRenderer* renderer{nullptr};
|
|
||||||
const RenderConfig* config{nullptr};
|
|
||||||
int screenWidth{0};
|
|
||||||
int screenHeight{0};
|
|
||||||
|
|
||||||
// Source dimensions after JPEGDEC's built-in scaling
|
|
||||||
int scaledSrcWidth{0};
|
|
||||||
int scaledSrcHeight{0};
|
|
||||||
|
|
||||||
// Final output dimensions
|
|
||||||
int dstWidth{0};
|
|
||||||
int dstHeight{0};
|
|
||||||
|
|
||||||
// Fine scale in 16.16 fixed-point (ESP32-C3 has no FPU).
|
|
||||||
// X and Y axes use separate scale factors: the aspect ratio of the output (dstWidth/dstHeight)
|
|
||||||
// may differ from the source (srcWidth/srcHeight) due to integer rounding of displayHeight.
|
|
||||||
// Using a single (X-based) scale for both axes causes the wrong srcRow to be skipped
|
|
||||||
// during nearest-neighbor downscaling, potentially losing critical image content.
|
|
||||||
int32_t fineScaleFPX{1 << 16}; // X: src -> dst column mapping
|
|
||||||
int32_t invScaleFPX{1 << 16}; // X: dst -> src column mapping
|
|
||||||
int32_t fineScaleFPY{1 << 16}; // Y: src -> dst row mapping
|
|
||||||
int32_t invScaleFPY{1 << 16}; // Y: dst -> src row mapping
|
|
||||||
|
|
||||||
PixelCache cache;
|
|
||||||
bool caching{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
// File I/O callbacks use pFile->fHandle to access the HalFile*,
|
|
||||||
// avoiding the need for global file state.
|
|
||||||
void* jpegOpen(const char* filename, int32_t* size) {
|
|
||||||
HalFile* f = new HalFile();
|
|
||||||
if (!Storage.openFileForRead("JPG", std::string(filename), *f)) {
|
|
||||||
delete f;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
*size = f->size();
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void jpegClose(void* handle) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(handle);
|
|
||||||
if (f) {
|
|
||||||
f->close();
|
|
||||||
delete f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JPEGDEC tracks file position via pFile->iPos internally (e.g. JPEGGetMoreData
|
|
||||||
// checks iPos < iSize to decide whether more data is available). The callbacks
|
|
||||||
// MUST maintain iPos to match the actual file position, otherwise progressive
|
|
||||||
// JPEGs with large headers fail during parsing.
|
|
||||||
int32_t jpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
|
||||||
if (!f) return 0;
|
|
||||||
int32_t bytesRead = f->read(pBuf, len);
|
|
||||||
if (bytesRead < 0) return 0;
|
|
||||||
pFile->iPos += bytesRead;
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t jpegSeek(JPEGFILE* pFile, int32_t pos) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
|
||||||
if (!f) return -1;
|
|
||||||
if (!f->seek(pos)) return -1;
|
|
||||||
pFile->iPos = pos;
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JPEGDEC object is ~17 KB due to internal decode buffers.
|
|
||||||
// Heap-allocate on demand so memory is only used during active decode.
|
|
||||||
constexpr size_t JPEG_DECODER_APPROX_SIZE = 20 * 1024;
|
|
||||||
constexpr size_t MIN_FREE_HEAP_FOR_JPEG = JPEG_DECODER_APPROX_SIZE + 16 * 1024;
|
|
||||||
|
|
||||||
// Choose JPEGDEC's built-in scale factor for coarse downscaling.
|
|
||||||
// Returns the scale denominator (1, 2, 4, or 8) and sets jpegScaleOption.
|
|
||||||
int chooseJpegScale(float targetScale, int& jpegScaleOption) {
|
|
||||||
if (targetScale <= 0.125f) {
|
|
||||||
jpegScaleOption = JPEG_SCALE_EIGHTH;
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
if (targetScale <= 0.25f) {
|
|
||||||
jpegScaleOption = JPEG_SCALE_QUARTER;
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
if (targetScale <= 0.5f) {
|
|
||||||
jpegScaleOption = JPEG_SCALE_HALF;
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
jpegScaleOption = 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed-point 16.16 arithmetic avoids software float emulation on ESP32-C3 (no FPU).
|
|
||||||
constexpr int FP_SHIFT = 16;
|
|
||||||
constexpr int32_t FP_ONE = 1 << FP_SHIFT;
|
|
||||||
constexpr int32_t FP_MASK = FP_ONE - 1;
|
|
||||||
|
|
||||||
int jpegDrawCallback(JPEGDRAW* pDraw) {
|
|
||||||
JpegContext* ctx = reinterpret_cast<JpegContext*>(pDraw->pUser);
|
|
||||||
if (!ctx || !ctx->config || !ctx->renderer) return 0;
|
|
||||||
|
|
||||||
// In EIGHT_BIT_GRAYSCALE mode, pPixels contains 8-bit grayscale values
|
|
||||||
// Buffer is densely packed: stride = pDraw->iWidth, valid columns = pDraw->iWidthUsed
|
|
||||||
uint8_t* pixels = reinterpret_cast<uint8_t*>(pDraw->pPixels);
|
|
||||||
const int stride = pDraw->iWidth;
|
|
||||||
const int validW = pDraw->iWidthUsed;
|
|
||||||
const int blockH = pDraw->iHeight;
|
|
||||||
|
|
||||||
if (stride <= 0 || blockH <= 0 || validW <= 0) return 1;
|
|
||||||
|
|
||||||
const bool useDithering = ctx->config->useDithering;
|
|
||||||
bool caching = ctx->caching;
|
|
||||||
const int32_t fineScaleFPX = ctx->fineScaleFPX;
|
|
||||||
const int32_t invScaleFPX = ctx->invScaleFPX;
|
|
||||||
const int32_t fineScaleFPY = ctx->fineScaleFPY;
|
|
||||||
const int32_t invScaleFPY = ctx->invScaleFPY;
|
|
||||||
GfxRenderer& renderer = *ctx->renderer;
|
|
||||||
const int cfgX = ctx->config->x;
|
|
||||||
const int cfgY = ctx->config->y;
|
|
||||||
const int blockX = pDraw->x;
|
|
||||||
const int blockY = pDraw->y;
|
|
||||||
|
|
||||||
// Determine destination pixel range covered by this source block
|
|
||||||
const int srcYEnd = blockY + blockH;
|
|
||||||
const int srcXEnd = blockX + validW;
|
|
||||||
|
|
||||||
int dstYStart = (int)((int64_t)blockY * fineScaleFPY >> FP_SHIFT);
|
|
||||||
int dstYEnd = (srcYEnd >= ctx->scaledSrcHeight) ? ctx->dstHeight : (int)((int64_t)srcYEnd * fineScaleFPY >> FP_SHIFT);
|
|
||||||
int dstXStart = (int)((int64_t)blockX * fineScaleFPX >> FP_SHIFT);
|
|
||||||
int dstXEnd = (srcXEnd >= ctx->scaledSrcWidth) ? ctx->dstWidth : (int)((int64_t)srcXEnd * fineScaleFPX >> FP_SHIFT);
|
|
||||||
|
|
||||||
// Pre-clamp destination ranges to screen bounds (eliminates per-pixel screen checks)
|
|
||||||
int clampYMax = ctx->dstHeight;
|
|
||||||
if (ctx->screenHeight - cfgY < clampYMax) clampYMax = ctx->screenHeight - cfgY;
|
|
||||||
if (dstYStart < -cfgY) dstYStart = -cfgY;
|
|
||||||
if (dstYEnd > clampYMax) dstYEnd = clampYMax;
|
|
||||||
|
|
||||||
int clampXMax = ctx->dstWidth;
|
|
||||||
if (ctx->screenWidth - cfgX < clampXMax) clampXMax = ctx->screenWidth - cfgX;
|
|
||||||
if (dstXStart < -cfgX) dstXStart = -cfgX;
|
|
||||||
if (dstXEnd > clampXMax) dstXEnd = clampXMax;
|
|
||||||
|
|
||||||
if (dstYStart >= dstYEnd || dstXStart >= dstXEnd) return 1;
|
|
||||||
|
|
||||||
// Pre-compute orientation and render-mode state once per callback invocation
|
|
||||||
DirectPixelWriter pw;
|
|
||||||
pw.init(renderer);
|
|
||||||
|
|
||||||
// The cache streams to disk one MCU-row band at a time. Flushing rows below
|
|
||||||
// this block (raster order guarantees they are final) repositions the band;
|
|
||||||
// cacheOriginY then maps screen rows to the band-local buffer rows. If a flush
|
|
||||||
// write fails, stop caching for the rest of this decode (and let finalize drop
|
|
||||||
// the partial file) rather than writing past the band buffer.
|
|
||||||
DirectCacheWriter cw;
|
|
||||||
int cacheOriginY = 0;
|
|
||||||
if (caching) {
|
|
||||||
if (!ctx->cache.advanceTo(dstYStart)) {
|
|
||||||
caching = false;
|
|
||||||
ctx->caching = false;
|
|
||||||
} else {
|
|
||||||
cw.init(ctx->cache.buffer, ctx->cache.bytesPerRow, ctx->cache.bandRows, ctx->cache.originX);
|
|
||||||
cacheOriginY = ctx->config->y + ctx->cache.bandStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === 1:1 fast path: no scaling math ===
|
|
||||||
if (fineScaleFPX == FP_ONE && fineScaleFPY == FP_ONE) {
|
|
||||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
|
||||||
const int outY = cfgY + dstY;
|
|
||||||
pw.beginRow(outY);
|
|
||||||
if (caching) cw.beginRow(outY, cacheOriginY);
|
|
||||||
const uint8_t* row = &pixels[(dstY - blockY) * stride];
|
|
||||||
for (int dstX = dstXStart; dstX < dstXEnd; dstX++) {
|
|
||||||
const int outX = cfgX + dstX;
|
|
||||||
uint8_t gray = row[dstX - blockX];
|
|
||||||
uint8_t dithered;
|
|
||||||
if (useDithering) {
|
|
||||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
dithered = gray / 85;
|
|
||||||
if (dithered > 3) dithered = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, dithered);
|
|
||||||
if (caching) cw.writePixel(outX, dithered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Bilinear interpolation (upscale: fineScale > 1.0) ===
|
|
||||||
// Smooths block boundaries that would otherwise create visible banding
|
|
||||||
// on progressive JPEG DC-only decode (1/8 resolution upscaled to target).
|
|
||||||
if (fineScaleFPX > FP_ONE && fineScaleFPY > FP_ONE) {
|
|
||||||
// Pre-compute safe X range where lx0 and lx0+1 are both in [0, validW-1].
|
|
||||||
// Only the left/right edge pixels (typically 0-2 and 1-8 respectively) need clamping.
|
|
||||||
int safeXStart = (int)(((int64_t)blockX * fineScaleFPX + FP_MASK) >> FP_SHIFT);
|
|
||||||
int safeXEnd = (int)((int64_t)(blockX + validW - 1) * fineScaleFPX >> FP_SHIFT);
|
|
||||||
if (safeXStart < dstXStart) safeXStart = dstXStart;
|
|
||||||
if (safeXEnd > dstXEnd) safeXEnd = dstXEnd;
|
|
||||||
if (safeXStart > safeXEnd) safeXEnd = safeXStart;
|
|
||||||
|
|
||||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
|
||||||
const int outY = cfgY + dstY;
|
|
||||||
pw.beginRow(outY);
|
|
||||||
if (caching) cw.beginRow(outY, cacheOriginY);
|
|
||||||
const int32_t srcFyFP = dstY * invScaleFPY;
|
|
||||||
const int32_t fy = srcFyFP & FP_MASK;
|
|
||||||
const int32_t fyInv = FP_ONE - fy;
|
|
||||||
int ly0 = (srcFyFP >> FP_SHIFT) - blockY;
|
|
||||||
int ly1 = ly0 + 1;
|
|
||||||
if (ly0 < 0) ly0 = 0;
|
|
||||||
if (ly0 >= blockH) ly0 = blockH - 1;
|
|
||||||
if (ly1 >= blockH) ly1 = blockH - 1;
|
|
||||||
|
|
||||||
const uint8_t* row0 = &pixels[ly0 * stride];
|
|
||||||
const uint8_t* row1 = &pixels[ly1 * stride];
|
|
||||||
|
|
||||||
// Left edge (with X boundary clamping)
|
|
||||||
for (int dstX = dstXStart; dstX < safeXStart; dstX++) {
|
|
||||||
const int outX = cfgX + dstX;
|
|
||||||
const int32_t srcFxFP = dstX * invScaleFPX;
|
|
||||||
const int32_t fx = srcFxFP & FP_MASK;
|
|
||||||
const int32_t fxInv = FP_ONE - fx;
|
|
||||||
int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
|
|
||||||
int lx1 = lx0 + 1;
|
|
||||||
if (lx0 < 0) lx0 = 0;
|
|
||||||
if (lx1 < 0) lx1 = 0;
|
|
||||||
if (lx0 >= validW) lx0 = validW - 1;
|
|
||||||
if (lx1 >= validW) lx1 = validW - 1;
|
|
||||||
|
|
||||||
int top = ((int)row0[lx0] * fxInv + (int)row0[lx1] * fx) >> FP_SHIFT;
|
|
||||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx1] * fx) >> FP_SHIFT;
|
|
||||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
|
||||||
|
|
||||||
uint8_t dithered;
|
|
||||||
if (useDithering) {
|
|
||||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
dithered = gray / 85;
|
|
||||||
if (dithered > 3) dithered = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, dithered);
|
|
||||||
if (caching) cw.writePixel(outX, dithered);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interior (no X boundary checks — lx0 and lx0+1 guaranteed in bounds)
|
|
||||||
for (int dstX = safeXStart; dstX < safeXEnd; dstX++) {
|
|
||||||
const int outX = cfgX + dstX;
|
|
||||||
const int32_t srcFxFP = dstX * invScaleFPX;
|
|
||||||
const int32_t fx = srcFxFP & FP_MASK;
|
|
||||||
const int32_t fxInv = FP_ONE - fx;
|
|
||||||
const int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
|
|
||||||
|
|
||||||
int top = ((int)row0[lx0] * fxInv + (int)row0[lx0 + 1] * fx) >> FP_SHIFT;
|
|
||||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx0 + 1] * fx) >> FP_SHIFT;
|
|
||||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
|
||||||
|
|
||||||
uint8_t dithered;
|
|
||||||
if (useDithering) {
|
|
||||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
dithered = gray / 85;
|
|
||||||
if (dithered > 3) dithered = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, dithered);
|
|
||||||
if (caching) cw.writePixel(outX, dithered);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right edge (with X boundary clamping)
|
|
||||||
for (int dstX = safeXEnd; dstX < dstXEnd; dstX++) {
|
|
||||||
const int outX = cfgX + dstX;
|
|
||||||
const int32_t srcFxFP = dstX * invScaleFPX;
|
|
||||||
const int32_t fx = srcFxFP & FP_MASK;
|
|
||||||
const int32_t fxInv = FP_ONE - fx;
|
|
||||||
int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
|
|
||||||
int lx1 = lx0 + 1;
|
|
||||||
if (lx0 >= validW) lx0 = validW - 1;
|
|
||||||
if (lx1 >= validW) lx1 = validW - 1;
|
|
||||||
|
|
||||||
int top = ((int)row0[lx0] * fxInv + (int)row0[lx1] * fx) >> FP_SHIFT;
|
|
||||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx1] * fx) >> FP_SHIFT;
|
|
||||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
|
||||||
|
|
||||||
uint8_t dithered;
|
|
||||||
if (useDithering) {
|
|
||||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
dithered = gray / 85;
|
|
||||||
if (dithered > 3) dithered = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, dithered);
|
|
||||||
if (caching) cw.writePixel(outX, dithered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Nearest-neighbor (downscale: fineScale < 1.0) ===
|
|
||||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
|
||||||
const int outY = cfgY + dstY;
|
|
||||||
pw.beginRow(outY);
|
|
||||||
if (caching) cw.beginRow(outY, cacheOriginY);
|
|
||||||
const int32_t srcFyFP = dstY * invScaleFPY;
|
|
||||||
int ly = (srcFyFP >> FP_SHIFT) - blockY;
|
|
||||||
if (ly < 0) ly = 0;
|
|
||||||
if (ly >= blockH) ly = blockH - 1;
|
|
||||||
const uint8_t* row = &pixels[ly * stride];
|
|
||||||
|
|
||||||
for (int dstX = dstXStart; dstX < dstXEnd; dstX++) {
|
|
||||||
const int outX = cfgX + dstX;
|
|
||||||
const int32_t srcFxFP = dstX * invScaleFPX;
|
|
||||||
int lx = (srcFxFP >> FP_SHIFT) - blockX;
|
|
||||||
if (lx < 0) lx = 0;
|
|
||||||
if (lx >= validW) lx = validW - 1;
|
|
||||||
uint8_t gray = row[lx];
|
|
||||||
|
|
||||||
uint8_t dithered;
|
|
||||||
if (useDithering) {
|
|
||||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
dithered = gray / 85;
|
|
||||||
if (dithered > 3) dithered = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, dithered);
|
|
||||||
if (caching) cw.writePixel(outX, dithered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) {
|
|
||||||
size_t freeHeap = ESP.getFreeHeap();
|
|
||||||
if (freeHeap < MIN_FREE_HEAP_FOR_JPEG) {
|
|
||||||
LOG_ERR("JPG", "Not enough heap for JPEG decoder (%u free, need %u)", freeHeap, MIN_FREE_HEAP_FOR_JPEG);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<JPEGDEC> jpeg(new (std::nothrow) JPEGDEC());
|
|
||||||
if (!jpeg) {
|
|
||||||
LOG_ERR("JPG", "Failed to allocate JPEG decoder for dimensions");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc = jpeg->open(imagePath.c_str(), jpegOpen, jpegClose, jpegRead, jpegSeek, nullptr);
|
|
||||||
const ScopedCleanup cleanup{[&jpeg]() { jpeg->close(); }};
|
|
||||||
if (rc != 1) {
|
|
||||||
LOG_ERR("JPG", "Failed to open JPEG for dimensions (err=%d): %s", jpeg->getLastError(), imagePath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.width = jpeg->getWidth();
|
|
||||||
out.height = jpeg->getHeight();
|
|
||||||
LOG_DBG("JPG", "Image dimensions: %dx%d", out.width, out.height);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config) {
|
|
||||||
LOG_DBG("JPG", "Decoding JPEG: %s", imagePath.c_str());
|
|
||||||
|
|
||||||
size_t freeHeap = ESP.getFreeHeap();
|
|
||||||
if (freeHeap < MIN_FREE_HEAP_FOR_JPEG) {
|
|
||||||
LOG_ERR("JPG", "Not enough heap for JPEG decoder (%u free, need %u)", freeHeap, MIN_FREE_HEAP_FOR_JPEG);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<JPEGDEC> jpeg(new (std::nothrow) JPEGDEC());
|
|
||||||
if (!jpeg) {
|
|
||||||
LOG_ERR("JPG", "Failed to allocate JPEG decoder");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
JpegContext ctx;
|
|
||||||
ctx.renderer = &renderer;
|
|
||||||
ctx.config = &config;
|
|
||||||
ctx.screenWidth = renderer.getScreenWidth();
|
|
||||||
ctx.screenHeight = renderer.getScreenHeight();
|
|
||||||
|
|
||||||
int rc = jpeg->open(imagePath.c_str(), jpegOpen, jpegClose, jpegRead, jpegSeek, jpegDrawCallback);
|
|
||||||
const ScopedCleanup cleanup{[&jpeg]() { jpeg->close(); }};
|
|
||||||
if (rc != 1) {
|
|
||||||
LOG_ERR("JPG", "Failed to open JPEG (err=%d): %s", jpeg->getLastError(), imagePath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int srcWidth = jpeg->getWidth();
|
|
||||||
int srcHeight = jpeg->getHeight();
|
|
||||||
|
|
||||||
if (srcWidth <= 0 || srcHeight <= 0) {
|
|
||||||
LOG_ERR("JPG", "Invalid JPEG dimensions: %dx%d", srcWidth, srcHeight);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateImageDimensions(srcWidth, srcHeight, "JPEG")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isProgressive = jpeg->getJPEGType() == JPEG_MODE_PROGRESSIVE;
|
|
||||||
if (isProgressive) {
|
|
||||||
LOG_INF("JPG", "Progressive JPEG detected - decoding DC coefficients only (lower quality)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate overall target scale
|
|
||||||
float targetScale;
|
|
||||||
int destWidth, destHeight;
|
|
||||||
|
|
||||||
if (config.useExactDimensions && config.maxWidth > 0 && config.maxHeight > 0) {
|
|
||||||
destWidth = config.maxWidth;
|
|
||||||
destHeight = config.maxHeight;
|
|
||||||
targetScale = (float)destWidth / srcWidth;
|
|
||||||
} else {
|
|
||||||
float scaleX = (config.maxWidth > 0 && srcWidth > config.maxWidth) ? (float)config.maxWidth / srcWidth : 1.0f;
|
|
||||||
float scaleY = (config.maxHeight > 0 && srcHeight > config.maxHeight) ? (float)config.maxHeight / srcHeight : 1.0f;
|
|
||||||
targetScale = (scaleX < scaleY) ? scaleX : scaleY;
|
|
||||||
if (targetScale > 1.0f) targetScale = 1.0f;
|
|
||||||
|
|
||||||
destWidth = (int)(srcWidth * targetScale);
|
|
||||||
destHeight = (int)(srcHeight * targetScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose JPEGDEC built-in scaling for coarse downscaling.
|
|
||||||
// Progressive JPEGs: JPEGDEC forces JPEG_SCALE_EIGHTH internally (DC-only
|
|
||||||
// decode produces 1/8 resolution). We must match this to avoid the if/else
|
|
||||||
// priority chain in DecodeJPEG selecting a different scale.
|
|
||||||
int jpegScaleOption;
|
|
||||||
int jpegScaleDenom;
|
|
||||||
if (isProgressive) {
|
|
||||||
jpegScaleOption = JPEG_SCALE_EIGHTH;
|
|
||||||
jpegScaleDenom = 8;
|
|
||||||
} else {
|
|
||||||
jpegScaleDenom = chooseJpegScale(targetScale, jpegScaleOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destWidth <= 0 || destHeight <= 0) {
|
|
||||||
LOG_ERR("JPG", "Degenerate output dimensions %dx%d for %s, skipping render", destWidth, destHeight,
|
|
||||||
imagePath.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.scaledSrcWidth = (srcWidth + jpegScaleDenom - 1) / jpegScaleDenom;
|
|
||||||
ctx.scaledSrcHeight = (srcHeight + jpegScaleDenom - 1) / jpegScaleDenom;
|
|
||||||
ctx.dstWidth = destWidth;
|
|
||||||
ctx.dstHeight = destHeight;
|
|
||||||
ctx.fineScaleFPX = (int32_t)((int64_t)destWidth * FP_ONE / ctx.scaledSrcWidth);
|
|
||||||
ctx.invScaleFPX = (int32_t)((int64_t)ctx.scaledSrcWidth * FP_ONE / destWidth);
|
|
||||||
ctx.fineScaleFPY = (int32_t)((int64_t)destHeight * FP_ONE / ctx.scaledSrcHeight);
|
|
||||||
ctx.invScaleFPY = (int32_t)((int64_t)ctx.scaledSrcHeight * FP_ONE / destHeight);
|
|
||||||
|
|
||||||
LOG_DBG("JPG", "JPEG %dx%d -> %dx%d (scale %.2f, jpegScale 1/%d, fineScale %.2f)%s", srcWidth, srcHeight, destWidth,
|
|
||||||
destHeight, targetScale, jpegScaleDenom, (float)destWidth / ctx.scaledSrcWidth,
|
|
||||||
isProgressive ? " [progressive]" : "");
|
|
||||||
|
|
||||||
// Set pixel type to 8-bit grayscale (must be after open())
|
|
||||||
jpeg->setPixelType(EIGHT_BIT_GRAYSCALE);
|
|
||||||
jpeg->setUserPointer(&ctx);
|
|
||||||
|
|
||||||
// Start streaming the pixel cache to disk. The band only needs to hold the
|
|
||||||
// tallest single decode block: a JPEGDEC MCU cell is at most 16 scaled-source
|
|
||||||
// rows tall, which our fine scale maps to this many output rows.
|
|
||||||
ctx.caching = !config.cachePath.empty();
|
|
||||||
if (ctx.caching) {
|
|
||||||
const int maxBlockDstRows = (int)(((int64_t)16 * ctx.fineScaleFPY) >> FP_SHIFT) + 2;
|
|
||||||
if (!ctx.cache.begin(config.cachePath, destWidth, destHeight, config.x, config.y, maxBlockDstRows)) {
|
|
||||||
LOG_ERR("JPG", "Failed to start cache stream, continuing without caching");
|
|
||||||
ctx.caching = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long decodeStart = millis();
|
|
||||||
rc = jpeg->decode(0, 0, jpegScaleOption);
|
|
||||||
unsigned long decodeTime = millis() - decodeStart;
|
|
||||||
|
|
||||||
if (rc != 1) {
|
|
||||||
LOG_ERR("JPG", "Decode failed (rc=%d, lastError=%d)", rc, jpeg->getLastError());
|
|
||||||
if (ctx.caching) ctx.cache.abort();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("JPG", "JPEG decoding complete - render time: %lu ms", decodeTime);
|
|
||||||
|
|
||||||
// Finalize the streamed cache file. Note: a flush failure mid-decode clears
|
|
||||||
// ctx.caching (the partial file is dropped), so re-read the flag here.
|
|
||||||
if (ctx.caching) {
|
|
||||||
ctx.cache.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JpegToFramebufferConverter::supportsFormat(const std::string& extension) {
|
|
||||||
return FsHelpers::hasJpgExtension(extension);
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "ImageToFramebufferDecoder.h"
|
|
||||||
|
|
||||||
class JpegToFramebufferConverter final : public ImageToFramebufferDecoder {
|
|
||||||
public:
|
|
||||||
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
|
||||||
|
|
||||||
bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) override;
|
|
||||||
|
|
||||||
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
|
||||||
return getDimensionsStatic(imagePath, dims);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool supportsFormat(const std::string& extension);
|
|
||||||
const char* getFormatName() const override { return "JPEG"; }
|
|
||||||
};
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Streaming cache writer for 2-bit pixels (4 levels). Packs 4 pixels per byte,
|
|
||||||
// MSB first.
|
|
||||||
//
|
|
||||||
// The .pxc file is written incrementally in small row bands rather than holding
|
|
||||||
// the whole decoded image in one heap buffer. A full-page image (e.g. 482x728)
|
|
||||||
// needs ~88KB packed, which will not fit alongside the ~20KB JPEG decoder on a
|
|
||||||
// fragmented 380KB heap (free heap is routinely ~55KB on an image page). When
|
|
||||||
// the cache cannot be written, every render pass re-decodes the JPEG from
|
|
||||||
// scratch; an anti-aliased image page renders ~14 times (BW + AA restore + two
|
|
||||||
// grayscale planes x ~6 strips), so a 2s decode becomes a ~30s freeze / watchdog
|
|
||||||
// reset. Streaming keeps the working set to a single MCU-row band, so caching
|
|
||||||
// succeeds and the image is decoded exactly once.
|
|
||||||
//
|
|
||||||
// Correctness relies on JPEGDEC delivering blocks in raster MCU order (outer
|
|
||||||
// loop over y, inner over x: see jpeg.inl DecodeJPEG). Consecutive MCU rows map
|
|
||||||
// to contiguous, non-overlapping destination row ranges, so once a block whose
|
|
||||||
// top row is Y arrives, every output row < Y is final and is flushed to disk.
|
|
||||||
struct PixelCache {
|
|
||||||
uint8_t* buffer; // band buffer: (bandRows + 1) rows; last row kept zeroed
|
|
||||||
uint8_t* zeroRow; // points at the spare zeroed row, for gap/clip fill
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
int bytesPerRow;
|
|
||||||
int originX; // config.x - to convert screen coords to cache coords
|
|
||||||
int originY; // config.y
|
|
||||||
int bandRows; // rows held in the band buffer
|
|
||||||
int bandStart; // image-local row index of band buffer row 0
|
|
||||||
int flushedRows; // image-local rows already written to file
|
|
||||||
HalFile file;
|
|
||||||
std::string cachePathStr;
|
|
||||||
bool ok;
|
|
||||||
|
|
||||||
PixelCache()
|
|
||||||
: buffer(nullptr),
|
|
||||||
zeroRow(nullptr),
|
|
||||||
width(0),
|
|
||||||
height(0),
|
|
||||||
bytesPerRow(0),
|
|
||||||
originX(0),
|
|
||||||
originY(0),
|
|
||||||
bandRows(0),
|
|
||||||
bandStart(0),
|
|
||||||
flushedRows(0),
|
|
||||||
ok(false) {}
|
|
||||||
PixelCache(const PixelCache&) = delete;
|
|
||||||
PixelCache& operator=(const PixelCache&) = delete;
|
|
||||||
|
|
||||||
static constexpr int MIN_BAND_ROWS = 16;
|
|
||||||
static constexpr size_t MAX_BAND_BYTES = 24 * 1024; // band working-set ceiling
|
|
||||||
|
|
||||||
// Open the cache file, write the header, and allocate a band buffer big enough
|
|
||||||
// to hold the tallest single decode block (maxBlockDstRows output rows).
|
|
||||||
bool begin(const std::string& cachePath, int w, int h, int ox, int oy, int maxBlockDstRows) {
|
|
||||||
width = w;
|
|
||||||
height = h;
|
|
||||||
originX = ox;
|
|
||||||
originY = oy;
|
|
||||||
bytesPerRow = (w + 3) / 4; // 2 bits per pixel, 4 pixels per byte
|
|
||||||
bandStart = 0;
|
|
||||||
flushedRows = 0;
|
|
||||||
ok = false;
|
|
||||||
|
|
||||||
int wantRows = maxBlockDstRows + 2;
|
|
||||||
if (wantRows < MIN_BAND_ROWS) wantRows = MIN_BAND_ROWS;
|
|
||||||
if (wantRows > h) wantRows = h;
|
|
||||||
|
|
||||||
size_t maxRowsByMem = MAX_BAND_BYTES / (size_t)bytesPerRow;
|
|
||||||
if (maxRowsByMem < 1) maxRowsByMem = 1;
|
|
||||||
if ((size_t)wantRows > maxRowsByMem) wantRows = (int)maxRowsByMem;
|
|
||||||
|
|
||||||
// A single decode block must fit inside the band, otherwise streaming would
|
|
||||||
// drop rows. This only fails for pathological upscales that could not be
|
|
||||||
// cached at all; fall back to the no-cache path.
|
|
||||||
if (wantRows < maxBlockDstRows) {
|
|
||||||
LOG_ERR("IMG", "Cache band too small (%d < %d rows) for %dx%d", wantRows, maxBlockDstRows, w, h);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bandRows = wantRows;
|
|
||||||
|
|
||||||
const size_t bufSize = (size_t)(bandRows + 1) * bytesPerRow; // +1 spare zero row
|
|
||||||
buffer = (uint8_t*)malloc(bufSize);
|
|
||||||
if (!buffer) {
|
|
||||||
LOG_ERR("IMG", "OOM cache band: %u bytes", (unsigned)bufSize);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
memset(buffer, 0, bufSize);
|
|
||||||
zeroRow = buffer + (size_t)bandRows * bytesPerRow;
|
|
||||||
|
|
||||||
if (!Storage.openFileForWrite("IMG", cachePath, file)) {
|
|
||||||
LOG_ERR("IMG", "Failed to open cache file for writing: %s", cachePath.c_str());
|
|
||||||
free(buffer);
|
|
||||||
buffer = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
cachePathStr = cachePath;
|
|
||||||
|
|
||||||
uint16_t w16 = (uint16_t)w;
|
|
||||||
uint16_t h16 = (uint16_t)h;
|
|
||||||
if (file.write(&w16, 2) != 2 || file.write(&h16, 2) != 2) {
|
|
||||||
LOG_ERR("IMG", "Failed to write cache header: %s", cachePath.c_str());
|
|
||||||
abort();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("IMG", "Cache stream started: %s (%dx%d, band %d rows)", cachePath.c_str(), w, h, bandRows);
|
|
||||||
ok = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush every output row below newTopRow (they are final in raster order) and
|
|
||||||
// reposition the band to start at newTopRow. Returns false if a write failed,
|
|
||||||
// in which case the caller must stop caching for the rest of the decode.
|
|
||||||
bool advanceTo(int newTopRow) {
|
|
||||||
if (!ok) return false;
|
|
||||||
if (newTopRow <= bandStart) return true;
|
|
||||||
if (newTopRow > height) newTopRow = height;
|
|
||||||
|
|
||||||
for (int r = bandStart; r < newTopRow; ++r) {
|
|
||||||
const int idx = r - bandStart;
|
|
||||||
const uint8_t* rowPtr = (idx < bandRows) ? (buffer + (size_t)idx * bytesPerRow) : zeroRow;
|
|
||||||
if (file.write(rowPtr, (size_t)bytesPerRow) != (size_t)bytesPerRow) {
|
|
||||||
LOG_ERR("IMG", "Cache write error at row %d", r);
|
|
||||||
ok = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flushedRows = newTopRow;
|
|
||||||
bandStart = newTopRow;
|
|
||||||
memset(buffer, 0, (size_t)bandRows * bytesPerRow); // fresh band (gaps stay black)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the final band and zero-fill any rows never covered (image clipped by
|
|
||||||
// the screen), then close the file.
|
|
||||||
bool finalize() {
|
|
||||||
if (!ok) {
|
|
||||||
abort();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int r = flushedRows; r < height; ++r) {
|
|
||||||
const int idx = r - bandStart;
|
|
||||||
const uint8_t* rowPtr = (idx >= 0 && idx < bandRows) ? (buffer + (size_t)idx * bytesPerRow) : zeroRow;
|
|
||||||
if (file.write(rowPtr, (size_t)bytesPerRow) != (size_t)bytesPerRow) {
|
|
||||||
LOG_ERR("IMG", "Cache write error at row %d", r);
|
|
||||||
abort();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
LOG_DBG("IMG", "Cache written: %s (%dx%d, %d bytes)", cachePathStr.c_str(), width, height,
|
|
||||||
4 + bytesPerRow * height);
|
|
||||||
ok = false; // file handed off; nothing left to clean up
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop a partial/failed cache so a later decode re-creates it cleanly.
|
|
||||||
void abort() {
|
|
||||||
if (file.isOpen()) file.close();
|
|
||||||
if (!cachePathStr.empty()) {
|
|
||||||
Storage.remove(cachePathStr.c_str());
|
|
||||||
}
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
~PixelCache() {
|
|
||||||
if (file.isOpen()) {
|
|
||||||
// The file is still open, so neither finalize() nor abort() ran, or a
|
|
||||||
// mid-stream write failed (advanceTo() cleared ok but left the file open).
|
|
||||||
// Drop the partial cache so we leave no corrupt file behind.
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
if (buffer) {
|
|
||||||
free(buffer);
|
|
||||||
buffer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,458 +0,0 @@
|
|||||||
#include "PngToFramebufferConverter.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Memory.h>
|
|
||||||
#include <PNGdec.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <memory>
|
|
||||||
#include <new>
|
|
||||||
|
|
||||||
#include "DirectPixelWriter.h"
|
|
||||||
#include "DitherUtils.h"
|
|
||||||
#include "PixelCache.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Context struct passed through PNGdec callbacks to avoid global mutable state.
|
|
||||||
// The draw callback receives this via pDraw->pUser (set by png.decode()).
|
|
||||||
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by pngOpen()).
|
|
||||||
struct PngContext {
|
|
||||||
GfxRenderer* renderer{nullptr};
|
|
||||||
const RenderConfig* config{nullptr};
|
|
||||||
int screenWidth{0};
|
|
||||||
int screenHeight{0};
|
|
||||||
|
|
||||||
// Scaling state
|
|
||||||
float scale{1.f};
|
|
||||||
int srcWidth{0};
|
|
||||||
int srcHeight{0};
|
|
||||||
int dstWidth{0};
|
|
||||||
int dstHeight{0};
|
|
||||||
int lastDstY{-1}; // Track last rendered destination Y to avoid duplicates
|
|
||||||
|
|
||||||
PixelCache cache;
|
|
||||||
bool caching{false};
|
|
||||||
|
|
||||||
uint8_t* grayLineBuffer{nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
// File I/O callbacks use pFile->fHandle to access the HalFile*,
|
|
||||||
// avoiding the need for global file state.
|
|
||||||
void* pngOpenWithHandle(const char* filename, int32_t* size) {
|
|
||||||
HalFile* f = new HalFile();
|
|
||||||
if (!Storage.openFileForRead("PNG", std::string(filename), *f)) {
|
|
||||||
delete f;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
*size = f->size();
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pngCloseWithHandle(void* handle) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(handle);
|
|
||||||
if (f) {
|
|
||||||
f->close();
|
|
||||||
delete f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t pngReadWithHandle(PNGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
|
||||||
if (!f) return 0;
|
|
||||||
return f->read(pBuf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t pngSeekWithHandle(PNGFILE* pFile, int32_t pos) {
|
|
||||||
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
|
||||||
if (!f) return -1;
|
|
||||||
return f->seek(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The PNG decoder (PNGdec) is ~42 KB due to internal zlib decompression buffers.
|
|
||||||
// We heap-allocate it on demand rather than using a static instance, so this memory
|
|
||||||
// is only consumed while actually decoding/querying PNG images. This is critical on
|
|
||||||
// the ESP32-C3 where total RAM is ~320 KB.
|
|
||||||
constexpr size_t PNG_DECODER_APPROX_SIZE = 44 * 1024; // ~42 KB + overhead
|
|
||||||
constexpr size_t MIN_FREE_HEAP_FOR_PNG = PNG_DECODER_APPROX_SIZE + 16 * 1024; // decoder + 16 KB headroom
|
|
||||||
|
|
||||||
// PNGdec keeps TWO scanlines in its internal ucPixels buffer (current + previous)
|
|
||||||
// and each scanline includes a leading filter byte.
|
|
||||||
// Required storage is therefore approximately: 2 * (pitch + 1) + alignment slack.
|
|
||||||
// If PNG_MAX_BUFFERED_PIXELS is smaller than this requirement for a given image,
|
|
||||||
// PNGdec can overrun its internal buffer before our draw callback executes.
|
|
||||||
int bytesPerPixelFromType(int pixelType) {
|
|
||||||
switch (pixelType) {
|
|
||||||
case PNG_PIXEL_TRUECOLOR:
|
|
||||||
return 3;
|
|
||||||
case PNG_PIXEL_GRAY_ALPHA:
|
|
||||||
return 2;
|
|
||||||
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
|
||||||
return 4;
|
|
||||||
case PNG_PIXEL_GRAYSCALE:
|
|
||||||
case PNG_PIXEL_INDEXED:
|
|
||||||
default:
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int packedRowBytes(int srcWidth, int bitsPerSample) { return (srcWidth * bitsPerSample + 7) / 8; }
|
|
||||||
|
|
||||||
int requiredPngInternalBufferBytes(int srcWidth, int pixelType, int bitsPerSample) {
|
|
||||||
// +1 filter byte per scanline, *2 for current+previous lines, +32 for alignment margin.
|
|
||||||
int pitch = srcWidth * bytesPerPixelFromType(pixelType);
|
|
||||||
if ((pixelType == PNG_PIXEL_GRAYSCALE || pixelType == PNG_PIXEL_INDEXED) && bitsPerSample < 8) {
|
|
||||||
pitch = packedRowBytes(srcWidth, bitsPerSample);
|
|
||||||
}
|
|
||||||
return ((pitch + 1) * 2) + 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSupportedBitDepth(int pixelType, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return true;
|
|
||||||
if (bitsPerSample != 1 && bitsPerSample != 2 && bitsPerSample != 4) return false;
|
|
||||||
return pixelType == PNG_PIXEL_GRAYSCALE || pixelType == PNG_PIXEL_INDEXED;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t readPackedSample(const uint8_t* pixels, int x, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return pixels[x];
|
|
||||||
|
|
||||||
const int bitOffset = x * bitsPerSample;
|
|
||||||
const int shift = 8 - bitsPerSample - (bitOffset & 7);
|
|
||||||
const uint8_t mask = (1U << bitsPerSample) - 1;
|
|
||||||
return (pixels[bitOffset >> 3] >> shift) & mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t expandSampleToByte(uint8_t sample, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return sample;
|
|
||||||
const uint8_t maxSample = (1U << bitsPerSample) - 1;
|
|
||||||
return static_cast<uint8_t>((sample * 255U) / maxSample);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert entire source line to grayscale with alpha blending to white background.
|
|
||||||
// Low-bit-depth grayscale/indexed scanlines are packed most-significant sample first.
|
|
||||||
// For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards.
|
|
||||||
// Processing the whole line at once improves cache locality and reduces per-pixel overhead.
|
|
||||||
void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int pixelType, int bitsPerSample,
|
|
||||||
uint8_t* palette, int hasAlpha) {
|
|
||||||
switch (pixelType) {
|
|
||||||
case PNG_PIXEL_GRAYSCALE:
|
|
||||||
if (bitsPerSample == 8) {
|
|
||||||
memcpy(grayLine, pPixels, width);
|
|
||||||
} else {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR:
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
const uint8_t* p = &pPixels[x * 3];
|
|
||||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PNG_PIXEL_INDEXED:
|
|
||||||
if (palette) {
|
|
||||||
if (hasAlpha) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
|
||||||
uint8_t* p = &palette[idx * 3];
|
|
||||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
|
||||||
uint8_t alpha = palette[768 + idx];
|
|
||||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
|
||||||
uint8_t* p = &palette[idx * 3];
|
|
||||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PNG_PIXEL_GRAY_ALPHA:
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
uint8_t gray = pPixels[x * 2];
|
|
||||||
uint8_t alpha = pPixels[x * 2 + 1];
|
|
||||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
const uint8_t* p = &pPixels[x * 4];
|
|
||||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
|
||||||
uint8_t alpha = p[3];
|
|
||||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
memset(grayLine, 128, width);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int pngDrawCallback(PNGDRAW* pDraw) {
|
|
||||||
PngContext* ctx = reinterpret_cast<PngContext*>(pDraw->pUser);
|
|
||||||
if (!ctx || !ctx->config || !ctx->renderer || !ctx->grayLineBuffer) return 0;
|
|
||||||
|
|
||||||
int srcY = pDraw->y;
|
|
||||||
int srcWidth = ctx->srcWidth;
|
|
||||||
|
|
||||||
// Map source rows with the exact output-height ratio. During downscaling,
|
|
||||||
// multiple source rows can select the same output row; during upscaling, one
|
|
||||||
// source row must be repeated across every output row in its range. Emitting
|
|
||||||
// only the first row of an upscale leaves zero-filled (black) gaps in the
|
|
||||||
// streamed pixel cache.
|
|
||||||
int firstDstY = (srcY * ctx->dstHeight) / ctx->srcHeight;
|
|
||||||
int endDstY = firstDstY + 1;
|
|
||||||
if (ctx->dstHeight > ctx->srcHeight) {
|
|
||||||
endDstY = ((srcY + 1) * ctx->dstHeight) / ctx->srcHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstDstY <= ctx->lastDstY) firstDstY = ctx->lastDstY + 1;
|
|
||||||
if (firstDstY >= endDstY || firstDstY >= ctx->dstHeight) return 1;
|
|
||||||
if (endDstY > ctx->dstHeight) endDstY = ctx->dstHeight;
|
|
||||||
|
|
||||||
// Convert entire source line to grayscale (improves cache locality)
|
|
||||||
convertLineToGray(pDraw->pPixels, ctx->grayLineBuffer, srcWidth, pDraw->iPixelType, pDraw->iBpp, pDraw->pPalette,
|
|
||||||
pDraw->iHasAlpha);
|
|
||||||
|
|
||||||
// Render scaled rows using Bresenham-style integer stepping (no floating-point division)
|
|
||||||
int dstWidth = ctx->dstWidth;
|
|
||||||
int outXBase = ctx->config->x;
|
|
||||||
int screenWidth = ctx->screenWidth;
|
|
||||||
bool useDithering = ctx->config->useDithering;
|
|
||||||
|
|
||||||
// Pre-compute orientation and render-mode state once per callback.
|
|
||||||
DirectPixelWriter pw;
|
|
||||||
pw.init(*ctx->renderer);
|
|
||||||
|
|
||||||
for (int dstY = firstDstY; dstY < endDstY; dstY++) {
|
|
||||||
ctx->lastDstY = dstY;
|
|
||||||
int outY = ctx->config->y + dstY;
|
|
||||||
if (outY >= ctx->screenHeight) continue;
|
|
||||||
|
|
||||||
pw.beginRow(outY);
|
|
||||||
|
|
||||||
// The cache streams to disk one row at a time. Flushing rows below this one
|
|
||||||
// (PNGdec delivers scanlines top to bottom) repositions the single-row band.
|
|
||||||
// A flush failure stops caching for the rest of the decode so we never write
|
|
||||||
// past the band buffer; finalize() then drops the partial file.
|
|
||||||
bool caching = ctx->caching;
|
|
||||||
DirectCacheWriter cw;
|
|
||||||
if (caching) {
|
|
||||||
if (!ctx->cache.advanceTo(dstY)) {
|
|
||||||
caching = false;
|
|
||||||
ctx->caching = false;
|
|
||||||
} else {
|
|
||||||
cw.init(ctx->cache.buffer, ctx->cache.bytesPerRow, ctx->cache.bandRows, ctx->cache.originX);
|
|
||||||
cw.beginRow(outY, ctx->config->y + ctx->cache.bandStart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int srcX = 0;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
for (int dstX = 0; dstX < dstWidth; dstX++) {
|
|
||||||
int outX = outXBase + dstX;
|
|
||||||
if (outX < screenWidth) {
|
|
||||||
uint8_t gray = ctx->grayLineBuffer[srcX];
|
|
||||||
|
|
||||||
uint8_t ditheredGray;
|
|
||||||
if (useDithering) {
|
|
||||||
ditheredGray = applyBayerDither4Level(gray, outX, outY);
|
|
||||||
} else {
|
|
||||||
ditheredGray = gray / 85;
|
|
||||||
if (ditheredGray > 3) ditheredGray = 3;
|
|
||||||
}
|
|
||||||
pw.writePixel(outX, ditheredGray);
|
|
||||||
if (caching) cw.writePixel(outX, ditheredGray);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bresenham-style stepping: advance srcX based on ratio srcWidth/dstWidth
|
|
||||||
error += srcWidth;
|
|
||||||
while (error >= dstWidth) {
|
|
||||||
error -= dstWidth;
|
|
||||||
srcX++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool PngToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) {
|
|
||||||
size_t freeHeap = ESP.getFreeHeap();
|
|
||||||
if (freeHeap < MIN_FREE_HEAP_FOR_PNG) {
|
|
||||||
LOG_ERR("PNG", "Not enough heap for PNG decoder (%u free, need %u)", freeHeap, MIN_FREE_HEAP_FOR_PNG);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PNG> png(new (std::nothrow) PNG());
|
|
||||||
if (!png) {
|
|
||||||
LOG_ERR("PNG", "Failed to allocate PNG decoder for dimensions");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc = png->open(imagePath.c_str(), pngOpenWithHandle, pngCloseWithHandle, pngReadWithHandle, pngSeekWithHandle,
|
|
||||||
nullptr);
|
|
||||||
const ScopedCleanup cleanup{[&png]() { png->close(); }};
|
|
||||||
|
|
||||||
if (rc != 0) {
|
|
||||||
LOG_ERR("PNG", "Failed to open PNG for dimensions: %d", rc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.width = png->getWidth();
|
|
||||||
out.height = png->getHeight();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer,
|
|
||||||
const RenderConfig& config) {
|
|
||||||
LOG_DBG("PNG", "Decoding PNG: %s", imagePath.c_str());
|
|
||||||
|
|
||||||
size_t freeHeap = ESP.getFreeHeap();
|
|
||||||
if (freeHeap < MIN_FREE_HEAP_FOR_PNG) {
|
|
||||||
LOG_ERR("PNG", "Not enough heap for PNG decoder (%u free, need %u)", freeHeap, MIN_FREE_HEAP_FOR_PNG);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heap-allocate PNG decoder (~42 KB) - freed at end of function
|
|
||||||
std::unique_ptr<PNG> png(new (std::nothrow) PNG());
|
|
||||||
if (!png) {
|
|
||||||
LOG_ERR("PNG", "Failed to allocate PNG decoder");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PngContext ctx;
|
|
||||||
ctx.renderer = &renderer;
|
|
||||||
ctx.config = &config;
|
|
||||||
ctx.screenWidth = renderer.getScreenWidth();
|
|
||||||
ctx.screenHeight = renderer.getScreenHeight();
|
|
||||||
|
|
||||||
int rc = png->open(imagePath.c_str(), pngOpenWithHandle, pngCloseWithHandle, pngReadWithHandle, pngSeekWithHandle,
|
|
||||||
pngDrawCallback);
|
|
||||||
const ScopedCleanup cleanup{[&png]() { png->close(); }};
|
|
||||||
if (rc != PNG_SUCCESS) {
|
|
||||||
LOG_ERR("PNG", "Failed to open PNG: %d", rc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateImageDimensions(png->getWidth(), png->getHeight(), "PNG")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate output dimensions
|
|
||||||
ctx.srcWidth = png->getWidth();
|
|
||||||
ctx.srcHeight = png->getHeight();
|
|
||||||
|
|
||||||
if (config.useExactDimensions && config.maxWidth > 0 && config.maxHeight > 0) {
|
|
||||||
// Use exact dimensions as specified (avoids rounding mismatches with pre-calculated sizes)
|
|
||||||
ctx.dstWidth = config.maxWidth;
|
|
||||||
ctx.dstHeight = config.maxHeight;
|
|
||||||
ctx.scale = (float)ctx.dstWidth / ctx.srcWidth;
|
|
||||||
} else {
|
|
||||||
// Calculate scale factor to fit within maxWidth/maxHeight
|
|
||||||
float scaleX = (float)config.maxWidth / ctx.srcWidth;
|
|
||||||
float scaleY = (float)config.maxHeight / ctx.srcHeight;
|
|
||||||
ctx.scale = (scaleX < scaleY) ? scaleX : scaleY;
|
|
||||||
if (ctx.scale > 1.0f) ctx.scale = 1.0f; // Don't upscale
|
|
||||||
|
|
||||||
ctx.dstWidth = (int)(ctx.srcWidth * ctx.scale);
|
|
||||||
ctx.dstHeight = (int)(ctx.srcHeight * ctx.scale);
|
|
||||||
}
|
|
||||||
ctx.lastDstY = -1; // Reset row tracking
|
|
||||||
|
|
||||||
const int pixelType = png->getPixelType();
|
|
||||||
const int bitsPerSample = png->getBpp();
|
|
||||||
LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth,
|
|
||||||
ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
|
|
||||||
|
|
||||||
const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType, bitsPerSample);
|
|
||||||
if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) {
|
|
||||||
LOG_ERR(
|
|
||||||
"PNG",
|
|
||||||
"PNG row buffer too small: need %d bytes for width=%d type=%d bpp=%d, configured PNG_MAX_BUFFERED_PIXELS=%d",
|
|
||||||
requiredInternal, ctx.srcWidth, pixelType, bitsPerSample, PNG_MAX_BUFFERED_PIXELS);
|
|
||||||
LOG_ERR("PNG", "Aborting decode to avoid PNGdec internal buffer overflow");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSupportedBitDepth(pixelType, bitsPerSample)) {
|
|
||||||
warnUnsupportedFeature(
|
|
||||||
"bit depth (" + std::to_string(bitsPerSample) + "bpp) for pixel type " + std::to_string(pixelType), imagePath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The converter expands each source row to 8-bit grayscale before dithering,
|
|
||||||
// so this scratch buffer is sized by source pixels even when PNGdec reads a
|
|
||||||
// packed 1/2/4-bit row internally.
|
|
||||||
constexpr size_t MAX_GRAY_LINE_BUFFER_BYTES = PNG_MAX_BUFFERED_PIXELS / 2;
|
|
||||||
const size_t grayBufSize = static_cast<size_t>(ctx.srcWidth);
|
|
||||||
if (grayBufSize > MAX_GRAY_LINE_BUFFER_BYTES) {
|
|
||||||
LOG_ERR("PNG", "Expanded gray row too wide: need %u bytes for width=%d, max=%u", static_cast<unsigned>(grayBufSize),
|
|
||||||
ctx.srcWidth, static_cast<unsigned>(MAX_GRAY_LINE_BUFFER_BYTES));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto grayLineBuffer = makeUniqueNoThrow<uint8_t[]>(grayBufSize);
|
|
||||||
if (!grayLineBuffer) {
|
|
||||||
LOG_ERR("PNG", "Failed to allocate gray line buffer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ctx.grayLineBuffer = grayLineBuffer.get();
|
|
||||||
|
|
||||||
// Stream the pixel cache to disk. PNGdec delivers source scanlines top to
|
|
||||||
// bottom and we emit at most one (downscaled) output row per callback, so the
|
|
||||||
// band only needs a single row. Streaming keeps the working set tiny, so
|
|
||||||
// unlike the old full-image buffer it neither competes with the ~44KB decoder
|
|
||||||
// nor forces larger images to skip caching - which previously meant a full
|
|
||||||
// re-decode on every one of an image page's ~14 render passes.
|
|
||||||
ctx.caching = !config.cachePath.empty();
|
|
||||||
if (ctx.caching) {
|
|
||||||
if (!ctx.cache.begin(config.cachePath, ctx.dstWidth, ctx.dstHeight, config.x, config.y, 1)) {
|
|
||||||
LOG_ERR("PNG", "Failed to start cache stream, continuing without caching");
|
|
||||||
ctx.caching = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long decodeStart = millis();
|
|
||||||
rc = png->decode(&ctx, 0);
|
|
||||||
unsigned long decodeTime = millis() - decodeStart;
|
|
||||||
|
|
||||||
ctx.grayLineBuffer = nullptr;
|
|
||||||
|
|
||||||
if (rc != PNG_SUCCESS) {
|
|
||||||
LOG_ERR("PNG", "Decode failed: %d", rc);
|
|
||||||
if (ctx.caching) ctx.cache.abort();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("PNG", "PNG decoding complete - render time: %lu ms", decodeTime);
|
|
||||||
|
|
||||||
// Finalize the streamed cache (caching may have been cleared on a flush error).
|
|
||||||
if (ctx.caching) {
|
|
||||||
ctx.cache.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PngToFramebufferConverter::supportsFormat(const std::string& extension) {
|
|
||||||
return FsHelpers::hasPngExtension(extension);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ImageToFramebufferDecoder.h"
|
|
||||||
|
|
||||||
class PngToFramebufferConverter final : public ImageToFramebufferDecoder {
|
|
||||||
public:
|
|
||||||
static bool getDimensionsStatic(const std::string& imagePath, ImageDimensions& out);
|
|
||||||
|
|
||||||
bool decodeToFramebuffer(const std::string& imagePath, GfxRenderer& renderer, const RenderConfig& config) override;
|
|
||||||
|
|
||||||
bool getDimensions(const std::string& imagePath, ImageDimensions& dims) const override {
|
|
||||||
return getDimensionsStatic(imagePath, dims);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool supportsFormat(const std::string& extension);
|
|
||||||
const char* getFormatName() const override { return "PNG"; }
|
|
||||||
};
|
|
||||||
@@ -1,951 +0,0 @@
|
|||||||
#include "CssParser.h"
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cctype>
|
|
||||||
#include <charconv>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Stack-allocated string buffer to avoid heap reallocations during parsing
|
|
||||||
// Provides string-like interface with fixed capacity
|
|
||||||
struct StackBuffer {
|
|
||||||
static constexpr size_t CAPACITY = 1024;
|
|
||||||
char data[CAPACITY];
|
|
||||||
size_t len = 0;
|
|
||||||
|
|
||||||
void push_back(char c) {
|
|
||||||
if (len < CAPACITY - 1) {
|
|
||||||
data[len++] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() { len = 0; }
|
|
||||||
bool empty() const { return len == 0; }
|
|
||||||
size_t size() const { return len; }
|
|
||||||
|
|
||||||
// Get string view of current content (zero-copy)
|
|
||||||
std::string_view view() const { return std::string_view(data, len); }
|
|
||||||
operator std::string_view() const noexcept { return view(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Buffer size for reading CSS files
|
|
||||||
constexpr size_t READ_BUFFER_SIZE = 512;
|
|
||||||
|
|
||||||
// Maximum number of CSS rules to store in the selector map
|
|
||||||
// Prevents unbounded memory growth from pathological CSS files
|
|
||||||
constexpr size_t MAX_RULES = 1500;
|
|
||||||
|
|
||||||
// Minimum free heap required to apply CSS during rendering
|
|
||||||
// If below this threshold, we skip CSS to avoid display artifacts.
|
|
||||||
constexpr size_t MIN_FREE_HEAP_FOR_CSS = 48 * 1024;
|
|
||||||
|
|
||||||
// Maximum length for a single selector string
|
|
||||||
// Prevents parsing of extremely long or malformed selectors
|
|
||||||
constexpr size_t MAX_SELECTOR_LENGTH = 256;
|
|
||||||
|
|
||||||
// Check if character is CSS whitespace
|
|
||||||
constexpr bool isCssWhitespace(const char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; }
|
|
||||||
|
|
||||||
constexpr std::string_view trimCssWhitespace(std::string_view s) {
|
|
||||||
while (!s.empty() && isCssWhitespace(s.front())) s.remove_prefix(1);
|
|
||||||
while (!s.empty() && isCssWhitespace(s.back())) s.remove_suffix(1);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr char asciiToLower(const char c) { return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + 32) : c; }
|
|
||||||
|
|
||||||
// Case-insensitive equality on ASCII. lowercaseKeyword MUST already be
|
|
||||||
// lowercase; CSS keywords are ASCII by spec so byte-wise tolower is safe.
|
|
||||||
constexpr bool iequalsAscii(std::string_view value, std::string_view lowercaseKeyword) {
|
|
||||||
return std::equal(value.begin(), value.end(), lowercaseKeyword.begin(), lowercaseKeyword.end(),
|
|
||||||
[](char a, char b) { return asciiToLower(a) == b; });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk s and invoke fn(token) for each non-empty run between delimiters.
|
|
||||||
// Tokens are boundary-trimmed and yielded as string_views into s; no
|
|
||||||
// allocation. Runs of consecutive delimiters coalesce — no empty tokens are
|
|
||||||
// emitted. `isDelimiter` is invoked once per character.
|
|
||||||
template <typename Pred, typename F>
|
|
||||||
void forEachDelimitedToken(std::string_view s, Pred isDelimiter, F&& fn) {
|
|
||||||
size_t start = 0;
|
|
||||||
for (size_t i = 0; i <= s.size(); ++i) {
|
|
||||||
if (i == s.size() || isDelimiter(s[i])) {
|
|
||||||
const std::string_view trimmed = trimCssWhitespace(s.substr(start, i - start));
|
|
||||||
if (!trimmed.empty()) {
|
|
||||||
fn(trimmed);
|
|
||||||
}
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FNV-1a per Fowler/Noll/Vo, sized to match size_t on the target. The firmware
|
|
||||||
// runs on a 32-bit core where size_t is 32 bits, so naively using the 64-bit
|
|
||||||
// constants would silently truncate FNV_PRIME to a non-prime and wreck hash
|
|
||||||
// distribution. The selection below picks the canonical 32- or 64-bit
|
|
||||||
// constants at compile time so the same source works in a 64-bit host
|
|
||||||
// simulator. `fnv1aMix` is the per-byte mix step; callers apply any
|
|
||||||
// byte-level transform (e.g. asciiToLower) first.
|
|
||||||
static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8, "FNV constants are only defined for 32- or 64-bit size_t");
|
|
||||||
constexpr size_t FNV_OFFSET_BASIS =
|
|
||||||
sizeof(size_t) == 8 ? static_cast<size_t>(14695981039346656037ULL) : static_cast<size_t>(2166136261U);
|
|
||||||
constexpr size_t FNV_PRIME =
|
|
||||||
sizeof(size_t) == 8 ? static_cast<size_t>(1099511628211ULL) : static_cast<size_t>(16777619U);
|
|
||||||
|
|
||||||
constexpr size_t fnv1aMix(size_t hash, unsigned char byte) { return (hash ^ byte) * FNV_PRIME; }
|
|
||||||
|
|
||||||
// Parse the entirety of s as a number into `out`. Accepts an optional leading
|
|
||||||
// '+' (which std::from_chars rejects by spec) so callers can pass CSS-style
|
|
||||||
// signed numbers without manual trimming. Returns false on empty input, a
|
|
||||||
// non-numeric suffix, or any from_chars error.
|
|
||||||
template <typename T>
|
|
||||||
bool tryParseNumber(std::string_view s, T& out) {
|
|
||||||
const char* begin = s.data();
|
|
||||||
const char* end = s.data() + s.size();
|
|
||||||
if (begin < end && *begin == '+') ++begin;
|
|
||||||
const auto r = std::from_chars(begin, end, out);
|
|
||||||
return r.ec == std::errc{} && r.ptr == end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect up to 4 whitespace-separated tokens for a CSS edge-value shorthand
|
|
||||||
// (margin, padding, and the border-* family). Returns the number of tokens
|
|
||||||
// written; extras are silently dropped. Callers apply the 1/2/3/4-value
|
|
||||||
// fallback rule using the returned count.
|
|
||||||
size_t collectEdgeValueTokens(std::string_view s, std::string_view (&out)[4]) {
|
|
||||||
size_t count = 0;
|
|
||||||
forEachDelimitedToken(s, isCssWhitespace, [&](std::string_view tok) {
|
|
||||||
if (count < 4) out[count++] = tok;
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view stripTrailingImportant(std::string_view value) {
|
|
||||||
constexpr std::string_view IMPORTANT = "!important";
|
|
||||||
|
|
||||||
while (!value.empty() && isCssWhitespace(value.back())) {
|
|
||||||
value.remove_suffix(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.size() < IMPORTANT.size()) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t suffixPos = value.size() - IMPORTANT.size();
|
|
||||||
if (!iequalsAscii(value.substr(suffixPos), IMPORTANT)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value.remove_suffix(IMPORTANT.size());
|
|
||||||
while (!value.empty() && isCssWhitespace(value.back())) {
|
|
||||||
value.remove_suffix(1);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
// Transparent case-insensitive hash/equal. Bodies live here (rather than
|
|
||||||
// inline in the header) so they can share the anonymous-namespace asciiToLower
|
|
||||||
// with the other ASCII helpers in this translation unit.
|
|
||||||
|
|
||||||
size_t CssParser::SvHash::operator()(std::string_view sv) const noexcept {
|
|
||||||
size_t h = FNV_OFFSET_BASIS;
|
|
||||||
for (char c : sv) h = fnv1aMix(h, asciiToLower(c));
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t CssParser::SvHash::operator()(const std::string& s) const noexcept { return operator()(std::string_view(s)); }
|
|
||||||
|
|
||||||
size_t CssParser::SvHash::operator()(CompositeKey k) const noexcept {
|
|
||||||
// Hash the case-folded concatenation of every piece without materializing
|
|
||||||
// it — the running hash continues across pieces as if they were one buffer.
|
|
||||||
size_t h = FNV_OFFSET_BASIS;
|
|
||||||
for (std::string_view piece : k.pieces) {
|
|
||||||
for (char c : piece) h = fnv1aMix(h, asciiToLower(c));
|
|
||||||
}
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(std::string_view a, std::string_view b) const noexcept {
|
|
||||||
if (a.size() != b.size()) return false;
|
|
||||||
for (size_t i = 0; i < a.size(); ++i) {
|
|
||||||
if (asciiToLower(a[i]) != asciiToLower(b[i])) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(const std::string& a, std::string_view b) const noexcept {
|
|
||||||
return operator()(std::string_view(a), b);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(std::string_view a, const std::string& b) const noexcept {
|
|
||||||
return operator()(a, std::string_view(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(const std::string& a, const std::string& b) const noexcept {
|
|
||||||
return operator()(std::string_view(a), std::string_view(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(CompositeKey k, std::string_view sv) const noexcept {
|
|
||||||
size_t total = 0;
|
|
||||||
for (std::string_view piece : k.pieces) total += piece.size();
|
|
||||||
if (total != sv.size()) return false;
|
|
||||||
size_t i = 0;
|
|
||||||
for (std::string_view piece : k.pieces) {
|
|
||||||
for (char c : piece) {
|
|
||||||
if (asciiToLower(c) != asciiToLower(sv[i++])) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::SvEqual::operator()(std::string_view sv, CompositeKey k) const noexcept { return operator()(k, sv); }
|
|
||||||
|
|
||||||
// Property value interpreters
|
|
||||||
|
|
||||||
CssTextAlign CssParser::interpretAlignment(std::string_view val) {
|
|
||||||
val = trimCssWhitespace(val);
|
|
||||||
|
|
||||||
if (iequalsAscii(val, "left") || iequalsAscii(val, "start")) return CssTextAlign::Left;
|
|
||||||
if (iequalsAscii(val, "right") || iequalsAscii(val, "end")) return CssTextAlign::Right;
|
|
||||||
if (iequalsAscii(val, "center")) return CssTextAlign::Center;
|
|
||||||
if (iequalsAscii(val, "justify")) return CssTextAlign::Justify;
|
|
||||||
|
|
||||||
return CssTextAlign::Left;
|
|
||||||
}
|
|
||||||
|
|
||||||
CssFontStyle CssParser::interpretFontStyle(std::string_view val) {
|
|
||||||
val = trimCssWhitespace(val);
|
|
||||||
|
|
||||||
if (iequalsAscii(val, "italic") || iequalsAscii(val, "oblique")) return CssFontStyle::Italic;
|
|
||||||
return CssFontStyle::Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
CssFontWeight CssParser::interpretFontWeight(std::string_view val) {
|
|
||||||
val = trimCssWhitespace(val);
|
|
||||||
|
|
||||||
// Named values
|
|
||||||
if (iequalsAscii(val, "bold") || iequalsAscii(val, "bolder")) return CssFontWeight::Bold;
|
|
||||||
if (iequalsAscii(val, "normal") || iequalsAscii(val, "lighter")) return CssFontWeight::Normal;
|
|
||||||
|
|
||||||
// Numeric values: 100-900
|
|
||||||
// CSS spec: 400 = normal, 700 = bold
|
|
||||||
// We use: 0-400 = normal, 700+ = bold, 500-600 = normal (conservative)
|
|
||||||
long numericWeight = 0;
|
|
||||||
if (tryParseNumber(val, numericWeight)) {
|
|
||||||
return numericWeight >= 700 ? CssFontWeight::Bold : CssFontWeight::Normal;
|
|
||||||
}
|
|
||||||
return CssFontWeight::Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
CssTextDecoration CssParser::interpretDecoration(std::string_view val) {
|
|
||||||
// text-decoration can have multiple space-separated values. Compare whole tokens
|
|
||||||
// so malformed values like "notunderline" do not accidentally enable a line.
|
|
||||||
CssTextDecoration result = CssTextDecoration::None;
|
|
||||||
bool explicitNone = false;
|
|
||||||
forEachDelimitedToken(val, isCssWhitespace, [&](const std::string_view token) {
|
|
||||||
if (iequalsAscii(token, "none")) {
|
|
||||||
explicitNone = true;
|
|
||||||
} else if (iequalsAscii(token, "underline")) {
|
|
||||||
result = result | CssTextDecoration::Underline;
|
|
||||||
} else if (iequalsAscii(token, "line-through")) {
|
|
||||||
result = result | CssTextDecoration::LineThrough;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return explicitNone ? CssTextDecoration::None : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
CssLength CssParser::interpretLength(std::string_view val) {
|
|
||||||
CssLength result;
|
|
||||||
tryInterpretLength(val, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::tryInterpretLength(std::string_view val, CssLength& out) {
|
|
||||||
val = trimCssWhitespace(val);
|
|
||||||
if (val.empty()) {
|
|
||||||
out = CssLength{};
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t unitStart = val.size();
|
|
||||||
for (size_t i = 0; i < val.size(); ++i) {
|
|
||||||
const char c = val[i];
|
|
||||||
if (!std::isdigit(c) && c != '.' && c != '-' && c != '+') {
|
|
||||||
unitStart = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float numericValue;
|
|
||||||
if (!tryParseNumber(val.substr(0, unitStart), numericValue)) {
|
|
||||||
out = CssLength{};
|
|
||||||
return false; // No number parsed (e.g. auto, inherit, initial)
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string_view unitPart = val.substr(unitStart);
|
|
||||||
auto unit = CssUnit::Pixels;
|
|
||||||
if (iequalsAscii(unitPart, "em")) {
|
|
||||||
unit = CssUnit::Em;
|
|
||||||
} else if (iequalsAscii(unitPart, "rem")) {
|
|
||||||
unit = CssUnit::Rem;
|
|
||||||
} else if (iequalsAscii(unitPart, "pt")) {
|
|
||||||
unit = CssUnit::Points;
|
|
||||||
} else if (unitPart == "%") {
|
|
||||||
unit = CssUnit::Percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
out = CssLength{numericValue, unit};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declaration parsing
|
|
||||||
|
|
||||||
void CssParser::parseDeclarationIntoStyle(std::string_view decl, CssStyle& style) {
|
|
||||||
const size_t colonPos = decl.find(':');
|
|
||||||
if (colonPos == std::string_view::npos || colonPos == 0) return;
|
|
||||||
|
|
||||||
const std::string_view name = trimCssWhitespace(decl.substr(0, colonPos));
|
|
||||||
const std::string_view value = trimCssWhitespace(decl.substr(colonPos + 1));
|
|
||||||
|
|
||||||
if (name.empty() || value.empty()) return;
|
|
||||||
|
|
||||||
if (iequalsAscii(name, "text-align")) {
|
|
||||||
style.textAlign = interpretAlignment(value);
|
|
||||||
style.defined.textAlign = 1;
|
|
||||||
} else if (iequalsAscii(name, "font-style")) {
|
|
||||||
style.fontStyle = interpretFontStyle(value);
|
|
||||||
style.defined.fontStyle = 1;
|
|
||||||
} else if (iequalsAscii(name, "font-weight")) {
|
|
||||||
style.fontWeight = interpretFontWeight(value);
|
|
||||||
style.defined.fontWeight = 1;
|
|
||||||
} else if (iequalsAscii(name, "text-decoration") || iequalsAscii(name, "text-decoration-line")) {
|
|
||||||
style.textDecoration = interpretDecoration(value);
|
|
||||||
style.defined.textDecoration = 1;
|
|
||||||
} else if (iequalsAscii(name, "text-indent")) {
|
|
||||||
style.textIndent = interpretLength(value);
|
|
||||||
style.defined.textIndent = 1;
|
|
||||||
} else if (iequalsAscii(name, "margin-top")) {
|
|
||||||
style.marginTop = interpretLength(value);
|
|
||||||
style.defined.marginTop = 1;
|
|
||||||
} else if (iequalsAscii(name, "margin-bottom")) {
|
|
||||||
style.marginBottom = interpretLength(value);
|
|
||||||
style.defined.marginBottom = 1;
|
|
||||||
} else if (iequalsAscii(name, "margin-left")) {
|
|
||||||
style.marginLeft = interpretLength(value);
|
|
||||||
style.defined.marginLeft = 1;
|
|
||||||
} else if (iequalsAscii(name, "margin-right")) {
|
|
||||||
style.marginRight = interpretLength(value);
|
|
||||||
style.defined.marginRight = 1;
|
|
||||||
} else if (iequalsAscii(name, "margin")) {
|
|
||||||
std::string_view margins[4];
|
|
||||||
const size_t count = collectEdgeValueTokens(value, margins);
|
|
||||||
if (count > 0) {
|
|
||||||
style.marginTop = interpretLength(margins[0]);
|
|
||||||
style.marginRight = count >= 2 ? interpretLength(margins[1]) : style.marginTop;
|
|
||||||
style.marginBottom = count >= 3 ? interpretLength(margins[2]) : style.marginTop;
|
|
||||||
style.marginLeft = count >= 4 ? interpretLength(margins[3]) : style.marginRight;
|
|
||||||
style.defined.marginTop = style.defined.marginRight = style.defined.marginBottom = style.defined.marginLeft = 1;
|
|
||||||
}
|
|
||||||
} else if (iequalsAscii(name, "padding-top")) {
|
|
||||||
style.paddingTop = interpretLength(value);
|
|
||||||
style.defined.paddingTop = 1;
|
|
||||||
} else if (iequalsAscii(name, "padding-bottom")) {
|
|
||||||
style.paddingBottom = interpretLength(value);
|
|
||||||
style.defined.paddingBottom = 1;
|
|
||||||
} else if (iequalsAscii(name, "padding-left")) {
|
|
||||||
style.paddingLeft = interpretLength(value);
|
|
||||||
style.defined.paddingLeft = 1;
|
|
||||||
} else if (iequalsAscii(name, "padding-right")) {
|
|
||||||
style.paddingRight = interpretLength(value);
|
|
||||||
style.defined.paddingRight = 1;
|
|
||||||
} else if (iequalsAscii(name, "padding")) {
|
|
||||||
std::string_view paddings[4];
|
|
||||||
const size_t count = collectEdgeValueTokens(value, paddings);
|
|
||||||
if (count > 0) {
|
|
||||||
style.paddingTop = interpretLength(paddings[0]);
|
|
||||||
style.paddingRight = count >= 2 ? interpretLength(paddings[1]) : style.paddingTop;
|
|
||||||
style.paddingBottom = count >= 3 ? interpretLength(paddings[2]) : style.paddingTop;
|
|
||||||
style.paddingLeft = count >= 4 ? interpretLength(paddings[3]) : style.paddingRight;
|
|
||||||
style.defined.paddingTop = style.defined.paddingRight = style.defined.paddingBottom = style.defined.paddingLeft =
|
|
||||||
1;
|
|
||||||
}
|
|
||||||
} else if (iequalsAscii(name, "height")) {
|
|
||||||
CssLength len;
|
|
||||||
if (tryInterpretLength(value, len)) {
|
|
||||||
style.imageHeight = len;
|
|
||||||
style.defined.imageHeight = 1;
|
|
||||||
}
|
|
||||||
} else if (iequalsAscii(name, "width")) {
|
|
||||||
CssLength len;
|
|
||||||
if (tryInterpretLength(value, len)) {
|
|
||||||
style.imageWidth = len;
|
|
||||||
style.defined.imageWidth = 1;
|
|
||||||
}
|
|
||||||
} else if (iequalsAscii(name, "display")) {
|
|
||||||
const std::string_view displayValue = stripTrailingImportant(value);
|
|
||||||
style.display = iequalsAscii(displayValue, "none") ? CssDisplay::None : CssDisplay::Block;
|
|
||||||
style.defined.display = 1;
|
|
||||||
} else if (iequalsAscii(name, "direction")) {
|
|
||||||
const std::string_view directionValue = stripTrailingImportant(value);
|
|
||||||
if (iequalsAscii(directionValue, "rtl")) {
|
|
||||||
style.direction = CssTextDirection::Rtl;
|
|
||||||
style.defined.direction = 1;
|
|
||||||
} else if (iequalsAscii(directionValue, "ltr")) {
|
|
||||||
style.direction = CssTextDirection::Ltr;
|
|
||||||
style.defined.direction = 1;
|
|
||||||
}
|
|
||||||
} else if (iequalsAscii(name, "vertical-align")) {
|
|
||||||
if (iequalsAscii(value, "super")) {
|
|
||||||
style.verticalAlign = CssVerticalAlign::Super;
|
|
||||||
style.defined.verticalAlign = 1;
|
|
||||||
} else if (iequalsAscii(value, "sub")) {
|
|
||||||
style.verticalAlign = CssVerticalAlign::Sub;
|
|
||||||
style.defined.verticalAlign = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CssStyle CssParser::parseDeclarations(std::string_view declBlock) {
|
|
||||||
CssStyle style;
|
|
||||||
|
|
||||||
size_t start = 0;
|
|
||||||
for (size_t i = 0; i <= declBlock.size(); ++i) {
|
|
||||||
if (i == declBlock.size() || declBlock[i] == ';') {
|
|
||||||
if (i > start) {
|
|
||||||
parseDeclarationIntoStyle(declBlock.substr(start, i - start), style);
|
|
||||||
}
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule processing
|
|
||||||
|
|
||||||
void CssParser::processRuleBlockWithStyle(std::string_view selectorGroup, const CssStyle& style) {
|
|
||||||
// Check if we've reached the rule limit before processing
|
|
||||||
if (rulesBySelector_.size() >= MAX_RULES) {
|
|
||||||
LOG_DBG("CSS", "Reached max rules limit (%zu), stopping CSS parsing", MAX_RULES);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk comma-separated selectors in place — no vector allocation. Selectors
|
|
||||||
// with unsupported syntax (combinators, attributes, pseudo, etc.) are skipped
|
|
||||||
// silently; the only heap allocation per kept selector is the std::string
|
|
||||||
// map key, which is unavoidable since the map owns its keys.
|
|
||||||
bool limitReached = false;
|
|
||||||
forEachDelimitedToken(
|
|
||||||
selectorGroup, [](char c) { return c == ','; },
|
|
||||||
[&](std::string_view sel) {
|
|
||||||
if (limitReached) return;
|
|
||||||
|
|
||||||
if (sel.size() > MAX_SELECTOR_LENGTH) {
|
|
||||||
LOG_DBG("CSS", "Selector too long (%zu > %zu), skipping", sel.size(), MAX_SELECTOR_LENGTH);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support richer CSS selector syntax in the future. For now we only
|
|
||||||
// handle `tag`, `.class`, or `tag.class`. Reject anything containing a
|
|
||||||
// character that introduces unsupported syntax:
|
|
||||||
// '+' adjacent sibling combinator
|
|
||||||
// '>' child combinator
|
|
||||||
// '[' attribute selector
|
|
||||||
// ':' pseudo class/element
|
|
||||||
// '#' ID selector
|
|
||||||
// '~' general sibling combinator
|
|
||||||
// '*' wildcard
|
|
||||||
// ' ' descendant combinator
|
|
||||||
// Single-pass scan via find_first_of instead of eight sequential find() calls.
|
|
||||||
constexpr std::string_view kUnsupportedSelectorChars = "+>[:#~* ";
|
|
||||||
if (sel.find_first_of(kUnsupportedSelectorChars) != std::string_view::npos) return;
|
|
||||||
|
|
||||||
// Skip if this would exceed the rule limit
|
|
||||||
if (rulesBySelector_.size() >= MAX_RULES) {
|
|
||||||
LOG_DBG("CSS", "Reached max rules limit, stopping selector processing");
|
|
||||||
limitReached = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store or merge with existing. Hash/equal are case-insensitive, so two
|
|
||||||
// selectors that differ only in ASCII case collide on insert and merge.
|
|
||||||
auto it = rulesBySelector_.find(sel);
|
|
||||||
if (it != rulesBySelector_.end()) {
|
|
||||||
it->second.applyOver(style);
|
|
||||||
} else {
|
|
||||||
rulesBySelector_.emplace(std::string(sel), style);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main parsing entry point
|
|
||||||
|
|
||||||
bool CssParser::loadFromStream(HalFile& source) {
|
|
||||||
if (!source) {
|
|
||||||
LOG_ERR("CSS", "Cannot read from invalid file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t totalRead = 0;
|
|
||||||
|
|
||||||
// Use stack-allocated buffers for parsing to avoid heap reallocations
|
|
||||||
StackBuffer selector;
|
|
||||||
StackBuffer declBuffer;
|
|
||||||
|
|
||||||
bool inComment = false;
|
|
||||||
bool maybeSlash = false;
|
|
||||||
bool prevStar = false;
|
|
||||||
|
|
||||||
bool inAtRule = false;
|
|
||||||
int atDepth = 0;
|
|
||||||
|
|
||||||
int bodyDepth = 0;
|
|
||||||
bool skippingRule = false;
|
|
||||||
CssStyle currentStyle;
|
|
||||||
|
|
||||||
auto handleChar = [&](const char c) {
|
|
||||||
if (inAtRule) {
|
|
||||||
if (c == '{') {
|
|
||||||
++atDepth;
|
|
||||||
} else if (c == '}') {
|
|
||||||
if (atDepth > 0) --atDepth;
|
|
||||||
if (atDepth == 0) inAtRule = false;
|
|
||||||
} else if (c == ';' && atDepth == 0) {
|
|
||||||
inAtRule = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bodyDepth == 0) {
|
|
||||||
if (selector.empty() && isCssWhitespace(c)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c == '@' && selector.empty()) {
|
|
||||||
inAtRule = true;
|
|
||||||
atDepth = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c == '{') {
|
|
||||||
bodyDepth = 1;
|
|
||||||
currentStyle = CssStyle{};
|
|
||||||
declBuffer.clear();
|
|
||||||
if (selector.size() > MAX_SELECTOR_LENGTH * 4) {
|
|
||||||
skippingRule = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selector.push_back(c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bodyDepth > 0
|
|
||||||
if (c == '{') {
|
|
||||||
++bodyDepth;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c == '}') {
|
|
||||||
--bodyDepth;
|
|
||||||
if (bodyDepth == 0) {
|
|
||||||
if (!skippingRule && !declBuffer.empty()) {
|
|
||||||
parseDeclarationIntoStyle(declBuffer, currentStyle);
|
|
||||||
}
|
|
||||||
if (!skippingRule) {
|
|
||||||
processRuleBlockWithStyle(selector, currentStyle);
|
|
||||||
}
|
|
||||||
selector.clear();
|
|
||||||
declBuffer.clear();
|
|
||||||
skippingRule = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (bodyDepth > 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!skippingRule) {
|
|
||||||
if (c == ';') {
|
|
||||||
if (!declBuffer.empty()) {
|
|
||||||
parseDeclarationIntoStyle(declBuffer, currentStyle);
|
|
||||||
declBuffer.clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
declBuffer.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
char buffer[READ_BUFFER_SIZE];
|
|
||||||
while (source.available()) {
|
|
||||||
int bytesRead = source.read(buffer, sizeof(buffer));
|
|
||||||
if (bytesRead <= 0) break;
|
|
||||||
|
|
||||||
totalRead += static_cast<size_t>(bytesRead);
|
|
||||||
|
|
||||||
for (int i = 0; i < bytesRead; ++i) {
|
|
||||||
const char c = buffer[i];
|
|
||||||
|
|
||||||
if (inComment) {
|
|
||||||
if (prevStar && c == '/') {
|
|
||||||
inComment = false;
|
|
||||||
prevStar = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
prevStar = c == '*';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybeSlash) {
|
|
||||||
if (c == '*') {
|
|
||||||
inComment = true;
|
|
||||||
maybeSlash = false;
|
|
||||||
prevStar = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
handleChar('/');
|
|
||||||
maybeSlash = false;
|
|
||||||
// fall through to process current char
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '/') {
|
|
||||||
maybeSlash = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChar(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybeSlash) {
|
|
||||||
handleChar('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("CSS", "Parsed %zu rules from %zu bytes", rulesBySelector_.size(), totalRead);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style resolution
|
|
||||||
|
|
||||||
CssStyle CssParser::resolveStyle(std::string_view tagName, std::string_view classAttr) const {
|
|
||||||
static bool lowHeapWarningLogged = false;
|
|
||||||
if (ESP.getFreeHeap() < MIN_FREE_HEAP_FOR_CSS) {
|
|
||||||
if (!lowHeapWarningLogged) {
|
|
||||||
lowHeapWarningLogged = true;
|
|
||||||
LOG_DBG("CSS", "Warning: low heap (%u bytes) below MIN_FREE_HEAP_FOR_CSS (%u), returning empty style",
|
|
||||||
ESP.getFreeHeap(), static_cast<unsigned>(MIN_FREE_HEAP_FOR_CSS));
|
|
||||||
}
|
|
||||||
return CssStyle{};
|
|
||||||
}
|
|
||||||
|
|
||||||
CssStyle result;
|
|
||||||
|
|
||||||
// 1. Apply element-level style (lowest priority). The map's hash/equal are
|
|
||||||
// case-insensitive, so the raw tagName view can be used as the lookup key.
|
|
||||||
if (auto it = rulesBySelector_.find(tagName); it != rulesBySelector_.end()) {
|
|
||||||
result.applyOver(it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classAttr.empty()) return result;
|
|
||||||
|
|
||||||
// TODO: Support combinations of classes (e.g. style on .class1.class2)
|
|
||||||
// 2. Apply class styles (medium priority). The transparent hash/equal accept
|
|
||||||
// a CompositeKey, so we never materialize the concatenation.
|
|
||||||
forEachDelimitedToken(classAttr, isCssWhitespace, [&](std::string_view cls) {
|
|
||||||
if (auto it = rulesBySelector_.find(CompositeKey{".", cls}); it != rulesBySelector_.end()) {
|
|
||||||
result.applyOver(it->second);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Support combinations of classes (e.g. style on p.class1.class2)
|
|
||||||
// 3. Apply element.class styles (higher priority).
|
|
||||||
forEachDelimitedToken(classAttr, isCssWhitespace, [&](std::string_view cls) {
|
|
||||||
if (auto it = rulesBySelector_.find(CompositeKey{tagName, ".", cls}); it != rulesBySelector_.end()) {
|
|
||||||
result.applyOver(it->second);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline style parsing (static - doesn't need rule database)
|
|
||||||
|
|
||||||
CssStyle CssParser::parseInlineStyle(std::string_view styleValue) { return parseDeclarations(styleValue); }
|
|
||||||
|
|
||||||
// Cache serialization
|
|
||||||
|
|
||||||
// Cache file name (version is CssParser::CSS_CACHE_VERSION)
|
|
||||||
constexpr char rulesCache[] = "/css_rules.cache";
|
|
||||||
|
|
||||||
bool CssParser::hasCache() const { return Storage.exists((cachePath + rulesCache).c_str()); }
|
|
||||||
|
|
||||||
void CssParser::deleteCache() const {
|
|
||||||
if (hasCache()) Storage.remove((cachePath + rulesCache).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::saveToCache() const {
|
|
||||||
if (cachePath.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile file;
|
|
||||||
if (!Storage.openFileForWrite("CSS", cachePath + rulesCache, file)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write version
|
|
||||||
file.write(CssParser::CSS_CACHE_VERSION);
|
|
||||||
|
|
||||||
// Write rule count
|
|
||||||
const auto ruleCount = static_cast<uint16_t>(rulesBySelector_.size());
|
|
||||||
file.write(reinterpret_cast<const uint8_t*>(&ruleCount), sizeof(ruleCount));
|
|
||||||
|
|
||||||
// Write each rule: selector string + CssStyle fields
|
|
||||||
for (const auto& pair : rulesBySelector_) {
|
|
||||||
// Write selector string (length-prefixed)
|
|
||||||
const auto selectorLen = static_cast<uint16_t>(pair.first.size());
|
|
||||||
file.write(reinterpret_cast<const uint8_t*>(&selectorLen), sizeof(selectorLen));
|
|
||||||
file.write(reinterpret_cast<const uint8_t*>(pair.first.data()), selectorLen);
|
|
||||||
|
|
||||||
// Write CssStyle fields (all are POD types)
|
|
||||||
const CssStyle& style = pair.second;
|
|
||||||
file.write(static_cast<uint8_t>(style.textAlign));
|
|
||||||
file.write(static_cast<uint8_t>(style.fontStyle));
|
|
||||||
file.write(static_cast<uint8_t>(style.fontWeight));
|
|
||||||
file.write(static_cast<uint8_t>(style.textDecoration));
|
|
||||||
file.write(static_cast<uint8_t>(style.direction));
|
|
||||||
|
|
||||||
// Write CssLength fields (value + unit)
|
|
||||||
auto writeLength = [&file](const CssLength& len) {
|
|
||||||
file.write(reinterpret_cast<const uint8_t*>(&len.value), sizeof(len.value));
|
|
||||||
file.write(static_cast<uint8_t>(len.unit));
|
|
||||||
};
|
|
||||||
|
|
||||||
writeLength(style.textIndent);
|
|
||||||
writeLength(style.marginTop);
|
|
||||||
writeLength(style.marginBottom);
|
|
||||||
writeLength(style.marginLeft);
|
|
||||||
writeLength(style.marginRight);
|
|
||||||
writeLength(style.paddingTop);
|
|
||||||
writeLength(style.paddingBottom);
|
|
||||||
writeLength(style.paddingLeft);
|
|
||||||
writeLength(style.paddingRight);
|
|
||||||
writeLength(style.imageHeight);
|
|
||||||
writeLength(style.imageWidth);
|
|
||||||
file.write(static_cast<uint8_t>(style.display));
|
|
||||||
file.write(static_cast<uint8_t>(style.verticalAlign));
|
|
||||||
|
|
||||||
// Write defined flags as uint32_t
|
|
||||||
uint32_t definedBits = 0;
|
|
||||||
if (style.defined.textAlign) definedBits |= 1 << 0;
|
|
||||||
if (style.defined.fontStyle) definedBits |= 1 << 1;
|
|
||||||
if (style.defined.fontWeight) definedBits |= 1 << 2;
|
|
||||||
if (style.defined.textDecoration) definedBits |= 1 << 3;
|
|
||||||
if (style.defined.textIndent) definedBits |= 1 << 4;
|
|
||||||
if (style.defined.marginTop) definedBits |= 1 << 5;
|
|
||||||
if (style.defined.marginBottom) definedBits |= 1 << 6;
|
|
||||||
if (style.defined.marginLeft) definedBits |= 1 << 7;
|
|
||||||
if (style.defined.marginRight) definedBits |= 1 << 8;
|
|
||||||
if (style.defined.paddingTop) definedBits |= 1 << 9;
|
|
||||||
if (style.defined.paddingBottom) definedBits |= 1 << 10;
|
|
||||||
if (style.defined.paddingLeft) definedBits |= 1 << 11;
|
|
||||||
if (style.defined.paddingRight) definedBits |= 1 << 12;
|
|
||||||
if (style.defined.imageHeight) definedBits |= 1 << 13;
|
|
||||||
if (style.defined.imageWidth) definedBits |= 1 << 14;
|
|
||||||
if (style.defined.display) definedBits |= 1 << 15;
|
|
||||||
if (style.defined.direction) definedBits |= 1 << 16;
|
|
||||||
if (style.defined.verticalAlign) definedBits |= 1 << 17;
|
|
||||||
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("CSS", "Saved %u rules to cache", ruleCount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CssParser::loadFromCache() {
|
|
||||||
if (cachePath.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HalFile file;
|
|
||||||
if (!Storage.openFileForRead("CSS", cachePath + rulesCache, file)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear existing rules
|
|
||||||
clear();
|
|
||||||
|
|
||||||
// Read and verify version
|
|
||||||
uint8_t version = 0;
|
|
||||||
if (file.read(&version, 1) != 1 || version != CssParser::CSS_CACHE_VERSION) {
|
|
||||||
LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u), removing stale cache for rebuild", version,
|
|
||||||
CssParser::CSS_CACHE_VERSION);
|
|
||||||
// Explicitly close() file before calling Storage.remove()
|
|
||||||
file.close();
|
|
||||||
Storage.remove((cachePath + rulesCache).c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read rule count
|
|
||||||
uint16_t ruleCount = 0;
|
|
||||||
if (file.read(&ruleCount, sizeof(ruleCount)) != sizeof(ruleCount)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ruleCount > MAX_RULES) {
|
|
||||||
LOG_DBG("CSS", "Invalid cache rule count (%u > %zu)", ruleCount, MAX_RULES);
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size the bucket array up front to avoid incremental rehashes while loading rules.
|
|
||||||
rulesBySelector_.reserve(ruleCount);
|
|
||||||
|
|
||||||
auto hasRemainingBytes = [&file](const size_t neededBytes) -> bool {
|
|
||||||
return static_cast<size_t>(file.available()) >= neededBytes;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr size_t CSS_LENGTH_FIELD_COUNT = 11;
|
|
||||||
constexpr size_t CSS_LENGTH_BYTES = sizeof(float) + sizeof(uint8_t);
|
|
||||||
constexpr size_t CSS_FIXED_STYLE_BYTES =
|
|
||||||
5 * sizeof(uint8_t) + (CSS_LENGTH_FIELD_COUNT * CSS_LENGTH_BYTES) + sizeof(uint8_t) + sizeof(uint32_t);
|
|
||||||
|
|
||||||
// Read each rule
|
|
||||||
for (uint16_t i = 0; i < ruleCount; ++i) {
|
|
||||||
// Read selector string
|
|
||||||
uint16_t selectorLen = 0;
|
|
||||||
if (!hasRemainingBytes(sizeof(selectorLen))) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (file.read(&selectorLen, sizeof(selectorLen)) != sizeof(selectorLen)) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectorLen == 0 || selectorLen > MAX_SELECTOR_LENGTH || !hasRemainingBytes(selectorLen)) {
|
|
||||||
LOG_DBG("CSS", "Invalid selector length in cache: %u", selectorLen);
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string selector;
|
|
||||||
selector.resize(selectorLen);
|
|
||||||
if (file.read(&selector[0], selectorLen) != selectorLen) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasRemainingBytes(CSS_FIXED_STYLE_BYTES)) {
|
|
||||||
LOG_DBG("CSS", "Truncated CSS cache while reading style payload");
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read CssStyle fields
|
|
||||||
CssStyle style;
|
|
||||||
uint8_t enumVal;
|
|
||||||
|
|
||||||
if (file.read(&enumVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.textAlign = static_cast<CssTextAlign>(enumVal);
|
|
||||||
|
|
||||||
if (file.read(&enumVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.fontStyle = static_cast<CssFontStyle>(enumVal);
|
|
||||||
|
|
||||||
if (file.read(&enumVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.fontWeight = static_cast<CssFontWeight>(enumVal);
|
|
||||||
|
|
||||||
if (file.read(&enumVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.textDecoration = static_cast<CssTextDecoration>(enumVal & CSS_TEXT_DECORATION_MASK);
|
|
||||||
|
|
||||||
if (file.read(&enumVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.direction = static_cast<CssTextDirection>(enumVal);
|
|
||||||
|
|
||||||
// Read CssLength fields
|
|
||||||
auto readLength = [&file](CssLength& len) -> bool {
|
|
||||||
if (file.read(&len.value, sizeof(len.value)) != sizeof(len.value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint8_t unitVal;
|
|
||||||
if (file.read(&unitVal, 1) != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
len.unit = static_cast<CssUnit>(unitVal);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!readLength(style.textIndent) || !readLength(style.marginTop) || !readLength(style.marginBottom) ||
|
|
||||||
!readLength(style.marginLeft) || !readLength(style.marginRight) || !readLength(style.paddingTop) ||
|
|
||||||
!readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight) ||
|
|
||||||
!readLength(style.imageHeight) || !readLength(style.imageWidth)) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read display value
|
|
||||||
uint8_t displayVal;
|
|
||||||
if (file.read(&displayVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.display = static_cast<CssDisplay>(displayVal);
|
|
||||||
|
|
||||||
// Read verticalAlign value
|
|
||||||
uint8_t verticalAlignVal;
|
|
||||||
if (file.read(&verticalAlignVal, 1) != 1) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.verticalAlign = static_cast<CssVerticalAlign>(verticalAlignVal);
|
|
||||||
|
|
||||||
// Read defined flags
|
|
||||||
uint32_t definedBits = 0;
|
|
||||||
if (file.read(&definedBits, sizeof(definedBits)) != sizeof(definedBits)) {
|
|
||||||
rulesBySelector_.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
style.defined.textAlign = (definedBits & 1 << 0) != 0;
|
|
||||||
style.defined.fontStyle = (definedBits & 1 << 1) != 0;
|
|
||||||
style.defined.fontWeight = (definedBits & 1 << 2) != 0;
|
|
||||||
style.defined.textDecoration = (definedBits & 1 << 3) != 0;
|
|
||||||
style.defined.textIndent = (definedBits & 1 << 4) != 0;
|
|
||||||
style.defined.marginTop = (definedBits & 1 << 5) != 0;
|
|
||||||
style.defined.marginBottom = (definedBits & 1 << 6) != 0;
|
|
||||||
style.defined.marginLeft = (definedBits & 1 << 7) != 0;
|
|
||||||
style.defined.marginRight = (definedBits & 1 << 8) != 0;
|
|
||||||
style.defined.paddingTop = (definedBits & 1 << 9) != 0;
|
|
||||||
style.defined.paddingBottom = (definedBits & 1 << 10) != 0;
|
|
||||||
style.defined.paddingLeft = (definedBits & 1 << 11) != 0;
|
|
||||||
style.defined.paddingRight = (definedBits & 1 << 12) != 0;
|
|
||||||
style.defined.imageHeight = (definedBits & 1 << 13) != 0;
|
|
||||||
style.defined.imageWidth = (definedBits & 1 << 14) != 0;
|
|
||||||
style.defined.display = (definedBits & 1 << 15) != 0;
|
|
||||||
style.defined.direction = (definedBits & 1 << 16) != 0;
|
|
||||||
style.defined.verticalAlign = (definedBits & 1 << 17) != 0;
|
|
||||||
|
|
||||||
rulesBySelector_[selector] = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("CSS", "Loaded %u rules from cache", ruleCount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <HalStorage.h>
|
|
||||||
|
|
||||||
#include <initializer_list>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "CssStyle.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight CSS parser for EPUB stylesheets
|
|
||||||
*
|
|
||||||
* Parses CSS files and extracts styling information relevant for e-ink display.
|
|
||||||
* Uses a two-phase approach: first tokenizes the CSS content, then builds
|
|
||||||
* a rule database that can be queried during HTML parsing.
|
|
||||||
*
|
|
||||||
* Supported selectors:
|
|
||||||
* - Element selectors: p, div, h1, etc.
|
|
||||||
* - Class selectors: .classname
|
|
||||||
* - Combined: element.classname
|
|
||||||
* - Grouped: selector1, selector2 { }
|
|
||||||
*
|
|
||||||
* Not supported (silently ignored):
|
|
||||||
* - Descendant/child selectors
|
|
||||||
* - Pseudo-classes and pseudo-elements
|
|
||||||
* - Media queries (content is skipped)
|
|
||||||
* - @import, @font-face, etc.
|
|
||||||
*/
|
|
||||||
class CssParser {
|
|
||||||
public:
|
|
||||||
// Bump when CSS cache format or rules change; section caches are invalidated when this changes
|
|
||||||
static constexpr uint8_t CSS_CACHE_VERSION = 7;
|
|
||||||
|
|
||||||
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
|
|
||||||
~CssParser() = default;
|
|
||||||
|
|
||||||
// Non-copyable
|
|
||||||
CssParser(const CssParser&) = delete;
|
|
||||||
CssParser& operator=(const CssParser&) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load and parse CSS from a file stream.
|
|
||||||
* Can be called multiple times to accumulate rules from multiple stylesheets.
|
|
||||||
* @param source Open file handle to read from
|
|
||||||
* @return true if parsing completed (even if no rules found)
|
|
||||||
*/
|
|
||||||
bool loadFromStream(HalFile& source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up the style for an HTML element, considering tag name and class attributes.
|
|
||||||
* Applies CSS cascade: element style < class style < element.class style
|
|
||||||
*
|
|
||||||
* @param tagName The HTML element name (e.g., "p", "div")
|
|
||||||
* @param classAttr The class attribute value (may contain multiple space-separated classes)
|
|
||||||
* @return Combined style with all applicable rules merged
|
|
||||||
*/
|
|
||||||
[[nodiscard]] CssStyle resolveStyle(std::string_view tagName, std::string_view classAttr) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse an inline style attribute string.
|
|
||||||
* @param styleValue The value of a style="" attribute
|
|
||||||
* @return Parsed style properties
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static CssStyle parseInlineStyle(std::string_view styleValue);
|
|
||||||
|
|
||||||
// MEMFIX-PORT: stylesheet resident-bytes audit accessor; portable
|
|
||||||
// Approximate resident heap of the parsed stylesheet, for the audit log.
|
|
||||||
// unordered_map cost model: bucket array + one node per rule (libstdc++ node
|
|
||||||
// overhead ~= 2 pointers + hash) + key string capacity when it exceeds SSO.
|
|
||||||
size_t residentBytes() const {
|
|
||||||
size_t total = rulesBySelector_.bucket_count() * sizeof(void*);
|
|
||||||
for (const auto& kv : rulesBySelector_) {
|
|
||||||
total += sizeof(void*) * 2 + sizeof(size_t); // node overhead
|
|
||||||
total += sizeof(kv);
|
|
||||||
if (kv.first.capacity() > 15) total += kv.first.capacity(); // beyond SSO
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if any rules have been loaded
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool empty() const { return rulesBySelector_.empty(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get count of loaded rule sets
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t ruleCount() const { return rulesBySelector_.size(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all loaded rules
|
|
||||||
*/
|
|
||||||
void clear() { rulesBySelector_.clear(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if CSS rules cache file exists
|
|
||||||
*/
|
|
||||||
bool hasCache() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete CSS rules cache file exists
|
|
||||||
*/
|
|
||||||
void deleteCache() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save parsed CSS rules to a cache file.
|
|
||||||
* @return true if cache was written successfully
|
|
||||||
*/
|
|
||||||
bool saveToCache() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load CSS rules from a cache file.
|
|
||||||
* Clears any existing rules before loading.
|
|
||||||
* @return true if cache was loaded successfully
|
|
||||||
*/
|
|
||||||
bool loadFromCache();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Lookup key for a multi-piece selector. The pieces are hashed and compared
|
|
||||||
// as if concatenated, so callers can look up composite keys without
|
|
||||||
// materializing the concatenation in a scratch buffer. Constructed from a
|
|
||||||
// braced list of any arity, e.g. `CompositeKey{tagName, ".", cls}` or
|
|
||||||
// `CompositeKey{".", cls}`. The initializer_list's backing array lives for
|
|
||||||
// the full expression, which covers the lifetime of the find() call.
|
|
||||||
struct CompositeKey {
|
|
||||||
std::initializer_list<std::string_view> pieces;
|
|
||||||
CompositeKey(std::initializer_list<std::string_view> p) noexcept : pieces(p) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ASCII-case-insensitive transparent hash/equal. Stored selectors and lookup
|
|
||||||
// keys are compared without regard to case, so callers may insert and look up
|
|
||||||
// using whatever case the CSS source or HTML element name happens to use.
|
|
||||||
// Bodies live in CssParser.cpp so they can share the file-local asciiToLower.
|
|
||||||
struct SvHash {
|
|
||||||
using is_transparent = void;
|
|
||||||
size_t operator()(std::string_view sv) const noexcept;
|
|
||||||
size_t operator()(const std::string& s) const noexcept;
|
|
||||||
size_t operator()(CompositeKey k) const noexcept;
|
|
||||||
};
|
|
||||||
struct SvEqual {
|
|
||||||
using is_transparent = void;
|
|
||||||
bool operator()(std::string_view a, std::string_view b) const noexcept;
|
|
||||||
bool operator()(const std::string& a, std::string_view b) const noexcept;
|
|
||||||
bool operator()(std::string_view a, const std::string& b) const noexcept;
|
|
||||||
bool operator()(const std::string& a, const std::string& b) const noexcept;
|
|
||||||
bool operator()(CompositeKey a, std::string_view b) const noexcept;
|
|
||||||
bool operator()(std::string_view a, CompositeKey b) const noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Storage: maps selector -> style properties. Hash/equal are case-insensitive.
|
|
||||||
std::unordered_map<std::string, CssStyle, SvHash, SvEqual> rulesBySelector_;
|
|
||||||
|
|
||||||
std::string cachePath;
|
|
||||||
|
|
||||||
// Internal parsing helpers
|
|
||||||
void processRuleBlockWithStyle(std::string_view selectorGroup, const CssStyle& style);
|
|
||||||
static CssStyle parseDeclarations(std::string_view declBlock);
|
|
||||||
static void parseDeclarationIntoStyle(std::string_view decl, CssStyle& style);
|
|
||||||
|
|
||||||
// Individual property value parsers
|
|
||||||
static CssTextAlign interpretAlignment(std::string_view val);
|
|
||||||
static CssFontStyle interpretFontStyle(std::string_view val);
|
|
||||||
static CssFontWeight interpretFontWeight(std::string_view val);
|
|
||||||
static CssTextDecoration interpretDecoration(std::string_view val);
|
|
||||||
static CssLength interpretLength(std::string_view val);
|
|
||||||
/** Returns true only when a numeric length was parsed (e.g. 2em, 50%). False for auto/inherit/initial. */
|
|
||||||
static bool tryInterpretLength(std::string_view val, CssLength& out);
|
|
||||||
};
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
// Matches order of PARAGRAPH_ALIGNMENT in CrossPointSettings
|
|
||||||
enum class CssTextAlign : uint8_t { Justify = 0, Left = 1, Center = 2, Right = 3, None = 4 };
|
|
||||||
enum class CssUnit : uint8_t { Pixels = 0, Em = 1, Rem = 2, Points = 3, Percent = 4 };
|
|
||||||
enum class CssTextDirection : uint8_t { Ltr = 0, Rtl = 1 };
|
|
||||||
|
|
||||||
// Represents a CSS length value with its unit, allowing deferred resolution to pixels
|
|
||||||
struct CssLength {
|
|
||||||
float value = 0.0f;
|
|
||||||
CssUnit unit = CssUnit::Pixels;
|
|
||||||
|
|
||||||
CssLength() = default;
|
|
||||||
CssLength(const float v, const CssUnit u) : value(v), unit(u) {}
|
|
||||||
|
|
||||||
// Convenience constructor for pixel values (most common case)
|
|
||||||
explicit CssLength(const float pixels) : value(pixels) {}
|
|
||||||
|
|
||||||
// Returns true if this length can be resolved to pixels with the given context.
|
|
||||||
// Percentage units require a non-zero containerWidth to resolve.
|
|
||||||
[[nodiscard]] bool isResolvable(const float containerWidth = 0) const {
|
|
||||||
return unit != CssUnit::Percent || containerWidth > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve to pixels given the current em size (font line height)
|
|
||||||
// containerWidth is needed for percentage units (e.g. viewport width)
|
|
||||||
[[nodiscard]] float toPixels(const float emSize, const float containerWidth = 0) const {
|
|
||||||
switch (unit) {
|
|
||||||
case CssUnit::Em:
|
|
||||||
case CssUnit::Rem:
|
|
||||||
return value * emSize;
|
|
||||||
case CssUnit::Points:
|
|
||||||
return value * 1.33f; // Approximate pt to px conversion
|
|
||||||
case CssUnit::Percent:
|
|
||||||
return value * containerWidth / 100.0f;
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve to int16_t pixels (for BlockStyle fields)
|
|
||||||
[[nodiscard]] int16_t toPixelsInt16(const float emSize, const float containerWidth = 0) const {
|
|
||||||
return static_cast<int16_t>(toPixels(emSize, containerWidth));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Font style options matching CSS font-style property
|
|
||||||
enum class CssFontStyle : uint8_t { Normal = 0, Italic = 1 };
|
|
||||||
|
|
||||||
// Font weight options - CSS supports 100-900, we simplify to normal/bold
|
|
||||||
enum class CssFontWeight : uint8_t { Normal = 0, Bold = 1 };
|
|
||||||
|
|
||||||
// Text decoration options. Values are bit flags so CSS can combine multiple line decorations.
|
|
||||||
enum class CssTextDecoration : uint8_t { None = 0, Underline = 1, LineThrough = 2 };
|
|
||||||
|
|
||||||
constexpr CssTextDecoration operator|(const CssTextDecoration a, const CssTextDecoration b) {
|
|
||||||
return static_cast<CssTextDecoration>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr CssTextDecoration operator&(const CssTextDecoration a, const CssTextDecoration b) {
|
|
||||||
return static_cast<CssTextDecoration>(static_cast<uint8_t>(a) & static_cast<uint8_t>(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint8_t CSS_TEXT_DECORATION_MASK =
|
|
||||||
static_cast<uint8_t>(CssTextDecoration::Underline) | static_cast<uint8_t>(CssTextDecoration::LineThrough);
|
|
||||||
|
|
||||||
// Display options - only None and Block are relevant for e-ink rendering
|
|
||||||
enum class CssDisplay : uint8_t { Block = 0, None = 1 };
|
|
||||||
|
|
||||||
// Vertical alignment options for inline elements (e.g. superscript/subscript)
|
|
||||||
enum class CssVerticalAlign : uint8_t { Baseline = 0, Super = 1, Sub = 2 };
|
|
||||||
|
|
||||||
// Bitmask for tracking which properties have been explicitly set
|
|
||||||
struct CssPropertyFlags {
|
|
||||||
uint16_t textAlign : 1;
|
|
||||||
uint16_t fontStyle : 1;
|
|
||||||
uint16_t fontWeight : 1;
|
|
||||||
uint16_t textDecoration : 1;
|
|
||||||
uint16_t textIndent : 1;
|
|
||||||
uint16_t marginTop : 1;
|
|
||||||
uint16_t marginBottom : 1;
|
|
||||||
uint16_t marginLeft : 1;
|
|
||||||
uint16_t marginRight : 1;
|
|
||||||
uint16_t paddingTop : 1;
|
|
||||||
uint16_t paddingBottom : 1;
|
|
||||||
uint16_t paddingLeft : 1;
|
|
||||||
uint16_t paddingRight : 1;
|
|
||||||
uint16_t imageHeight : 1;
|
|
||||||
uint16_t imageWidth : 1;
|
|
||||||
uint16_t display : 1;
|
|
||||||
uint16_t direction : 1;
|
|
||||||
uint16_t verticalAlign : 1;
|
|
||||||
|
|
||||||
CssPropertyFlags()
|
|
||||||
: textAlign(0),
|
|
||||||
fontStyle(0),
|
|
||||||
fontWeight(0),
|
|
||||||
textDecoration(0),
|
|
||||||
textIndent(0),
|
|
||||||
marginTop(0),
|
|
||||||
marginBottom(0),
|
|
||||||
marginLeft(0),
|
|
||||||
marginRight(0),
|
|
||||||
paddingTop(0),
|
|
||||||
paddingBottom(0),
|
|
||||||
paddingLeft(0),
|
|
||||||
paddingRight(0),
|
|
||||||
imageHeight(0),
|
|
||||||
imageWidth(0),
|
|
||||||
display(0),
|
|
||||||
direction(0),
|
|
||||||
verticalAlign(0) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool anySet() const {
|
|
||||||
return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom ||
|
|
||||||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || imageHeight ||
|
|
||||||
imageWidth || display || direction || verticalAlign;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearAll() {
|
|
||||||
textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0;
|
|
||||||
marginTop = marginBottom = marginLeft = marginRight = 0;
|
|
||||||
paddingTop = paddingBottom = paddingLeft = paddingRight = 0;
|
|
||||||
imageHeight = imageWidth = display = direction = verticalAlign = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache serializes defined flags as uint32_t with bit indices 0..17.
|
|
||||||
static_assert(sizeof(CssPropertyFlags) <= sizeof(uint32_t),
|
|
||||||
"CssPropertyFlags exceeds 32 bits; update cache read/write in CssParser.cpp");
|
|
||||||
|
|
||||||
// Represents a collection of CSS style properties
|
|
||||||
// Only stores properties relevant to e-ink text rendering
|
|
||||||
// Length values are stored as CssLength (value + unit) for deferred resolution
|
|
||||||
struct CssStyle {
|
|
||||||
CssTextAlign textAlign = CssTextAlign::Left;
|
|
||||||
CssFontStyle fontStyle = CssFontStyle::Normal;
|
|
||||||
CssFontWeight fontWeight = CssFontWeight::Normal;
|
|
||||||
CssTextDecoration textDecoration = CssTextDecoration::None;
|
|
||||||
CssTextDirection direction = CssTextDirection::Ltr;
|
|
||||||
|
|
||||||
CssLength textIndent; // First-line indent (deferred resolution)
|
|
||||||
CssLength marginTop; // Vertical spacing before block
|
|
||||||
CssLength marginBottom; // Vertical spacing after block
|
|
||||||
CssLength marginLeft; // Horizontal spacing left of block
|
|
||||||
CssLength marginRight; // Horizontal spacing right of block
|
|
||||||
CssLength paddingTop; // Padding before
|
|
||||||
CssLength paddingBottom; // Padding after
|
|
||||||
CssLength paddingLeft; // Padding left
|
|
||||||
CssLength paddingRight; // Padding right
|
|
||||||
CssLength imageHeight; // Height for img (e.g. 2em) – width derived from aspect ratio when only height set
|
|
||||||
CssLength imageWidth; // Width for img when both or only width set
|
|
||||||
CssDisplay display = CssDisplay::Block; // display property (Block or None)
|
|
||||||
CssVerticalAlign verticalAlign = CssVerticalAlign::Baseline; // vertical-align (super/sub positioning)
|
|
||||||
|
|
||||||
CssPropertyFlags defined; // Tracks which properties were explicitly set
|
|
||||||
|
|
||||||
// Apply properties from another style, only overwriting if the other style
|
|
||||||
// has that property explicitly defined
|
|
||||||
void applyOver(const CssStyle& base) {
|
|
||||||
if (base.hasTextAlign()) {
|
|
||||||
textAlign = base.textAlign;
|
|
||||||
defined.textAlign = 1;
|
|
||||||
}
|
|
||||||
if (base.hasFontStyle()) {
|
|
||||||
fontStyle = base.fontStyle;
|
|
||||||
defined.fontStyle = 1;
|
|
||||||
}
|
|
||||||
if (base.hasFontWeight()) {
|
|
||||||
fontWeight = base.fontWeight;
|
|
||||||
defined.fontWeight = 1;
|
|
||||||
}
|
|
||||||
if (base.hasTextDecoration()) {
|
|
||||||
textDecoration = base.textDecoration;
|
|
||||||
defined.textDecoration = 1;
|
|
||||||
}
|
|
||||||
if (base.hasTextIndent()) {
|
|
||||||
textIndent = base.textIndent;
|
|
||||||
defined.textIndent = 1;
|
|
||||||
}
|
|
||||||
if (base.hasMarginTop()) {
|
|
||||||
marginTop = base.marginTop;
|
|
||||||
defined.marginTop = 1;
|
|
||||||
}
|
|
||||||
if (base.hasMarginBottom()) {
|
|
||||||
marginBottom = base.marginBottom;
|
|
||||||
defined.marginBottom = 1;
|
|
||||||
}
|
|
||||||
if (base.hasMarginLeft()) {
|
|
||||||
marginLeft = base.marginLeft;
|
|
||||||
defined.marginLeft = 1;
|
|
||||||
}
|
|
||||||
if (base.hasMarginRight()) {
|
|
||||||
marginRight = base.marginRight;
|
|
||||||
defined.marginRight = 1;
|
|
||||||
}
|
|
||||||
if (base.hasPaddingTop()) {
|
|
||||||
paddingTop = base.paddingTop;
|
|
||||||
defined.paddingTop = 1;
|
|
||||||
}
|
|
||||||
if (base.hasPaddingBottom()) {
|
|
||||||
paddingBottom = base.paddingBottom;
|
|
||||||
defined.paddingBottom = 1;
|
|
||||||
}
|
|
||||||
if (base.hasPaddingLeft()) {
|
|
||||||
paddingLeft = base.paddingLeft;
|
|
||||||
defined.paddingLeft = 1;
|
|
||||||
}
|
|
||||||
if (base.hasPaddingRight()) {
|
|
||||||
paddingRight = base.paddingRight;
|
|
||||||
defined.paddingRight = 1;
|
|
||||||
}
|
|
||||||
if (base.hasImageHeight()) {
|
|
||||||
imageHeight = base.imageHeight;
|
|
||||||
defined.imageHeight = 1;
|
|
||||||
}
|
|
||||||
if (base.hasImageWidth()) {
|
|
||||||
imageWidth = base.imageWidth;
|
|
||||||
defined.imageWidth = 1;
|
|
||||||
}
|
|
||||||
if (base.hasDisplay()) {
|
|
||||||
display = base.display;
|
|
||||||
defined.display = 1;
|
|
||||||
}
|
|
||||||
if (base.hasDirection()) {
|
|
||||||
direction = base.direction;
|
|
||||||
defined.direction = 1;
|
|
||||||
}
|
|
||||||
if (base.hasVerticalAlign()) {
|
|
||||||
verticalAlign = base.verticalAlign;
|
|
||||||
defined.verticalAlign = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool hasTextAlign() const { return defined.textAlign; }
|
|
||||||
[[nodiscard]] bool hasFontStyle() const { return defined.fontStyle; }
|
|
||||||
[[nodiscard]] bool hasFontWeight() const { return defined.fontWeight; }
|
|
||||||
[[nodiscard]] bool hasTextDecoration() const { return defined.textDecoration; }
|
|
||||||
[[nodiscard]] bool hasTextIndent() const { return defined.textIndent; }
|
|
||||||
[[nodiscard]] bool hasMarginTop() const { return defined.marginTop; }
|
|
||||||
[[nodiscard]] bool hasMarginBottom() const { return defined.marginBottom; }
|
|
||||||
[[nodiscard]] bool hasMarginLeft() const { return defined.marginLeft; }
|
|
||||||
[[nodiscard]] bool hasMarginRight() const { return defined.marginRight; }
|
|
||||||
[[nodiscard]] bool hasPaddingTop() const { return defined.paddingTop; }
|
|
||||||
[[nodiscard]] bool hasPaddingBottom() const { return defined.paddingBottom; }
|
|
||||||
[[nodiscard]] bool hasPaddingLeft() const { return defined.paddingLeft; }
|
|
||||||
[[nodiscard]] bool hasPaddingRight() const { return defined.paddingRight; }
|
|
||||||
[[nodiscard]] bool hasImageHeight() const { return defined.imageHeight; }
|
|
||||||
[[nodiscard]] bool hasImageWidth() const { return defined.imageWidth; }
|
|
||||||
[[nodiscard]] bool hasDisplay() const { return defined.display; }
|
|
||||||
[[nodiscard]] bool hasDirection() const { return defined.direction; }
|
|
||||||
[[nodiscard]] bool hasVerticalAlign() const { return defined.verticalAlign; }
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
textAlign = CssTextAlign::Left;
|
|
||||||
fontStyle = CssFontStyle::Normal;
|
|
||||||
fontWeight = CssFontWeight::Normal;
|
|
||||||
textDecoration = CssTextDecoration::None;
|
|
||||||
direction = CssTextDirection::Ltr;
|
|
||||||
textIndent = CssLength{};
|
|
||||||
marginTop = marginBottom = marginLeft = marginRight = CssLength{};
|
|
||||||
paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{};
|
|
||||||
imageHeight = imageWidth = CssLength{};
|
|
||||||
display = CssDisplay::Block;
|
|
||||||
verticalAlign = CssVerticalAlign::Baseline;
|
|
||||||
defined.clearAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// based on
|
|
||||||
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
|
|
||||||
|
|
||||||
#include "htmlEntities.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <iterator>
|
|
||||||
|
|
||||||
struct EntityPair {
|
|
||||||
const char* key;
|
|
||||||
const char* value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sorted lexicographically by key to allow binary search.
|
|
||||||
static constexpr EntityPair ENTITY_LOOKUP[] = {
|
|
||||||
{"Æ", "Æ"}, {"Á", "Á"}, {"Â", "Â"}, {"À", "À"}, {"Α", "Α"},
|
|
||||||
{"Å", "Å"}, {"Ã", "Ã"}, {"Ä", "Ä"}, {"Β", "Β"}, {"Ç", "Ç"},
|
|
||||||
{"Χ", "Χ"}, {"‡", "‡"}, {"Δ", "Δ"}, {"Ð", "Ð"}, {"É", "É"},
|
|
||||||
{"Ê", "Ê"}, {"È", "È"}, {"Ε", "Ε"}, {"Η", "Η"}, {"Ë", "Ë"},
|
|
||||||
{"Γ", "Γ"}, {"Í", "Í"}, {"Î", "Î"}, {"Ì", "Ì"}, {"Ι", "Ι"},
|
|
||||||
{"Ï", "Ï"}, {"Κ", "Κ"}, {"Λ", "Λ"}, {"Μ", "Μ"}, {"Ñ", "Ñ"},
|
|
||||||
{"Ν", "Ν"}, {"Œ", "Œ"}, {"Ó", "Ó"}, {"Ô", "Ô"}, {"Ò", "Ò"},
|
|
||||||
{"Ω", "Ω"}, {"Ο", "Ο"}, {"Ø", "Ø"}, {"Õ", "Õ"}, {"Ö", "Ö"},
|
|
||||||
{"Φ", "Φ"}, {"Π", "Π"}, {"″", "″"}, {"Ψ", "Ψ"}, {"Ρ", "Ρ"},
|
|
||||||
{"Š", "Š"}, {"Σ", "Σ"}, {"Þ", "Þ"}, {"Τ", "Τ"}, {"Θ", "Θ"},
|
|
||||||
{"Ú", "Ú"}, {"Û", "Û"}, {"Ù", "Ù"}, {"Υ", "Υ"}, {"Ü", "Ü"},
|
|
||||||
{"Ξ", "Ξ"}, {"Ý", "Ý"}, {"Ÿ", "Ÿ"}, {"Ζ", "Ζ"}, {"á", "á"},
|
|
||||||
{"â", "â"}, {"´", "´"}, {"æ", "æ"}, {"à", "à"}, {"ℵ", "ℵ"},
|
|
||||||
{"α", "α"}, {"&", "&"}, {"∧", "∧"}, {"∠", "∠"}, {"å", "å"},
|
|
||||||
{"≈", "≈"}, {"ã", "ã"}, {"ä", "ä"}, {"„", "„"}, {"β", "β"},
|
|
||||||
{"¦", "¦"}, {"•", "•"}, {"∩", "∩"}, {"ç", "ç"}, {"¸", "¸"},
|
|
||||||
{"¢", "¢"}, {"χ", "χ"}, {"ˆ", "ˆ"}, {"♣", "♣"}, {"≅", "≅"},
|
|
||||||
{"©", "©"}, {"↵", "↵"}, {"∪", "∪"}, {"¤", "¤"}, {"⇓", "⇓"},
|
|
||||||
{"†", "†"}, {"↓", "↓"}, {"°", "°"}, {"δ", "δ"}, {"♦", "♦"},
|
|
||||||
{"÷", "÷"}, {"é", "é"}, {"ê", "ê"}, {"è", "è"}, {"∅", "∅"},
|
|
||||||
{" ", " "}, {" ", " "}, {"ε", "ε"}, {"≡", "≡"}, {"η", "η"},
|
|
||||||
{"ð", "ð"}, {"ë", "ë"}, {"€", "€"}, {"∃", "∃"}, {"ƒ", "ƒ"},
|
|
||||||
{"∀", "∀"}, {"½", "½"}, {"¼", "¼"}, {"¾", "¾"}, {"⁄", "⁄"},
|
|
||||||
{"γ", "γ"}, {"≥", "≥"}, {">", ">"}, {"⇔", "⇔"}, {"↔", "↔"},
|
|
||||||
{"♥", "♥"}, {"…", "…"}, {"í", "í"}, {"î", "î"}, {"¡", "¡"},
|
|
||||||
{"ì", "ì"}, {"ℑ", "ℑ"}, {"∞", "∞"}, {"∫", "∫"}, {"ι", "ι"},
|
|
||||||
{"¿", "¿"}, {"∈", "∈"}, {"ï", "ï"}, {"κ", "κ"}, {"⇐", "⇐"},
|
|
||||||
{"λ", "λ"}, {"⟨", "〈"}, {"«", "«"}, {"←", "←"}, {"⌈", "⌈"},
|
|
||||||
{"“", "\u201C"}, {"≤", "≤"}, {"⌊", "⌊"}, {"∗", "∗"}, {"◊", "◊"},
|
|
||||||
{"‎", "\u200E"}, {"‹", "‹"}, {"‘", "\u2018"}, {"<", "<"}, {"¯", "¯"},
|
|
||||||
{"—", "—"}, {"µ", "µ"}, {"·", "·"}, {"−", "−"}, {"μ", "μ"},
|
|
||||||
{"∇", "∇"}, {" ", "\xC2\xA0"}, {"–", "–"}, {"≠", "≠"}, {"∋", "∋"},
|
|
||||||
{"¬", "¬"}, {"∉", "∉"}, {"⊄", "⊄"}, {"ñ", "ñ"}, {"ν", "ν"},
|
|
||||||
{"ó", "ó"}, {"ô", "ô"}, {"œ", "œ"}, {"ò", "ò"}, {"‾", "‾"},
|
|
||||||
{"ω", "ω"}, {"ο", "ο"}, {"⊕", "⊕"}, {"∨", "∨"}, {"ª", "ª"},
|
|
||||||
{"º", "º"}, {"ø", "ø"}, {"õ", "õ"}, {"⊗", "⊗"}, {"ö", "ö"},
|
|
||||||
{"¶", "¶"}, {"∂", "∂"}, {"‰", "‰"}, {"⊥", "⊥"}, {"φ", "φ"},
|
|
||||||
{"π", "π"}, {"ϖ", "ϖ"}, {"±", "±"}, {"£", "£"}, {"′", "′"},
|
|
||||||
{"∏", "∏"}, {"∝", "∝"}, {"ψ", "ψ"}, {""", "\""}, {"⇒", "⇒"},
|
|
||||||
{"√", "√"}, {"⟩", "〉"}, {"»", "»"}, {"→", "→"}, {"⌉", "⌉"},
|
|
||||||
{"”", "\u201D"}, {"ℜ", "\u211C"}, {"®", "®"}, {"⌋", "⌋"}, {"ρ", "ρ"},
|
|
||||||
{"‏", "\u200F"}, {"›", "›"}, {"’", "\u2019"}, {"‚", "‚"}, {"š", "š"},
|
|
||||||
{"⋅", "⋅"}, {"§", "§"}, {"­", "\xC2\xAD"}, {"σ", "σ"}, {"ς", "ς"},
|
|
||||||
{"∼", "∼"}, {"♠", "♠"}, {"⊂", "⊂"}, {"⊆", "⊆"}, {"∑", "∑"},
|
|
||||||
{"¹", "¹"}, {"²", "²"}, {"³", "³"}, {"⊃", "⊃"}, {"⊇", "⊇"},
|
|
||||||
{"ß", "ß"}, {"τ", "τ"}, {"∴", "∴"}, {"θ", "θ"}, {"ϑ", "ϑ"},
|
|
||||||
{" ", " "}, {"þ", "þ"}, {"˜", "˜"}, {"×", "×"}, {"™", "™"},
|
|
||||||
{"⇑", "⇑"}, {"ú", "ú"}, {"↑", "↑"}, {"û", "û"}, {"ù", "ù"},
|
|
||||||
{"¨", "¨"}, {"ϒ", "ϒ"}, {"υ", "υ"}, {"ü", "ü"}, {"℘", "℘"},
|
|
||||||
{"ξ", "ξ"}, {"ý", "ý"}, {"¥", "¥"}, {"ÿ", "ÿ"}, {"ζ", "ζ"},
|
|
||||||
{"‍", "\u200D"}, {"‌", "\u200C"},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Verify the table is sorted at compile time.
|
|
||||||
static constexpr int constexprStrcmp(const char* a, const char* b) {
|
|
||||||
for (size_t i = 0;; i++) {
|
|
||||||
if (a[i] != b[i]) return (unsigned char)a[i] < (unsigned char)b[i] ? -1 : 1;
|
|
||||||
if (a[i] == '\0') return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr bool isTableSorted() {
|
|
||||||
for (size_t i = 1; i < std::size(ENTITY_LOOKUP); i++) {
|
|
||||||
if (constexprStrcmp(ENTITY_LOOKUP[i - 1].key, ENTITY_LOOKUP[i].key) >= 0) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
static_assert(isTableSorted(), "ENTITY_LOOKUP must be sorted lexicographically by key");
|
|
||||||
|
|
||||||
// Lookup a single HTML entity and return its UTF-8 value.
|
|
||||||
const char* lookupHtmlEntity(const char* entity, size_t len) {
|
|
||||||
if (entity == nullptr || len == 0) return nullptr;
|
|
||||||
|
|
||||||
size_t lo = 0;
|
|
||||||
size_t hi = std::size(ENTITY_LOOKUP);
|
|
||||||
|
|
||||||
while (lo < hi) {
|
|
||||||
const size_t mid = lo + (hi - lo) / 2;
|
|
||||||
const char* key = ENTITY_LOOKUP[mid].key;
|
|
||||||
const size_t keyLen = strlen(key);
|
|
||||||
const size_t cmpLen = (len < keyLen) ? len : keyLen;
|
|
||||||
int cmp = memcmp(entity, key, cmpLen);
|
|
||||||
if (cmp == 0) {
|
|
||||||
// safety net: if prefix equal, shorter string is considered smaller
|
|
||||||
if (len < keyLen)
|
|
||||||
cmp = -1;
|
|
||||||
else if (len > keyLen)
|
|
||||||
cmp = 1;
|
|
||||||
else
|
|
||||||
cmp = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmp == 0) return ENTITY_LOOKUP[mid].value;
|
|
||||||
if (cmp < 0)
|
|
||||||
hi = mid;
|
|
||||||
else
|
|
||||||
lo = mid + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// based on
|
|
||||||
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Lookup a single HTML entity (including & and ;) and return its UTF-8 value
|
|
||||||
// Returns nullptr if entity is not found
|
|
||||||
const char* lookupHtmlEntity(const char* entity, size_t len);
|
|
||||||
@@ -1,471 +0,0 @@
|
|||||||
#include "HyphenationCommon.h"
|
|
||||||
|
|
||||||
#include <Utf8.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Convert Latin uppercase letters (ASCII plus Latin-1 supplement) to lowercase
|
|
||||||
uint32_t toLowerLatinImpl(const uint32_t cp) {
|
|
||||||
if (cp >= 'A' && cp <= 'Z') {
|
|
||||||
return cp - 'A' + 'a';
|
|
||||||
}
|
|
||||||
if ((cp >= 0x00C0 && cp <= 0x00D6) || (cp >= 0x00D8 && cp <= 0x00DE)) {
|
|
||||||
return cp + 0x20;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Latin Extended-A (U+0100..U+017E): uppercase letters are paired with
|
|
||||||
// lowercase at cp+1. Two sub-ranges have different alignment:
|
|
||||||
// U+0100..U+0137: uppercase on EVEN codepoints
|
|
||||||
// U+0139..U+0148: uppercase on ODD codepoints
|
|
||||||
// U+014A..U+0177: uppercase on EVEN codepoints
|
|
||||||
// U+0179..U+017E: uppercase on ODD codepoints
|
|
||||||
// Covers Polish (Ą/ą, Ć/ć, Ę/ę, Ł/ł, Ń/ń, Ś/ś, Ź/ź, Ż/ż), Czech, Hungarian, Turkish, etc.
|
|
||||||
if ((cp >= 0x0100 && cp <= 0x0137 && (cp % 2 == 0)) || (cp >= 0x0139 && cp <= 0x0148 && (cp % 2 == 1)) ||
|
|
||||||
(cp >= 0x014A && cp <= 0x0177 && (cp % 2 == 0)) || (cp >= 0x0179 && cp <= 0x017E && (cp % 2 == 1))) {
|
|
||||||
return cp + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cp) {
|
|
||||||
case 0x0178: // Ÿ
|
|
||||||
return 0x00FF; // ÿ
|
|
||||||
case 0x1E9E: // ẞ
|
|
||||||
return 0x00DF; // ß
|
|
||||||
default:
|
|
||||||
return cp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Cyrillic uppercase letters to lowercase
|
|
||||||
// Cyrillic uppercase range 0x0410-0x042F maps to lowercase by adding 0x20
|
|
||||||
// Special case: Cyrillic capital IO (0x0401) maps to lowercase io (0x0451)
|
|
||||||
uint32_t toLowerCyrillicImpl(const uint32_t cp) {
|
|
||||||
if (cp >= 0x0410 && cp <= 0x042F) {
|
|
||||||
return cp + 0x20;
|
|
||||||
}
|
|
||||||
if (cp == 0x0401) {
|
|
||||||
return 0x0451;
|
|
||||||
}
|
|
||||||
return cp;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
uint32_t toLowerLatin(const uint32_t cp) { return toLowerLatinImpl(cp); }
|
|
||||||
|
|
||||||
uint32_t toLowerCyrillic(const uint32_t cp) { return toLowerCyrillicImpl(cp); }
|
|
||||||
|
|
||||||
bool isLatinLetter(const uint32_t cp) {
|
|
||||||
if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (((cp >= 0x00C0 && cp <= 0x00D6) || (cp >= 0x00D8 && cp <= 0x00F6) || (cp >= 0x00F8 && cp <= 0x00FF)) &&
|
|
||||||
cp != 0x00D7 && cp != 0x00F7) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Latin Extended-A (U+0100..U+017F): Polish, Czech, Hungarian, Turkish, etc.
|
|
||||||
if (cp >= 0x0100 && cp <= 0x017F) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cp) {
|
|
||||||
case 0x0152: // Œ
|
|
||||||
case 0x0153: // œ
|
|
||||||
case 0x0178: // Ÿ
|
|
||||||
case 0x1E9E: // ẞ
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isCyrillicLetter(const uint32_t cp) { return (cp >= 0x0400 && cp <= 0x052F); }
|
|
||||||
|
|
||||||
bool isAlphabetic(const uint32_t cp) { return isLatinLetter(cp) || isCyrillicLetter(cp); }
|
|
||||||
|
|
||||||
bool isPunctuation(const uint32_t cp) {
|
|
||||||
switch (cp) {
|
|
||||||
case '-':
|
|
||||||
case '.':
|
|
||||||
case ',':
|
|
||||||
case '!':
|
|
||||||
case '?':
|
|
||||||
case ';':
|
|
||||||
case ':':
|
|
||||||
case '"':
|
|
||||||
case '\'':
|
|
||||||
case ')':
|
|
||||||
case '(':
|
|
||||||
case 0x00AB: // «
|
|
||||||
case 0x00BB: // »
|
|
||||||
case 0x2018: // ‘
|
|
||||||
case 0x2019: // ’
|
|
||||||
case 0x201A: // ‚
|
|
||||||
case 0x201C: // “
|
|
||||||
case 0x201D: // ”
|
|
||||||
case 0x201E: // „
|
|
||||||
case 0x00A0: // no-break space
|
|
||||||
case '{':
|
|
||||||
case '}':
|
|
||||||
case '[':
|
|
||||||
case ']':
|
|
||||||
case '/':
|
|
||||||
case 0x2039: // ‹
|
|
||||||
case 0x203A: // ›
|
|
||||||
case 0x2026: // …
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAsciiDigit(const uint32_t cp) { return cp >= '0' && cp <= '9'; }
|
|
||||||
|
|
||||||
bool isApostrophe(const uint32_t cp) {
|
|
||||||
switch (cp) {
|
|
||||||
case '\'':
|
|
||||||
case 0x2018: // left single quotation mark
|
|
||||||
case 0x2019: // right single quotation mark
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isExplicitHyphen(const uint32_t cp) {
|
|
||||||
switch (cp) {
|
|
||||||
case '-':
|
|
||||||
case 0x00AD: // soft hyphen
|
|
||||||
case 0x058A: // Armenian hyphen
|
|
||||||
case 0x2010: // hyphen
|
|
||||||
case 0x2011: // non-breaking hyphen
|
|
||||||
case 0x2012: // figure dash
|
|
||||||
case 0x2013: // en dash
|
|
||||||
case 0x2014: // em dash
|
|
||||||
case 0x2015: // horizontal bar
|
|
||||||
case 0x2043: // hyphen bullet
|
|
||||||
case 0x207B: // superscript minus
|
|
||||||
case 0x208B: // subscript minus
|
|
||||||
case 0x2212: // minus sign
|
|
||||||
case 0x2E17: // double oblique hyphen
|
|
||||||
case 0x2E3A: // two-em dash
|
|
||||||
case 0x2E3B: // three-em dash
|
|
||||||
case 0xFE58: // small em dash
|
|
||||||
case 0xFE63: // small hyphen-minus
|
|
||||||
case 0xFF0D: // fullwidth hyphen-minus
|
|
||||||
case 0x005F: // Underscore
|
|
||||||
case 0x2026: // Ellipsis
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSoftHyphen(const uint32_t cp) { return cp == 0x00AD; }
|
|
||||||
|
|
||||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps) {
|
|
||||||
if (cps.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove trailing footnote references like [12], even if punctuation trails after the closing bracket.
|
|
||||||
if (cps.size() >= 3) {
|
|
||||||
int end = static_cast<int>(cps.size()) - 1;
|
|
||||||
while (end >= 0 && isPunctuation(cps[end].value)) {
|
|
||||||
--end;
|
|
||||||
}
|
|
||||||
int pos = end;
|
|
||||||
if (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
|
||||||
while (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
|
||||||
--pos;
|
|
||||||
}
|
|
||||||
if (pos >= 0 && cps[pos].value == '[' && end - pos > 1) {
|
|
||||||
cps.erase(cps.begin() + pos, cps.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!cps.empty() && isPunctuation(cps.front().value)) {
|
|
||||||
cps.erase(cps.begin());
|
|
||||||
}
|
|
||||||
while (!cps.empty() && isPunctuation(cps.back().value)) {
|
|
||||||
cps.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<CodepointInfo> collectCodepoints(const std::string& word) {
|
|
||||||
std::vector<CodepointInfo> cps;
|
|
||||||
cps.reserve(word.size());
|
|
||||||
|
|
||||||
const unsigned char* base = reinterpret_cast<const unsigned char*>(word.c_str());
|
|
||||||
const unsigned char* ptr = base;
|
|
||||||
while (*ptr != 0) {
|
|
||||||
const unsigned char* current = ptr;
|
|
||||||
const uint32_t cp = utf8NextCodepoint(&ptr);
|
|
||||||
// If this is a combining diacritic (e.g., U+0301 = acute) and there's
|
|
||||||
// a previous base character that can be composed into a single
|
|
||||||
// precomposed Unicode scalar (Latin-1 / Latin-Extended), do that
|
|
||||||
// composition here. This provides lightweight NFC-like behavior for
|
|
||||||
// common Western European diacritics (acute, grave, circumflex, tilde,
|
|
||||||
// diaeresis, cedilla) without pulling in a full Unicode normalization
|
|
||||||
// library.
|
|
||||||
if (!cps.empty()) {
|
|
||||||
uint32_t prev = cps.back().value;
|
|
||||||
uint32_t composed = 0;
|
|
||||||
switch (cp) {
|
|
||||||
case 0x0300: // grave
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x00C0;
|
|
||||||
break; // A -> À
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x00E0;
|
|
||||||
break; // a -> à
|
|
||||||
case 0x0045:
|
|
||||||
composed = 0x00C8;
|
|
||||||
break; // E -> È
|
|
||||||
case 0x0065:
|
|
||||||
composed = 0x00E8;
|
|
||||||
break; // e -> è
|
|
||||||
case 0x0049:
|
|
||||||
composed = 0x00CC;
|
|
||||||
break; // I -> Ì
|
|
||||||
case 0x0069:
|
|
||||||
composed = 0x00EC;
|
|
||||||
break; // i -> ì
|
|
||||||
case 0x004F:
|
|
||||||
composed = 0x00D2;
|
|
||||||
break; // O -> Ò
|
|
||||||
case 0x006F:
|
|
||||||
composed = 0x00F2;
|
|
||||||
break; // o -> ò
|
|
||||||
case 0x0055:
|
|
||||||
composed = 0x00D9;
|
|
||||||
break; // U -> Ù
|
|
||||||
case 0x0075:
|
|
||||||
composed = 0x00F9;
|
|
||||||
break; // u -> ù
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0301: // acute
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x00C1;
|
|
||||||
break; // A -> Á
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x00E1;
|
|
||||||
break; // a -> á
|
|
||||||
case 0x0045:
|
|
||||||
composed = 0x00C9;
|
|
||||||
break; // E -> É
|
|
||||||
case 0x0065:
|
|
||||||
composed = 0x00E9;
|
|
||||||
break; // e -> é
|
|
||||||
case 0x0049:
|
|
||||||
composed = 0x00CD;
|
|
||||||
break; // I -> Í
|
|
||||||
case 0x0069:
|
|
||||||
composed = 0x00ED;
|
|
||||||
break; // i -> í
|
|
||||||
case 0x004F:
|
|
||||||
composed = 0x00D3;
|
|
||||||
break; // O -> Ó
|
|
||||||
case 0x006F:
|
|
||||||
composed = 0x00F3;
|
|
||||||
break; // o -> ó
|
|
||||||
case 0x0055:
|
|
||||||
composed = 0x00DA;
|
|
||||||
break; // U -> Ú
|
|
||||||
case 0x0075:
|
|
||||||
composed = 0x00FA;
|
|
||||||
break; // u -> ú
|
|
||||||
case 0x0059:
|
|
||||||
composed = 0x00DD;
|
|
||||||
break; // Y -> Ý
|
|
||||||
case 0x0079:
|
|
||||||
composed = 0x00FD;
|
|
||||||
break; // y -> ý
|
|
||||||
case 0x0043:
|
|
||||||
composed = 0x0106;
|
|
||||||
break; // C -> Ć
|
|
||||||
case 0x0063:
|
|
||||||
composed = 0x0107;
|
|
||||||
break; // c -> ć
|
|
||||||
case 0x004E:
|
|
||||||
composed = 0x0143;
|
|
||||||
break; // N -> Ń
|
|
||||||
case 0x006E:
|
|
||||||
composed = 0x0144;
|
|
||||||
break; // n -> ń
|
|
||||||
case 0x0053:
|
|
||||||
composed = 0x015A;
|
|
||||||
break; // S -> Ś
|
|
||||||
case 0x0073:
|
|
||||||
composed = 0x015B;
|
|
||||||
break; // s -> ś
|
|
||||||
case 0x005A:
|
|
||||||
composed = 0x0179;
|
|
||||||
break; // Z -> Ź
|
|
||||||
case 0x007A:
|
|
||||||
composed = 0x017A;
|
|
||||||
break; // z -> ź
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0302: // circumflex
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x00C2;
|
|
||||||
break; // A -> Â
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x00E2;
|
|
||||||
break; // a -> â
|
|
||||||
case 0x0045:
|
|
||||||
composed = 0x00CA;
|
|
||||||
break; // E -> Ê
|
|
||||||
case 0x0065:
|
|
||||||
composed = 0x00EA;
|
|
||||||
break; // e -> ê
|
|
||||||
case 0x0049:
|
|
||||||
composed = 0x00CE;
|
|
||||||
break; // I -> Î
|
|
||||||
case 0x0069:
|
|
||||||
composed = 0x00EE;
|
|
||||||
break; // i -> î
|
|
||||||
case 0x004F:
|
|
||||||
composed = 0x00D4;
|
|
||||||
break; // O -> Ô
|
|
||||||
case 0x006F:
|
|
||||||
composed = 0x00F4;
|
|
||||||
break; // o -> ô
|
|
||||||
case 0x0055:
|
|
||||||
composed = 0x00DB;
|
|
||||||
break; // U -> Û
|
|
||||||
case 0x0075:
|
|
||||||
composed = 0x00FB;
|
|
||||||
break; // u -> û
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0303: // tilde
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x00C3;
|
|
||||||
break; // A -> Ã
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x00E3;
|
|
||||||
break; // a -> ã
|
|
||||||
case 0x004E:
|
|
||||||
composed = 0x00D1;
|
|
||||||
break; // N -> Ñ
|
|
||||||
case 0x006E:
|
|
||||||
composed = 0x00F1;
|
|
||||||
break; // n -> ñ
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0308: // diaeresis/umlaut
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x00C4;
|
|
||||||
break; // A -> Ä
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x00E4;
|
|
||||||
break; // a -> ä
|
|
||||||
case 0x0045:
|
|
||||||
composed = 0x00CB;
|
|
||||||
break; // E -> Ë
|
|
||||||
case 0x0065:
|
|
||||||
composed = 0x00EB;
|
|
||||||
break; // e -> ë
|
|
||||||
case 0x0049:
|
|
||||||
composed = 0x00CF;
|
|
||||||
break; // I -> Ï
|
|
||||||
case 0x0069:
|
|
||||||
composed = 0x00EF;
|
|
||||||
break; // i -> ï
|
|
||||||
case 0x004F:
|
|
||||||
composed = 0x00D6;
|
|
||||||
break; // O -> Ö
|
|
||||||
case 0x006F:
|
|
||||||
composed = 0x00F6;
|
|
||||||
break; // o -> ö
|
|
||||||
case 0x0055:
|
|
||||||
composed = 0x00DC;
|
|
||||||
break; // U -> Ü
|
|
||||||
case 0x0075:
|
|
||||||
composed = 0x00FC;
|
|
||||||
break; // u -> ü
|
|
||||||
case 0x0059:
|
|
||||||
composed = 0x0178;
|
|
||||||
break; // Y -> Ÿ
|
|
||||||
case 0x0079:
|
|
||||||
composed = 0x00FF;
|
|
||||||
break; // y -> ÿ
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0307: // dot above (Polish Ż)
|
|
||||||
switch (prev) {
|
|
||||||
case 0x005A:
|
|
||||||
composed = 0x017B;
|
|
||||||
break; // Z -> Ż
|
|
||||||
case 0x007A:
|
|
||||||
composed = 0x017C;
|
|
||||||
break; // z -> ż
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0327: // cedilla
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0043:
|
|
||||||
composed = 0x00C7;
|
|
||||||
break; // C -> Ç
|
|
||||||
case 0x0063:
|
|
||||||
composed = 0x00E7;
|
|
||||||
break; // c -> ç
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0328: // ogonek (Polish Ą, Ę)
|
|
||||||
switch (prev) {
|
|
||||||
case 0x0041:
|
|
||||||
composed = 0x0104;
|
|
||||||
break; // A -> Ą
|
|
||||||
case 0x0061:
|
|
||||||
composed = 0x0105;
|
|
||||||
break; // a -> ą
|
|
||||||
case 0x0045:
|
|
||||||
composed = 0x0118;
|
|
||||||
break; // E -> Ę
|
|
||||||
case 0x0065:
|
|
||||||
composed = 0x0119;
|
|
||||||
break; // e -> ę
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (composed != 0) {
|
|
||||||
cps.back().value = composed;
|
|
||||||
continue; // skip pushing the combining mark itself
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cps.push_back({cp, static_cast<size_t>(current - base)});
|
|
||||||
}
|
|
||||||
|
|
||||||
return cps;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct CodepointInfo {
|
|
||||||
uint32_t value;
|
|
||||||
size_t byteOffset;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t toLowerLatin(uint32_t cp);
|
|
||||||
uint32_t toLowerCyrillic(uint32_t cp);
|
|
||||||
|
|
||||||
bool isLatinLetter(uint32_t cp);
|
|
||||||
bool isCyrillicLetter(uint32_t cp);
|
|
||||||
|
|
||||||
bool isAlphabetic(uint32_t cp);
|
|
||||||
bool isPunctuation(uint32_t cp);
|
|
||||||
bool isAsciiDigit(uint32_t cp);
|
|
||||||
bool isApostrophe(uint32_t cp);
|
|
||||||
bool isExplicitHyphen(uint32_t cp);
|
|
||||||
bool isSoftHyphen(uint32_t cp);
|
|
||||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps);
|
|
||||||
std::vector<CodepointInfo> collectCodepoints(const std::string& word);
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
#include "Hyphenator.h"
|
|
||||||
|
|
||||||
#include <Utf8.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "HyphenationCommon.h"
|
|
||||||
#include "LanguageHyphenator.h"
|
|
||||||
#include "LanguageRegistry.h"
|
|
||||||
|
|
||||||
const LanguageHyphenator* Hyphenator::cachedHyphenator_ = nullptr;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Normalize ISO 639-2 (three-letter) codes to ISO 639-1 (two-letter) codes used by the
|
|
||||||
// hyphenation registry. EPUBs may use either form in their dc:language metadata (e.g.
|
|
||||||
// "eng" instead of "en"). Both the bibliographic ("fre"/"ger") and terminological
|
|
||||||
// ("fra"/"deu") ISO 639-2 variants are mapped.
|
|
||||||
struct Iso639Mapping {
|
|
||||||
const char* iso639_2;
|
|
||||||
const char* iso639_1;
|
|
||||||
};
|
|
||||||
static constexpr Iso639Mapping kIso639Mappings[] = {{"eng", "en"}, {"fra", "fr"}, {"fre", "fr"}, {"deu", "de"},
|
|
||||||
{"ger", "de"}, {"rus", "ru"}, {"spa", "es"}, {"ita", "it"},
|
|
||||||
{"ukr", "uk"}, {"swe", "sv"}, {"fin", "fi"}};
|
|
||||||
|
|
||||||
// Maps a BCP-47 or ISO 639-2 language tag to a language-specific hyphenator.
|
|
||||||
const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
|
|
||||||
if (langTag.empty()) return nullptr;
|
|
||||||
|
|
||||||
// Extract primary subtag and normalize to lowercase (e.g., "en-US" -> "en", "ENG" -> "en").
|
|
||||||
std::string primary;
|
|
||||||
primary.reserve(langTag.size());
|
|
||||||
for (char c : langTag) {
|
|
||||||
if (c == '-' || c == '_') break;
|
|
||||||
if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
|
|
||||||
primary.push_back(c);
|
|
||||||
}
|
|
||||||
if (primary.empty()) return nullptr;
|
|
||||||
|
|
||||||
// Normalize ISO 639-2 three-letter codes to two-letter equivalents.
|
|
||||||
for (const auto& mapping : kIso639Mappings) {
|
|
||||||
if (primary == mapping.iso639_2) {
|
|
||||||
primary = mapping.iso639_1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getLanguageHyphenatorForPrimaryTag(primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps a codepoint index back to its byte offset inside the source word.
|
|
||||||
size_t byteOffsetForIndex(const std::vector<CodepointInfo>& cps, const size_t index) {
|
|
||||||
return (index < cps.size()) ? cps[index].byteOffset : (cps.empty() ? 0 : cps.back().byteOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builds a vector of break information from explicit hyphen markers in the given codepoints.
|
|
||||||
// Only hyphens that appear between two alphabetic characters are considered valid breaks.
|
|
||||||
//
|
|
||||||
// Example: "US-Satellitensystems" (cps: U, S, -, S, a, t, ...)
|
|
||||||
// -> finds '-' at index 2 with alphabetic neighbors 'S' and 'S'
|
|
||||||
// -> returns one BreakInfo at the byte offset of 'S' (the char after '-'),
|
|
||||||
// with requiresInsertedHyphen=false because '-' is already visible.
|
|
||||||
//
|
|
||||||
// Example: "Satel\u00ADliten" (soft-hyphen between 'l' and 'l')
|
|
||||||
// -> returns one BreakInfo with requiresInsertedHyphen=true (soft-hyphen
|
|
||||||
// is invisible and needs a visible '-' when the break is used).
|
|
||||||
std::vector<Hyphenator::BreakInfo> buildExplicitBreakInfos(const std::vector<CodepointInfo>& cps) {
|
|
||||||
std::vector<Hyphenator::BreakInfo> breaks;
|
|
||||||
|
|
||||||
for (size_t i = 1; i + 1 < cps.size(); ++i) {
|
|
||||||
const uint32_t cp = cps[i].value;
|
|
||||||
if (!isExplicitHyphen(cp) || !isAlphabetic(cps[i - 1].value) || !isAlphabetic(cps[i + 1].value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Offset points to the next codepoint so rendering starts after the hyphen marker.
|
|
||||||
breaks.push_back({cps[i + 1].byteOffset, isSoftHyphen(cp)});
|
|
||||||
}
|
|
||||||
|
|
||||||
return breaks;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSegmentSeparator(const uint32_t cp) { return isExplicitHyphen(cp) || isApostrophe(cp); }
|
|
||||||
|
|
||||||
void appendSegmentPatternBreaks(const std::vector<CodepointInfo>& cps, const LanguageHyphenator& hyphenator,
|
|
||||||
const bool includeFallback, std::vector<Hyphenator::BreakInfo>& outBreaks) {
|
|
||||||
size_t segStart = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i <= cps.size(); ++i) {
|
|
||||||
const bool atEnd = i == cps.size();
|
|
||||||
const bool atSeparator = !atEnd && isSegmentSeparator(cps[i].value);
|
|
||||||
if (!atEnd && !atSeparator) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > segStart) {
|
|
||||||
std::vector<CodepointInfo> segment(cps.begin() + segStart, cps.begin() + i);
|
|
||||||
auto segIndexes = hyphenator.breakIndexes(segment);
|
|
||||||
|
|
||||||
if (includeFallback && segIndexes.empty()) {
|
|
||||||
const size_t minPrefix = hyphenator.minPrefix();
|
|
||||||
const size_t minSuffix = hyphenator.minSuffix();
|
|
||||||
for (size_t idx = minPrefix; idx + minSuffix <= segment.size(); ++idx) {
|
|
||||||
segIndexes.push_back(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const size_t idx : segIndexes) {
|
|
||||||
assert(idx > 0 && idx < segment.size());
|
|
||||||
if (idx == 0 || idx >= segment.size()) continue;
|
|
||||||
const size_t cpIdx = segStart + idx;
|
|
||||||
if (cpIdx < cps.size()) {
|
|
||||||
outBreaks.push_back({cps[cpIdx].byteOffset, true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
segStart = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void appendApostropheContractionBreaks(const std::vector<CodepointInfo>& cps,
|
|
||||||
std::vector<Hyphenator::BreakInfo>& outBreaks) {
|
|
||||||
constexpr size_t kMinLeftSegmentLen = 3;
|
|
||||||
constexpr size_t kMinRightSegmentLen = 3;
|
|
||||||
size_t segmentStart = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < cps.size(); ++i) {
|
|
||||||
if (isSegmentSeparator(cps[i].value)) {
|
|
||||||
if (isApostrophe(cps[i].value) && i > 0 && i + 1 < cps.size() && isAlphabetic(cps[i - 1].value) &&
|
|
||||||
isAlphabetic(cps[i + 1].value)) {
|
|
||||||
size_t leftPrefixLen = 0;
|
|
||||||
for (size_t j = segmentStart; j < i; ++j) {
|
|
||||||
if (isAlphabetic(cps[j].value)) {
|
|
||||||
++leftPrefixLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t rightSuffixLen = 0;
|
|
||||||
for (size_t j = i + 1; j < cps.size() && !isSegmentSeparator(cps[j].value); ++j) {
|
|
||||||
if (isAlphabetic(cps[j].value)) {
|
|
||||||
++rightSuffixLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid stranding short clitics like "l'"/"d'" or contraction tails like "'ve"/"'re"/"'ll".
|
|
||||||
if (leftPrefixLen >= kMinLeftSegmentLen && rightSuffixLen >= kMinRightSegmentLen) {
|
|
||||||
outBreaks.push_back({cps[i + 1].byteOffset, false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segmentStart = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sortAndDedupeBreakInfos(std::vector<Hyphenator::BreakInfo>& infos) {
|
|
||||||
std::sort(infos.begin(), infos.end(), [](const Hyphenator::BreakInfo& a, const Hyphenator::BreakInfo& b) {
|
|
||||||
if (a.byteOffset != b.byteOffset) {
|
|
||||||
return a.byteOffset < b.byteOffset;
|
|
||||||
}
|
|
||||||
return a.requiresInsertedHyphen < b.requiresInsertedHyphen;
|
|
||||||
});
|
|
||||||
|
|
||||||
infos.erase(std::unique(infos.begin(), infos.end(),
|
|
||||||
[](const Hyphenator::BreakInfo& a, const Hyphenator::BreakInfo& b) {
|
|
||||||
return a.byteOffset == b.byteOffset;
|
|
||||||
}),
|
|
||||||
infos.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::vector<Hyphenator::BreakInfo> Hyphenator::breakOffsets(const std::string& word, const bool includeFallback) {
|
|
||||||
if (word.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to codepoints and normalize word boundaries.
|
|
||||||
auto cps = collectCodepoints(word);
|
|
||||||
trimSurroundingPunctuationAndFootnote(cps);
|
|
||||||
const auto* hyphenator = cachedHyphenator_;
|
|
||||||
|
|
||||||
// Detect apostrophe-like separators early; used by both branches below.
|
|
||||||
bool hasApostropheLikeSeparator = false;
|
|
||||||
for (const auto& cp : cps) {
|
|
||||||
if (isApostrophe(cp.value)) {
|
|
||||||
hasApostropheLikeSeparator = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit hyphen markers (soft or hard) take precedence over language breaks.
|
|
||||||
auto explicitBreakInfos = buildExplicitBreakInfos(cps);
|
|
||||||
if (!explicitBreakInfos.empty()) {
|
|
||||||
// When a word contains explicit hyphens we also run Liang patterns on each alphabetic
|
|
||||||
// segment between them. Without this, "US-Satellitensystems" would only offer one split
|
|
||||||
// point (after "US-"), making it impossible to break mid-"Satellitensystems" even when
|
|
||||||
// "US-Satelliten-" would fit on the line.
|
|
||||||
//
|
|
||||||
// Example: "US-Satellitensystems"
|
|
||||||
// Segments: ["US", "Satellitensystems"]
|
|
||||||
// Explicit break: after "US-" -> @3 (no inserted hyphen)
|
|
||||||
// Pattern breaks on "Satellitensystems" -> @5 Sa|tel (+hyphen)
|
|
||||||
// @8 Satel|li (+hyphen)
|
|
||||||
// @10 Satelli|ten (+hyphen)
|
|
||||||
// @13 Satelliten|sys (+hyphen)
|
|
||||||
// @16 Satellitensys|tems (+hyphen)
|
|
||||||
// Result: 6 sorted break points; the line-breaker picks the widest prefix that fits.
|
|
||||||
if (hyphenator) {
|
|
||||||
appendSegmentPatternBreaks(cps, *hyphenator, /*includeFallback=*/false, explicitBreakInfos);
|
|
||||||
}
|
|
||||||
// Also add apostrophe contraction breaks when present (e.g. "l'état-major"
|
|
||||||
// has both an explicit hyphen and an apostrophe that can independently break).
|
|
||||||
if (hasApostropheLikeSeparator) {
|
|
||||||
appendApostropheContractionBreaks(cps, explicitBreakInfos);
|
|
||||||
}
|
|
||||||
// Merge all break points into ascending byte-offset order.
|
|
||||||
sortAndDedupeBreakInfos(explicitBreakInfos);
|
|
||||||
return explicitBreakInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apostrophe-like separators split compounds into alphabetic segments; run Liang on each segment.
|
|
||||||
// This allows words like "all'improvviso" to hyphenate within "improvviso" instead of becoming
|
|
||||||
// completely unsplittable due to the apostrophe punctuation. Apostrophe contraction breaks are
|
|
||||||
// applied regardless of whether a language hyphenator is available.
|
|
||||||
if (hasApostropheLikeSeparator) {
|
|
||||||
std::vector<BreakInfo> segmentedBreaks;
|
|
||||||
if (hyphenator) {
|
|
||||||
appendSegmentPatternBreaks(cps, *hyphenator, includeFallback, segmentedBreaks);
|
|
||||||
}
|
|
||||||
appendApostropheContractionBreaks(cps, segmentedBreaks);
|
|
||||||
sortAndDedupeBreakInfos(segmentedBreaks);
|
|
||||||
return segmentedBreaks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask language hyphenator for legal break points.
|
|
||||||
std::vector<size_t> indexes;
|
|
||||||
if (hyphenator) {
|
|
||||||
indexes = hyphenator->breakIndexes(cps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add fallback breaks if needed
|
|
||||||
if (includeFallback && indexes.empty()) {
|
|
||||||
const size_t minPrefix = hyphenator ? hyphenator->minPrefix() : LiangWordConfig::kDefaultMinPrefix;
|
|
||||||
const size_t minSuffix = hyphenator ? hyphenator->minSuffix() : LiangWordConfig::kDefaultMinSuffix;
|
|
||||||
for (size_t idx = minPrefix; idx + minSuffix <= cps.size(); ++idx) {
|
|
||||||
indexes.push_back(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexes.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Hyphenator::BreakInfo> breaks;
|
|
||||||
breaks.reserve(indexes.size());
|
|
||||||
for (const size_t idx : indexes) {
|
|
||||||
// CJK characters can break without inserting a visible hyphen.
|
|
||||||
// Check the codepoint at the break position: if it's a CJK character,
|
|
||||||
// no hyphen is needed since CJK scripts don't use hyphenation.
|
|
||||||
bool needsHyphen = true;
|
|
||||||
if (idx < cps.size() && utf8IsCjkBreakable(cps[idx].value)) {
|
|
||||||
needsHyphen = false;
|
|
||||||
} else if (idx > 0 && utf8IsCjkBreakable(cps[idx - 1].value)) {
|
|
||||||
needsHyphen = false;
|
|
||||||
}
|
|
||||||
breaks.push_back({byteOffsetForIndex(cps, idx), needsHyphen});
|
|
||||||
}
|
|
||||||
|
|
||||||
return breaks;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Hyphenator::setPreferredLanguage(const std::string& lang) { cachedHyphenator_ = hyphenatorForLanguage(lang); }
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class LanguageHyphenator;
|
|
||||||
|
|
||||||
class Hyphenator {
|
|
||||||
public:
|
|
||||||
struct BreakInfo {
|
|
||||||
size_t byteOffset; // Byte position inside the UTF-8 word where a break may occur.
|
|
||||||
bool requiresInsertedHyphen; // true = a visible '-' must be rendered at the break (pattern/fallback breaks).
|
|
||||||
// false = break occurs at an existing visible separator boundary
|
|
||||||
// (explicit '-' or eligible apostrophe contraction boundary).
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns byte offsets where the word may be hyphenated.
|
|
||||||
//
|
|
||||||
// Break sources (in priority order):
|
|
||||||
// 1. Explicit hyphens already present in the word (e.g. '-' or soft-hyphen U+00AD).
|
|
||||||
// When found, language patterns are additionally run on each alphabetic segment
|
|
||||||
// between separators so compound words can break within their parts.
|
|
||||||
// Example: "US-Satellitensystems" yields breaks after "US-" (no inserted hyphen)
|
|
||||||
// plus pattern breaks inside "Satellitensystems" (Sa|tel|li|ten|sys|tems).
|
|
||||||
// 2. Apostrophe contractions between letters (e.g. all'improvviso).
|
|
||||||
// Liang patterns are run per alphabetic segment around apostrophes.
|
|
||||||
// A direct break at the apostrophe boundary is allowed only when the left
|
|
||||||
// segment has at least 3 letters and the right segment has at least 3 letters,
|
|
||||||
// avoiding short clitics (e.g. l', d') and contraction tails (e.g. 've, 're, 'll).
|
|
||||||
// 3. Language-specific Liang patterns (e.g. German de_patterns).
|
|
||||||
// Example: "Quadratkilometer" -> Qua|drat|ki|lo|me|ter.
|
|
||||||
// 4. Fallback every-N-chars splitting (only when includeFallback is true AND no
|
|
||||||
// pattern breaks were found). Used as a last resort to prevent a single oversized
|
|
||||||
// word from overflowing the page width.
|
|
||||||
static std::vector<BreakInfo> breakOffsets(const std::string& word, bool includeFallback);
|
|
||||||
|
|
||||||
// Provide a publication-level language hint (e.g. "en", "en-US", "ru") used to select hyphenation rules.
|
|
||||||
static void setPreferredLanguage(const std::string& lang);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const LanguageHyphenator* cachedHyphenator_;
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "LiangHyphenation.h"
|
|
||||||
|
|
||||||
// Generic Liang-backed hyphenator that stores pattern metadata plus language-specific helpers.
|
|
||||||
class LanguageHyphenator {
|
|
||||||
public:
|
|
||||||
LanguageHyphenator(const SerializedHyphenationPatterns& patterns, bool (*isLetterFn)(uint32_t),
|
|
||||||
uint32_t (*toLowerFn)(uint32_t), size_t minPrefix = LiangWordConfig::kDefaultMinPrefix,
|
|
||||||
size_t minSuffix = LiangWordConfig::kDefaultMinSuffix)
|
|
||||||
: patterns_(patterns), config_(isLetterFn, toLowerFn, minPrefix, minSuffix) {}
|
|
||||||
|
|
||||||
std::vector<size_t> breakIndexes(const std::vector<CodepointInfo>& cps) const {
|
|
||||||
return liangBreakIndexes(cps, patterns_, config_);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t minPrefix() const { return config_.minPrefix; }
|
|
||||||
size_t minSuffix() const { return config_.minSuffix; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const SerializedHyphenationPatterns& patterns_;
|
|
||||||
LiangWordConfig config_;
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#include "LanguageRegistry.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "HyphenationCommon.h"
|
|
||||||
#include "generated/hyph-de.trie.h"
|
|
||||||
#include "generated/hyph-en.trie.h"
|
|
||||||
#include "generated/hyph-es.trie.h"
|
|
||||||
#include "generated/hyph-fi.trie.h"
|
|
||||||
#include "generated/hyph-fr.trie.h"
|
|
||||||
#include "generated/hyph-it.trie.h"
|
|
||||||
#include "generated/hyph-pl.trie.h"
|
|
||||||
#include "generated/hyph-ru.trie.h"
|
|
||||||
#include "generated/hyph-sv.trie.h"
|
|
||||||
#include "generated/hyph-uk.trie.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// English hyphenation patterns (3/3 minimum prefix/suffix length)
|
|
||||||
LanguageHyphenator englishHyphenator(en_patterns, isLatinLetter, toLowerLatin, 3, 3);
|
|
||||||
LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator russianHyphenator(ru_patterns, isCyrillicLetter, toLowerCyrillic);
|
|
||||||
LanguageHyphenator spanishHyphenator(es_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator italianHyphenator(it_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator swedishHyphenator(sv_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator ukrainianHyphenator(uk_patterns, isCyrillicLetter, toLowerCyrillic);
|
|
||||||
LanguageHyphenator polishHyphenator(pl_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
LanguageHyphenator finnishHyphenator(fi_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
|
|
||||||
using EntryArray = std::array<LanguageEntry, 10>;
|
|
||||||
|
|
||||||
const EntryArray& entries() {
|
|
||||||
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
|
||||||
{"french", "fr", &frenchHyphenator},
|
|
||||||
{"german", "de", &germanHyphenator},
|
|
||||||
{"russian", "ru", &russianHyphenator},
|
|
||||||
{"spanish", "es", &spanishHyphenator},
|
|
||||||
{"italian", "it", &italianHyphenator},
|
|
||||||
{"polish", "pl", &polishHyphenator},
|
|
||||||
{"swedish", "sv", &swedishHyphenator},
|
|
||||||
{"ukrainian", "uk", &ukrainianHyphenator},
|
|
||||||
{"finnish", "fi", &finnishHyphenator}}};
|
|
||||||
return kEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string& primaryTag) {
|
|
||||||
const auto& allEntries = entries();
|
|
||||||
const auto it = std::find_if(allEntries.begin(), allEntries.end(),
|
|
||||||
[&primaryTag](const LanguageEntry& entry) { return primaryTag == entry.primaryTag; });
|
|
||||||
return (it != allEntries.end()) ? it->hyphenator : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
LanguageEntryView getLanguageEntries() {
|
|
||||||
const auto& allEntries = entries();
|
|
||||||
return LanguageEntryView{allEntries.data(), allEntries.size()};
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "LanguageHyphenator.h"
|
|
||||||
|
|
||||||
struct LanguageEntry {
|
|
||||||
const char* cliName;
|
|
||||||
const char* primaryTag;
|
|
||||||
const LanguageHyphenator* hyphenator;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LanguageEntryView {
|
|
||||||
const LanguageEntry* data;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
const LanguageEntry* begin() const { return data; }
|
|
||||||
const LanguageEntry* end() const { return data + size; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns the Liang-backed hyphenator for a given primary language tag (e.g., "en", "fr").
|
|
||||||
const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string& primaryTag);
|
|
||||||
|
|
||||||
// Exposes the list of supported languages primarily for tooling/tests.
|
|
||||||
LanguageEntryView getLanguageEntries();
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
#include "LiangHyphenation.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Liang hyphenation pipeline overview (Typst-style binary trie variant)
|
|
||||||
* --------------------------------------------------------------------
|
|
||||||
* 1. Input normalization (buildAugmentedWord)
|
|
||||||
* - Accepts a vector of CodepointInfo structs emitted by the EPUB text
|
|
||||||
* parser. Each codepoint is validated with LiangWordConfig::isLetter so
|
|
||||||
* we abort early on digits, punctuation, etc. If the word is valid we
|
|
||||||
* build an "augmented" byte sequence: leading '.', lowercase UTF-8 bytes
|
|
||||||
* for every letter, then a trailing '.'. While doing this we capture the
|
|
||||||
* UTF-8 byte offset for each character and a reverse lookup table that
|
|
||||||
* maps UTF-8 byte indexes back to codepoint indexes. This lets the rest
|
|
||||||
* of the algorithm stay byte-oriented (matching the serialized automaton)
|
|
||||||
* while still emitting hyphen positions in codepoint space.
|
|
||||||
*
|
|
||||||
* 2. Automaton decoding
|
|
||||||
* - SerializedHyphenationPatterns stores a contiguous blob generated from
|
|
||||||
* Typst's binary tries. The first 4 bytes contain the root offset. Each
|
|
||||||
* node packs transitions, variable-stride relative offsets to child
|
|
||||||
* nodes, and an optional pointer into a shared "levels" list. We parse
|
|
||||||
* that layout lazily via decodeState/transition, keeping everything in
|
|
||||||
* flash memory; no heap allocations besides the stack-local AutomatonState
|
|
||||||
* structs. getAutomaton caches parseAutomaton results per blob pointer so
|
|
||||||
* multiple words hitting the same language only pay the cost once.
|
|
||||||
*
|
|
||||||
* 3. Pattern application
|
|
||||||
* - We walk the augmented bytes left-to-right. For each starting byte we
|
|
||||||
* stream transitions through the trie, terminating when a transition
|
|
||||||
* fails. Whenever a node exposes level data we expand the packed
|
|
||||||
* "dist+level" bytes: `dist` is the delta (in UTF-8 bytes) from the
|
|
||||||
* starting cursor and `level` is the Liang priority digit. Using the
|
|
||||||
* byte→codepoint lookup we mark the corresponding index in `scores`.
|
|
||||||
* Scores are only updated if the new level is higher, mirroring Liang's
|
|
||||||
* "max digit wins" rule.
|
|
||||||
*
|
|
||||||
* 4. Output filtering
|
|
||||||
* - collectBreakIndexes converts odd-valued score entries back to codepoint
|
|
||||||
* break positions while enforcing `minPrefix`/`minSuffix` constraints from
|
|
||||||
* LiangWordConfig. The caller (language-specific hyphenators) can then
|
|
||||||
* translate these indexes into renderer glyph offsets, page layout data,
|
|
||||||
* etc.
|
|
||||||
*
|
|
||||||
* Keeping the entire algorithm small and deterministic is critical on the
|
|
||||||
* ESP32-C3: we avoid recursion, dynamic allocations per node, or copying the
|
|
||||||
* trie. All lookups stay within the generated blob, which lives in flash, and
|
|
||||||
* the working buffers (augmented bytes/scores) scale with the word length rather
|
|
||||||
* than the pattern corpus.
|
|
||||||
*
|
|
||||||
* Memory design note (heap fragmentation avoidance)
|
|
||||||
* --------------------------------------------------
|
|
||||||
* AugmentedWord previously held three std::vector<> members that were heap-
|
|
||||||
* allocated and freed for every word during layout. For a German-language section
|
|
||||||
* with hundreds of words, these thousands of small alloc/free cycles fragment
|
|
||||||
* the heap enough to prevent large contiguous allocations (e.g. a 32 KB inflate
|
|
||||||
* ring buffer) even when total free memory is sufficient.
|
|
||||||
*
|
|
||||||
* The fix replaces those vectors with fixed-size C arrays sized for the longest
|
|
||||||
* plausible word. The longest known German word is ~63 codepoints; with up to
|
|
||||||
* 2 UTF-8 bytes per German letter + 2 sentinel dots = 128 bytes. MAX_WORD_BYTES=160
|
|
||||||
* and MAX_WORD_CHARS=70 give comfortable headroom. Words exceeding these limits
|
|
||||||
* are silently skipped (no hyphenation), which is acceptable for correctness.
|
|
||||||
* The struct lives on the render-task stack (8 KB) so no permanent DRAM is wasted.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
using EmbeddedAutomaton = SerializedHyphenationPatterns;
|
|
||||||
|
|
||||||
// Upper bounds for the fixed word buffers. Sized for German (longest known word
|
|
||||||
// ≈63 codepoints × 2 UTF-8 bytes + 2 sentinel dots = 128 bytes). Words that
|
|
||||||
// exceed these limits are skipped rather than heap-allocated.
|
|
||||||
static constexpr size_t MAX_WORD_BYTES = 160; // max UTF-8 bytes in augmented word
|
|
||||||
static constexpr size_t MAX_WORD_CHARS = 70; // max codepoints + 2 sentinel dots
|
|
||||||
|
|
||||||
struct AugmentedWord {
|
|
||||||
uint8_t bytes[MAX_WORD_BYTES];
|
|
||||||
size_t charByteOffsets[MAX_WORD_CHARS];
|
|
||||||
int32_t byteToCharIndex[MAX_WORD_BYTES];
|
|
||||||
size_t byteLen = 0;
|
|
||||||
size_t charCount_ = 0;
|
|
||||||
|
|
||||||
bool empty() const { return byteLen == 0; }
|
|
||||||
size_t charCount() const { return charCount_; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encode a single Unicode codepoint into UTF-8 and append to word.bytes[].
|
|
||||||
// Returns the number of bytes written, or 0 if the codepoint is invalid or the
|
|
||||||
// buffer would overflow. Surrogates (0xD800–0xDFFF) and values above 0x10FFFF
|
|
||||||
// are not valid Unicode scalar values and are rejected.
|
|
||||||
size_t encodeUtf8(uint32_t cp, AugmentedWord& word) {
|
|
||||||
if ((cp >= 0xD800u && cp <= 0xDFFFu) || cp > 0x10FFFFu) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t encoded[4];
|
|
||||||
size_t len = 0;
|
|
||||||
|
|
||||||
if (cp <= 0x7Fu) {
|
|
||||||
encoded[len++] = static_cast<uint8_t>(cp);
|
|
||||||
} else if (cp <= 0x7FFu) {
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0xC0u | ((cp >> 6) & 0x1Fu));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | (cp & 0x3Fu));
|
|
||||||
} else if (cp <= 0xFFFFu) {
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0xE0u | ((cp >> 12) & 0x0Fu));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | ((cp >> 6) & 0x3Fu));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | (cp & 0x3Fu));
|
|
||||||
} else {
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0xF0u | ((cp >> 18) & 0x07u));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | ((cp >> 12) & 0x3Fu));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | ((cp >> 6) & 0x3Fu));
|
|
||||||
encoded[len++] = static_cast<uint8_t>(0x80u | (cp & 0x3Fu));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (word.byteLen + len > MAX_WORD_BYTES) {
|
|
||||||
return 0; // overflow: word too long for fixed buffer, skip hyphenation
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < len; ++i) {
|
|
||||||
word.bytes[word.byteLen++] = encoded[i];
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the dotted, lowercase UTF-8 representation plus lookup tables into `word`.
|
|
||||||
// Returns false if the word should be skipped (empty, non-letter, or too long).
|
|
||||||
bool buildAugmentedWord(AugmentedWord& word, const std::vector<CodepointInfo>& cps, const LiangWordConfig& config) {
|
|
||||||
word.byteLen = 0;
|
|
||||||
word.charCount_ = 0;
|
|
||||||
|
|
||||||
if (cps.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leading sentinel '.'
|
|
||||||
word.charByteOffsets[word.charCount_++] = 0;
|
|
||||||
word.bytes[word.byteLen++] = '.';
|
|
||||||
|
|
||||||
for (const auto& info : cps) {
|
|
||||||
if (!config.isLetter(info.value)) {
|
|
||||||
word.byteLen = 0;
|
|
||||||
word.charCount_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Reserve one slot for the trailing sentinel and check byte headroom.
|
|
||||||
if (word.charCount_ >= MAX_WORD_CHARS - 1) {
|
|
||||||
word.byteLen = 0;
|
|
||||||
word.charCount_ = 0;
|
|
||||||
return false; // word too long
|
|
||||||
}
|
|
||||||
word.charByteOffsets[word.charCount_++] = word.byteLen;
|
|
||||||
if (encodeUtf8(config.toLower(info.value), word) == 0) {
|
|
||||||
word.byteLen = 0;
|
|
||||||
word.charCount_ = 0;
|
|
||||||
return false; // byte buffer overflow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trailing sentinel '.'
|
|
||||||
if (word.charCount_ >= MAX_WORD_CHARS || word.byteLen >= MAX_WORD_BYTES) {
|
|
||||||
word.byteLen = 0;
|
|
||||||
word.charCount_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
word.charByteOffsets[word.charCount_++] = word.byteLen;
|
|
||||||
word.bytes[word.byteLen++] = '.';
|
|
||||||
|
|
||||||
// Build byte→char reverse index: -1 for mid-codepoint bytes, char index for start bytes.
|
|
||||||
for (size_t i = 0; i < word.byteLen; ++i) {
|
|
||||||
word.byteToCharIndex[i] = -1;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < word.charCount_; ++i) {
|
|
||||||
const size_t offset = word.charByteOffsets[i];
|
|
||||||
if (offset < word.byteLen) {
|
|
||||||
word.byteToCharIndex[offset] = static_cast<int32_t>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoded view of a single trie node pulled straight out of the serialized blob.
|
|
||||||
// - transitions: contiguous list of next-byte values
|
|
||||||
// - targets: packed relative offsets (1/2/3 bytes) for each transition
|
|
||||||
// - levels: optional pointer into the global levels list with packed dist/level pairs
|
|
||||||
struct AutomatonState {
|
|
||||||
const uint8_t* data = nullptr;
|
|
||||||
size_t size = 0;
|
|
||||||
size_t addr = 0;
|
|
||||||
uint8_t stride = 1;
|
|
||||||
size_t childCount = 0;
|
|
||||||
const uint8_t* transitions = nullptr;
|
|
||||||
const uint8_t* targets = nullptr;
|
|
||||||
const uint8_t* levels = nullptr;
|
|
||||||
size_t levelsLen = 0;
|
|
||||||
|
|
||||||
bool valid() const { return data != nullptr; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpret the node located at `addr`, returning transition metadata.
|
|
||||||
AutomatonState decodeState(const EmbeddedAutomaton& automaton, size_t addr) {
|
|
||||||
AutomatonState state;
|
|
||||||
if (addr >= automaton.size) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* base = automaton.data + addr;
|
|
||||||
size_t remaining = automaton.size - addr;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
const uint8_t header = base[pos++];
|
|
||||||
// Header layout (bits):
|
|
||||||
// 7 - hasLevels flag
|
|
||||||
// 6..5 - stride selector (0 -> 1 byte, otherwise 1|2|3)
|
|
||||||
// 4..0 - child count (5 bits), 31 == overflow -> extra byte
|
|
||||||
const bool hasLevels = (header >> 7) != 0;
|
|
||||||
uint8_t stride = static_cast<uint8_t>((header >> 5) & 0x03u);
|
|
||||||
if (stride == 0) {
|
|
||||||
stride = 1;
|
|
||||||
}
|
|
||||||
size_t childCount = static_cast<size_t>(header & 0x1Fu);
|
|
||||||
if (childCount == 31u) {
|
|
||||||
if (pos >= remaining) {
|
|
||||||
return AutomatonState{};
|
|
||||||
}
|
|
||||||
childCount = base[pos++];
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* levelsPtr = nullptr;
|
|
||||||
size_t levelsLen = 0;
|
|
||||||
if (hasLevels) {
|
|
||||||
if (pos + 1 >= remaining) {
|
|
||||||
return AutomatonState{};
|
|
||||||
}
|
|
||||||
const uint8_t offsetHi = base[pos++];
|
|
||||||
const uint8_t offsetLoLen = base[pos++];
|
|
||||||
// The 12-bit offset (hi<<4 | top nibble) points into the blob-level levels list.
|
|
||||||
// The bottom nibble stores how many packed entries belong to this node.
|
|
||||||
const size_t offset = (static_cast<size_t>(offsetHi) << 4) | (offsetLoLen >> 4);
|
|
||||||
levelsLen = offsetLoLen & 0x0Fu;
|
|
||||||
if (offset + levelsLen > automaton.size) {
|
|
||||||
return AutomatonState{};
|
|
||||||
}
|
|
||||||
levelsPtr = automaton.data + offset - 4u;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos + childCount > remaining) {
|
|
||||||
return AutomatonState{};
|
|
||||||
}
|
|
||||||
const uint8_t* transitions = base + pos;
|
|
||||||
pos += childCount;
|
|
||||||
|
|
||||||
const size_t targetsBytes = childCount * stride;
|
|
||||||
if (pos + targetsBytes > remaining) {
|
|
||||||
return AutomatonState{};
|
|
||||||
}
|
|
||||||
const uint8_t* targets = base + pos;
|
|
||||||
|
|
||||||
state.data = automaton.data;
|
|
||||||
state.size = automaton.size;
|
|
||||||
state.addr = addr;
|
|
||||||
state.stride = stride;
|
|
||||||
state.childCount = childCount;
|
|
||||||
state.transitions = transitions;
|
|
||||||
state.targets = targets;
|
|
||||||
state.levels = levelsPtr;
|
|
||||||
state.levelsLen = levelsLen;
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the packed stride-sized delta back into a signed offset.
|
|
||||||
int32_t decodeDelta(const uint8_t* buf, uint8_t stride) {
|
|
||||||
if (stride == 1) {
|
|
||||||
return static_cast<int8_t>(buf[0]);
|
|
||||||
}
|
|
||||||
if (stride == 2) {
|
|
||||||
return static_cast<int16_t>((static_cast<uint16_t>(buf[0]) << 8) | static_cast<uint16_t>(buf[1]));
|
|
||||||
}
|
|
||||||
const int32_t unsignedVal =
|
|
||||||
(static_cast<int32_t>(buf[0]) << 16) | (static_cast<int32_t>(buf[1]) << 8) | static_cast<int32_t>(buf[2]);
|
|
||||||
return unsignedVal - (1 << 23);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow a single byte transition from `state`, decoding the child node on success.
|
|
||||||
bool transition(const EmbeddedAutomaton& automaton, const AutomatonState& state, uint8_t letter, AutomatonState& out) {
|
|
||||||
if (!state.valid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Children remain sorted by letter in the serialized blob, but the lists are
|
|
||||||
// short enough that a linear scan keeps code size down compared to binary search.
|
|
||||||
for (size_t idx = 0; idx < state.childCount; ++idx) {
|
|
||||||
if (state.transitions[idx] != letter) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const uint8_t* deltaPtr = state.targets + idx * state.stride;
|
|
||||||
const int32_t delta = decodeDelta(deltaPtr, state.stride);
|
|
||||||
// Deltas are relative to the current node's address, allowing us to keep all
|
|
||||||
// targets within 24 bits while still referencing further nodes in the blob.
|
|
||||||
const int64_t nextAddr = static_cast<int64_t>(state.addr) + delta;
|
|
||||||
if (nextAddr < 0 || static_cast<size_t>(nextAddr) >= automaton.size) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
out = decodeState(automaton, static_cast<size_t>(nextAddr));
|
|
||||||
return out.valid();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts odd score positions back into codepoint indexes, honoring min prefix/suffix constraints.
|
|
||||||
// Each break corresponds to scores[breakIndex + 1] because of the leading '.' sentinel.
|
|
||||||
// Convert odd score entries into hyphen positions while honoring prefix/suffix limits.
|
|
||||||
std::vector<size_t> collectBreakIndexes(const std::vector<CodepointInfo>& cps, const uint8_t* scores,
|
|
||||||
const size_t scoresSize, const size_t minPrefix, const size_t minSuffix) {
|
|
||||||
std::vector<size_t> indexes;
|
|
||||||
const size_t cpCount = cps.size();
|
|
||||||
if (cpCount < 2) {
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t breakIndex = 1; breakIndex < cpCount; ++breakIndex) {
|
|
||||||
if (breakIndex < minPrefix) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t suffixCount = cpCount - breakIndex;
|
|
||||||
if (suffixCount < minSuffix) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t scoreIdx = breakIndex + 1;
|
|
||||||
if (scoreIdx >= scoresSize) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((scores[scoreIdx] & 1u) == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
indexes.push_back(breakIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// Entry point that runs the full Liang pipeline for a single word.
|
|
||||||
std::vector<size_t> liangBreakIndexes(const std::vector<CodepointInfo>& cps,
|
|
||||||
const SerializedHyphenationPatterns& patterns, const LiangWordConfig& config) {
|
|
||||||
// AugmentedWord uses fixed-size C arrays (no heap allocation) to avoid
|
|
||||||
// fragmenting the heap across hundreds of words during page layout.
|
|
||||||
AugmentedWord augmented;
|
|
||||||
if (!buildAugmentedWord(augmented, cps, config)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmbeddedAutomaton& automaton = patterns;
|
|
||||||
|
|
||||||
const AutomatonState root = decodeState(automaton, automaton.rootOffset);
|
|
||||||
if (!root.valid()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Liang scores: one entry per augmented char (leading/trailing dots included).
|
|
||||||
// Stack-allocated to avoid heap fragmentation (see memory design note above).
|
|
||||||
uint8_t scores[MAX_WORD_CHARS];
|
|
||||||
for (size_t i = 0; i < augmented.charCount_; ++i) {
|
|
||||||
scores[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk every starting character position and stream bytes through the trie.
|
|
||||||
for (size_t charStart = 0; charStart < augmented.charCount_; ++charStart) {
|
|
||||||
const size_t byteStart = augmented.charByteOffsets[charStart];
|
|
||||||
AutomatonState state = root;
|
|
||||||
|
|
||||||
for (size_t cursor = byteStart; cursor < augmented.byteLen; ++cursor) {
|
|
||||||
AutomatonState next;
|
|
||||||
if (!transition(automaton, state, augmented.bytes[cursor], next)) {
|
|
||||||
break; // No more matches for this prefix.
|
|
||||||
}
|
|
||||||
state = next;
|
|
||||||
|
|
||||||
if (state.levels && state.levelsLen > 0) {
|
|
||||||
size_t offset = 0;
|
|
||||||
// Each packed byte stores the byte-distance delta and the Liang level digit.
|
|
||||||
for (size_t i = 0; i < state.levelsLen; ++i) {
|
|
||||||
const uint8_t packed = state.levels[i];
|
|
||||||
const size_t dist = static_cast<size_t>(packed / 10);
|
|
||||||
const uint8_t level = static_cast<uint8_t>(packed % 10);
|
|
||||||
|
|
||||||
offset += dist;
|
|
||||||
const size_t splitByte = byteStart + offset;
|
|
||||||
if (splitByte >= augmented.byteLen) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int32_t boundary = augmented.byteToCharIndex[splitByte];
|
|
||||||
if (boundary < 0) {
|
|
||||||
continue; // Mid-codepoint byte, wait for the next one.
|
|
||||||
}
|
|
||||||
if (boundary < 2 || boundary + 2 > static_cast<int32_t>(augmented.charCount_)) {
|
|
||||||
continue; // Skip splits that land in the leading/trailing sentinels.
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t idx = static_cast<size_t>(boundary);
|
|
||||||
if (idx >= augmented.charCount_) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
scores[idx] = std::max(scores[idx], level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return collectBreakIndexes(cps, scores, augmented.charCount_, config.minPrefix, config.minSuffix);
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "HyphenationCommon.h"
|
|
||||||
#include "SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Encapsulates every language-specific dial the Liang algorithm needs at runtime. The helpers are
|
|
||||||
// intentionally represented as bare function pointers because we invoke them inside tight loops and
|
|
||||||
// want to avoid the overhead of std::function or functors. The minima default to the TeX-recommended
|
|
||||||
// "2/2" split but individual languages (English, for example) can override them.
|
|
||||||
struct LiangWordConfig {
|
|
||||||
static constexpr size_t kDefaultMinPrefix = 2;
|
|
||||||
static constexpr size_t kDefaultMinSuffix = 2;
|
|
||||||
// Predicate used to reject non-alphabetic characters before pattern lookup. Returning false causes
|
|
||||||
// the entire word to be skipped, matching the behavior of classic TeX hyphenation tables.
|
|
||||||
bool (*isLetter)(uint32_t);
|
|
||||||
// Language-specific case folding that matches how the TeX patterns were authored (usually lower-case
|
|
||||||
// ASCII for Latin and lowercase Cyrillic for Russian). Patterns are stored in UTF-8, so this must
|
|
||||||
// operate on Unicode scalars rather than bytes.
|
|
||||||
uint32_t (*toLower)(uint32_t);
|
|
||||||
// Minimum codepoints required on the left/right of any break. These correspond to TeX's
|
|
||||||
// lefthyphenmin and righthyphenmin knobs.
|
|
||||||
size_t minPrefix;
|
|
||||||
size_t minSuffix;
|
|
||||||
|
|
||||||
// Lightweight aggregate constructor so call sites can declare `const LiangWordConfig config(...)`
|
|
||||||
// without verbose member assignment boilerplate.
|
|
||||||
LiangWordConfig(bool (*letterFn)(uint32_t), uint32_t (*lowerFn)(uint32_t), size_t prefix = kDefaultMinPrefix,
|
|
||||||
size_t suffix = kDefaultMinSuffix)
|
|
||||||
: isLetter(letterFn), toLower(lowerFn), minPrefix(prefix), minSuffix(suffix) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shared Liang pattern evaluator used by every language-specific hyphenator.
|
|
||||||
std::vector<size_t> liangBreakIndexes(const std::vector<CodepointInfo>& cps,
|
|
||||||
const SerializedHyphenationPatterns& patterns, const LiangWordConfig& config);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
// Lightweight descriptor that points at a serialized Liang hyphenation trie stored in flash.
|
|
||||||
struct SerializedHyphenationPatterns {
|
|
||||||
size_t rootOffset;
|
|
||||||
const std::uint8_t* data;
|
|
||||||
size_t size;
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,869 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t es_trie_data[] = {
|
|
||||||
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 = {
|
|
||||||
0x34F8u,
|
|
||||||
es_trie_data,
|
|
||||||
sizeof(es_trie_data),
|
|
||||||
};
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t fi_trie_data[] = {
|
|
||||||
0x01, 0x01, 0x16, 0x0B, 0x0C, 0x17, 0x0C, 0x15, 0x0C, 0x0B, 0x16, 0x29, 0x2B, 0x20, 0x1F, 0x0C,
|
|
||||||
0x33, 0x02, 0x0B, 0x02, 0x0B, 0x0C, 0x01, 0x0C, 0x34, 0x0B, 0x2A, 0x0B, 0x2A, 0x0B, 0x0C, 0x20,
|
|
||||||
0x0B, 0x21, 0xA0, 0x00, 0x41, 0xA0, 0x02, 0x51, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA1, 0x00,
|
|
||||||
0x41, 0x62, 0xFD, 0xA0, 0x01, 0xA2, 0xA1, 0x00, 0x81, 0x6F, 0xFD, 0xA3, 0x00, 0x81, 0x69, 0x6F,
|
|
||||||
0x75, 0xF8, 0xF8, 0xF8, 0x28, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x6C, 0x72, 0xDE, 0xDE, 0xEA,
|
|
||||||
0xDE, 0xDE, 0xDE, 0xF2, 0xF7, 0x22, 0xA4, 0xB6, 0xCD, 0xCD, 0xA1, 0x00, 0x81, 0x61, 0xD9, 0x28,
|
|
||||||
0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x72, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xF6, 0xFB,
|
|
||||||
0xA2, 0x00, 0x81, 0x61, 0x65, 0xC3, 0xC3, 0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x6C, 0x72,
|
|
||||||
0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xE3, 0xFF, 0xF9,
|
|
||||||
0x49, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0xFF, 0x92, 0xFF, 0x92, 0xFF, 0x92,
|
|
||||||
0xFF, 0x92, 0xFF, 0x92, 0xFF, 0x92, 0xFF, 0xC5, 0xFF, 0xA6, 0xFF, 0xCA, 0x47, 0x61, 0x65, 0x69,
|
|
||||||
0x6F, 0x75, 0x79, 0xC3, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76,
|
|
||||||
0xFF, 0xA9, 0xA0, 0x00, 0xF1, 0x21, 0x73, 0xFD, 0xA1, 0x00, 0x41, 0x75, 0xFD, 0xA0, 0x00, 0x81,
|
|
||||||
0x43, 0x61, 0x65, 0x69, 0xFF, 0x63, 0xFF, 0x63, 0xFF, 0x63, 0xC1, 0x01, 0xA2, 0x61, 0xFF, 0x59,
|
|
||||||
0x4A, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0x76, 0xFF, 0x42, 0xFF, 0xE8, 0xFF,
|
|
||||||
0x42, 0xFF, 0x42, 0xFF, 0x42, 0xFF, 0x42, 0xFF, 0x75, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xFA, 0xA1,
|
|
||||||
0x00, 0x41, 0x73, 0xCE, 0x47, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0xFB, 0xFF, 0x1E,
|
|
||||||
0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x51, 0xA0, 0x01, 0x52, 0x21, 0x6F, 0xFD,
|
|
||||||
0x22, 0x74, 0x6E, 0xFD, 0xFD, 0xA0, 0x01, 0x73, 0x21, 0x6E, 0xFD, 0x22, 0x61, 0x6F, 0xFD, 0xFA,
|
|
||||||
0x21, 0x61, 0xEA, 0x21, 0x6B, 0xFD, 0x21, 0x65, 0xF2, 0xA4, 0x00, 0x41, 0x6E, 0x6A, 0x69, 0x6C,
|
|
||||||
0xE7, 0xF2, 0xFA, 0xFD, 0x21, 0x73, 0xE1, 0x21, 0x75, 0xFD, 0xA1, 0x00, 0x41, 0x64, 0xFD, 0x21,
|
|
||||||
0x74, 0xD6, 0x21, 0x73, 0xFD, 0x22, 0x65, 0x69, 0xFA, 0xFD, 0x21, 0x61, 0xCB, 0x21, 0x6E, 0xBD,
|
|
||||||
0x22, 0x74, 0x6F, 0xBD, 0xFD, 0x21, 0x69, 0xC0, 0x21, 0x61, 0xFD, 0xA4, 0x00, 0x41, 0x70, 0x73,
|
|
||||||
0x74, 0x6D, 0xEA, 0xEF, 0xF5, 0xFD, 0x21, 0x69, 0xD9, 0xA1, 0x00, 0x41, 0x6C, 0xFD, 0x47, 0x61,
|
|
||||||
0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0xBB, 0xFF, 0xCC, 0xFE, 0xA4, 0xFF, 0xED, 0xFE, 0xA4,
|
|
||||||
0xFF, 0xFB, 0xFE, 0xD7, 0xA0, 0x01, 0x41, 0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x00, 0x41,
|
|
||||||
0x72, 0xFD, 0x49, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0xFE, 0x80, 0xFF, 0xFB,
|
|
||||||
0xFE, 0x80, 0xFE, 0x80, 0xFE, 0x80, 0xFE, 0x80, 0xFE, 0xB3, 0xFF, 0x2B, 0xFE, 0x94, 0x21, 0x61,
|
|
||||||
0xDC, 0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x74, 0xFF, 0x3E, 0xFE, 0x61, 0xFE, 0x61,
|
|
||||||
0xFE, 0x61, 0xFE, 0x61, 0xFE, 0x61, 0xFE, 0x94, 0xFF, 0xFD, 0x42, 0x69, 0x65, 0xFF, 0x80, 0xFF,
|
|
||||||
0x40, 0x42, 0x6F, 0x65, 0xFF, 0x84, 0xFF, 0x47, 0x41, 0x75, 0xFF, 0x32, 0x21, 0x74, 0xFC, 0x42,
|
|
||||||
0x61, 0x6F, 0xFF, 0xFD, 0xFF, 0x36, 0xA4, 0x00, 0x41, 0x73, 0x6C, 0x6A, 0x70, 0xE4, 0xEB, 0xF9,
|
|
||||||
0xF2, 0x41, 0x79, 0xFF, 0x24, 0x21, 0x74, 0xFC, 0x21, 0x69, 0xFD, 0xA1, 0x00, 0x41, 0x73, 0xFD,
|
|
||||||
0x42, 0x2E, 0x6E, 0xFF, 0x15, 0xFF, 0x15, 0x21, 0x61, 0xF9, 0x21, 0x65, 0xFD, 0xA1, 0x00, 0x41,
|
|
||||||
0x64, 0xFD, 0x41, 0x65, 0xFE, 0xF8, 0x21, 0x6A, 0xFC, 0x42, 0x6B, 0x74, 0xFE, 0xFC, 0xFE, 0xFC,
|
|
||||||
0x21, 0x73, 0xF9, 0x21, 0x69, 0xFD, 0xC3, 0x00, 0x41, 0x68, 0x70, 0x73, 0xFF, 0xF0, 0xFF, 0xFD,
|
|
||||||
0xFF, 0x24, 0x41, 0x74, 0xFF, 0x23, 0xC2, 0x00, 0x41, 0x72, 0x68, 0xFF, 0x30, 0xFF, 0xFC, 0xA0,
|
|
||||||
0x00, 0x52, 0x21, 0x72, 0xFD, 0x21, 0x69, 0xFA, 0x21, 0x6C, 0xFD, 0xA0, 0x00, 0x61, 0x21, 0x68,
|
|
||||||
0xFD, 0x4A, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x74, 0x70, 0x63, 0xFF, 0x95, 0xFF, 0xAA,
|
|
||||||
0xFF, 0xBC, 0xFF, 0xD5, 0xFD, 0xC1, 0xFF, 0xE5, 0xFD, 0xF4, 0xFF, 0xF1, 0xFF, 0xF7, 0xFF, 0xFD,
|
|
||||||
0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x73, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD,
|
|
||||||
0xA2, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD, 0xD5, 0xFF, 0xDE, 0xA0, 0x00, 0x92, 0xA0, 0x00, 0xB2, 0xA0,
|
|
||||||
0x01, 0x01, 0xC3, 0x00, 0x61, 0x69, 0x65, 0x79, 0xFE, 0x20, 0xFE, 0x20, 0xFF, 0xFD, 0x22, 0xA4,
|
|
||||||
0xB6, 0xF4, 0xAD, 0x25, 0x79, 0x61, 0x6F, 0x75, 0xC3, 0xA8, 0xE6, 0xE6, 0xE9, 0xFB, 0x42, 0xB6,
|
|
||||||
0xA4, 0xFF, 0x9D, 0xFF, 0x9D, 0x46, 0x79, 0x61, 0x6F, 0x75, 0xC3, 0x65, 0xFF, 0x96, 0xFF, 0xD4,
|
|
||||||
0xFF, 0xD4, 0xFF, 0xD7, 0xFF, 0xF9, 0xFF, 0xD7, 0x22, 0xA4, 0xB6, 0xDB, 0xED, 0xA0, 0x00, 0x72,
|
|
||||||
0xA0, 0x00, 0x71, 0x21, 0xA4, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0xA4, 0xFD, 0x21, 0x69, 0xF4, 0xA0,
|
|
||||||
0x01, 0x22, 0x21, 0x70, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x26, 0x61, 0x6F, 0x75, 0xC3,
|
|
||||||
0x65, 0x6C, 0xE2, 0xE2, 0xE2, 0xEE, 0xF1, 0xFD, 0x22, 0xA4, 0xB6, 0xD8, 0xD8, 0x21, 0x61, 0xD3,
|
|
||||||
0xA0, 0x00, 0xB1, 0x24, 0x75, 0x69, 0x65, 0x6F, 0xCD, 0xCD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F,
|
|
||||||
0x75, 0xF4, 0xF4, 0xF4, 0xF4, 0x25, 0x79, 0xC3, 0x61, 0x75, 0x69, 0xBB, 0xE3, 0xE8, 0xEE, 0xF7,
|
|
||||||
0xA0, 0x00, 0xD2, 0x22, 0xA4, 0xB6, 0xFD, 0xFD, 0x44, 0x61, 0x65, 0x6F, 0x69, 0xFF, 0x64, 0xFF,
|
|
||||||
0x64, 0xFF, 0x64, 0xFF, 0x64, 0x42, 0x65, 0x61, 0xFF, 0x9B, 0xFF, 0xCB, 0x21, 0x65, 0xC4, 0x22,
|
|
||||||
0x61, 0x75, 0xC1, 0xC1, 0xA0, 0x02, 0x32, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x49, 0x79, 0xC3,
|
|
||||||
0x75, 0x61, 0x65, 0x69, 0x6F, 0x73, 0x6C, 0xFF, 0x80, 0xFF, 0xD6, 0xFF, 0xDB, 0xFF, 0xB0, 0xFF,
|
|
||||||
0xE8, 0xFF, 0xEF, 0xFF, 0xF2, 0xFD, 0x70, 0xFF, 0xFD, 0x44, 0x69, 0x65, 0x6F, 0x75, 0xFF, 0x23,
|
|
||||||
0xFF, 0x23, 0xFF, 0x23, 0xFF, 0x23, 0x43, 0x75, 0x61, 0x65, 0xFF, 0x5A, 0xFF, 0x8A, 0xFF, 0x8A,
|
|
||||||
0x41, 0x76, 0xFF, 0x5F, 0x21, 0x61, 0xFC, 0xA0, 0x01, 0xC2, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD,
|
|
||||||
0x21, 0x65, 0xFD, 0x43, 0x69, 0x6F, 0x6B, 0xFF, 0xF1, 0xFD, 0xF7, 0xFF, 0xFD, 0xA0, 0x01, 0xA4,
|
|
||||||
0x21, 0x73, 0xFD, 0x21, 0x61, 0xFD, 0x43, 0x6E, 0x74, 0x6B, 0xFC, 0x7D, 0xFC, 0x7D, 0xFF, 0xFD,
|
|
||||||
0x41, 0x69, 0xFC, 0x73, 0x22, 0x61, 0x6F, 0xF2, 0xFC, 0x21, 0x69, 0xFB, 0x48, 0xC3, 0x61, 0x75,
|
|
||||||
0x65, 0x6F, 0x69, 0x6C, 0x73, 0xFF, 0x3C, 0xFF, 0xAD, 0xFF, 0xBA, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
|
|
||||||
0x50, 0xFF, 0xD7, 0xFF, 0xFD, 0x44, 0x61, 0x69, 0x75, 0x79, 0xFE, 0xB7, 0xFE, 0xB7, 0xFE, 0xB7,
|
|
||||||
0xFE, 0xB7, 0x42, 0x61, 0x69, 0xFE, 0xEE, 0xFE, 0xEE, 0x42, 0x75, 0x61, 0xFE, 0xE7, 0xFF, 0x17,
|
|
||||||
0x42, 0xA4, 0xB6, 0xFE, 0xE6, 0xFF, 0x30, 0x24, 0x65, 0x61, 0x75, 0xC3, 0xDE, 0xEB, 0xF2, 0xF9,
|
|
||||||
0x43, 0x61, 0x65, 0x6F, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0x42, 0x61, 0x75, 0xFE, 0xC6, 0xFE,
|
|
||||||
0xC6, 0x44, 0x75, 0x61, 0x65, 0x6F, 0xFE, 0xBF, 0xFE, 0xEF, 0xFE, 0xEF, 0xFE, 0xEF, 0x41, 0xB6,
|
|
||||||
0xFE, 0xB2, 0x21, 0xC3, 0xFC, 0x42, 0xA4, 0xB6, 0xFE, 0xB1, 0xFF, 0xFD, 0x43, 0x61, 0x6F, 0x79,
|
|
||||||
0xFE, 0xD4, 0xFE, 0xD4, 0xFE, 0xD4, 0x42, 0x61, 0x65, 0xFE, 0x56, 0xFE, 0x56, 0x26, 0x69, 0x61,
|
|
||||||
0x75, 0xC3, 0x65, 0x6F, 0xC3, 0xCD, 0xD4, 0xE8, 0xEF, 0xF9, 0xA0, 0x01, 0x11, 0x21, 0xA4, 0xFD,
|
|
||||||
0xA0, 0x01, 0xE2, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x64, 0xFD, 0xA0, 0x02, 0x03, 0x21,
|
|
||||||
0x61, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x75, 0xFD, 0x23, 0xC3, 0x79, 0x73, 0xE2,
|
|
||||||
0xEE, 0xFD, 0x41, 0x72, 0xFD, 0xD9, 0x42, 0x6C, 0x68, 0xFC, 0x47, 0xFF, 0xFC, 0xC1, 0x00, 0x81,
|
|
||||||
0x69, 0xFB, 0xA6, 0x21, 0x76, 0xFA, 0x59, 0x62, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0xC3, 0x79, 0x6F, 0x75, 0x61, 0x65, 0x69, 0x2E, 0x63, 0x71,
|
|
||||||
0xFB, 0xAE, 0xFB, 0xC9, 0xFB, 0xE1, 0xFB, 0xFA, 0xFC, 0x16, 0xFC, 0x16, 0xFC, 0x4A, 0xFC, 0x6E,
|
|
||||||
0xFC, 0x16, 0xFC, 0xE8, 0xFD, 0x0C, 0xFD, 0x2B, 0xFD, 0xCB, 0xFD, 0xEA, 0xFC, 0x16, 0xFE, 0x42,
|
|
||||||
0xFE, 0x65, 0xFE, 0x8F, 0xFE, 0xC7, 0xFF, 0x36, 0xFF, 0x71, 0xFF, 0xB7, 0xFF, 0xE5, 0xFF, 0xF0,
|
|
||||||
0xFF, 0xFD,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SerializedHyphenationPatterns fi_patterns = {
|
|
||||||
0x496u,
|
|
||||||
fi_trie_data,
|
|
||||||
sizeof(fi_trie_data),
|
|
||||||
};
|
|
||||||
@@ -1,453 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t fr_trie_data[] = {
|
|
||||||
0x02, 0x0C, 0x18, 0x22, 0x16, 0x21, 0x0B, 0x16, 0x21, 0x0E, 0x01, 0x0C, 0x0B, 0x3D, 0x0C, 0x2B,
|
|
||||||
0x0E, 0x0C, 0x0C, 0x33, 0x0C, 0x33, 0x16, 0x34, 0x2A, 0x0D, 0x20, 0x0D, 0x0C, 0x0D, 0x2A, 0x17,
|
|
||||||
0x04, 0x1F, 0x0C, 0x29, 0x0C, 0x20, 0x0B, 0x0C, 0x17, 0x17, 0x0C, 0x3F, 0x35, 0x53, 0x4A, 0x36,
|
|
||||||
0x34, 0x21, 0x2A, 0x0D, 0x0C, 0x2A, 0x0D, 0x16, 0x02, 0x17, 0x15, 0x15, 0x0C, 0x15, 0x16, 0x2C,
|
|
||||||
0x47, 0x0C, 0x49, 0x2B, 0x0C, 0x0D, 0x34, 0x0D, 0x2A, 0x0B, 0x16, 0x2B, 0x0C, 0x17, 0x2A, 0x0B,
|
|
||||||
0x0C, 0x03, 0x0C, 0x16, 0x0D, 0x01, 0x16, 0x0C, 0x0B, 0x0C, 0x3E, 0x48, 0x2C, 0x0B, 0x29, 0x16,
|
|
||||||
0x37, 0x40, 0x1F, 0x16, 0x20, 0x17, 0x36, 0x0D, 0x52, 0x3D, 0x16, 0x1F, 0x0C, 0x16, 0x3E, 0x0D,
|
|
||||||
0x49, 0x0C, 0x03, 0x16, 0x35, 0x0C, 0x22, 0x0F, 0x02, 0x0D, 0x51, 0x0C, 0x21, 0x0C, 0x20, 0x0B,
|
|
||||||
0x16, 0x21, 0x0C, 0x17, 0x21, 0x0C, 0x0D, 0xA0, 0x00, 0x91, 0x21, 0x61, 0xFD, 0x21, 0xA9, 0xFD,
|
|
||||||
0x21, 0xC3, 0xFD, 0x21, 0x72, 0xFD, 0xA0, 0x00, 0xC2, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21,
|
|
||||||
0x73, 0xFD, 0xA0, 0x00, 0x51, 0x21, 0x6C, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x63,
|
|
||||||
0xFD, 0xA0, 0x01, 0x12, 0x21, 0x63, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6E, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0xA0, 0x01, 0x32, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0xA0,
|
|
||||||
0x01, 0x52, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x68,
|
|
||||||
0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x01, 0x72, 0xA0, 0x01, 0xB1, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x6E, 0xFD, 0xA1, 0x01, 0x72, 0x6E, 0xFD, 0xA0, 0x01, 0x92, 0x21, 0xA9, 0xFD, 0x24, 0x61,
|
|
||||||
0x65, 0xC3, 0x73, 0xE9, 0xF5, 0xFD, 0xE9, 0x21, 0x69, 0xF7, 0x23, 0x61, 0x65, 0x74, 0xC2, 0xDA,
|
|
||||||
0xFD, 0xA0, 0x01, 0xC2, 0x21, 0x61, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD,
|
|
||||||
0xA0, 0x01, 0xE1, 0x21, 0x61, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x2E, 0xFF, 0x5E, 0x21, 0x74, 0xFC,
|
|
||||||
0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x22, 0x67, 0x70, 0xFD, 0xFD, 0xA0, 0x05, 0x72, 0x21, 0x74,
|
|
||||||
0xFD, 0x21, 0x61, 0xFD, 0x21, 0x6E, 0xFD, 0xC9, 0x00, 0x61, 0x62, 0x65, 0x6C, 0x6D, 0x6E, 0x70,
|
|
||||||
0x73, 0x72, 0x67, 0xFF, 0x4C, 0xFF, 0x58, 0xFF, 0x67, 0xFF, 0x79, 0xFF, 0xC3, 0xFF, 0xD6, 0xFF,
|
|
||||||
0xDF, 0xFF, 0xEF, 0xFF, 0xFD, 0xA0, 0x00, 0x71, 0x27, 0xA2, 0xAA, 0xA9, 0xA8, 0xAE, 0xB4, 0xBB,
|
|
||||||
0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0, 0x02, 0x52, 0x22, 0x61, 0x6F, 0xFD, 0xFD, 0xA0,
|
|
||||||
0x02, 0x93, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0xA2, 0x00, 0x61, 0x6E, 0x75, 0xF2, 0xFD, 0x21,
|
|
||||||
0xA9, 0xAC, 0x42, 0xC3, 0x69, 0xFF, 0xFD, 0xFF, 0xA9, 0x21, 0x6E, 0xF9, 0x41, 0x74, 0xFF, 0x06,
|
|
||||||
0x21, 0x61, 0xFC, 0x21, 0x6D, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0xA0, 0x01, 0xE2, 0x21,
|
|
||||||
0x74, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x72, 0xFF, 0x6B, 0x21, 0x75, 0xFC, 0x21, 0x67, 0xFD, 0xA2,
|
|
||||||
0x02, 0x52, 0x6E, 0x75, 0xF3, 0xFD, 0x41, 0x62, 0xFF, 0x5A, 0x21, 0x61, 0xFC, 0x21, 0x66, 0xFD,
|
|
||||||
0x41, 0x74, 0xFF, 0x50, 0x41, 0x72, 0xFF, 0x4F, 0x21, 0x6F, 0xFC, 0xC4, 0x02, 0x52, 0x66, 0x70,
|
|
||||||
0x72, 0x78, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0x45, 0xFF, 0xFD, 0xA0, 0x06, 0x82, 0x21, 0x61, 0xFD,
|
|
||||||
0x21, 0x74, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x72, 0xF4, 0x21, 0x72, 0xFD, 0x21,
|
|
||||||
0x61, 0xFD, 0xA2, 0x06, 0x62, 0x6C, 0x6E, 0xF4, 0xFD, 0x21, 0xA9, 0xF9, 0x41, 0x69, 0xFF, 0xA0,
|
|
||||||
0x21, 0x74, 0xFC, 0x21, 0x69, 0xFD, 0xC3, 0x02, 0x52, 0x6D, 0x71, 0x74, 0xFF, 0xFD, 0xFF, 0x96,
|
|
||||||
0xFF, 0x96, 0x41, 0x6C, 0xFF, 0x8A, 0x21, 0x75, 0xFC, 0x41, 0x64, 0xFE, 0xF7, 0xA2, 0x02, 0x52,
|
|
||||||
0x63, 0x6E, 0xF9, 0xFC, 0x41, 0x62, 0xFF, 0x43, 0x21, 0x61, 0xFC, 0x21, 0x74, 0xFD, 0xA0, 0x05,
|
|
||||||
0xF1, 0xA0, 0x06, 0xC1, 0x21, 0xA9, 0xFD, 0xA7, 0x06, 0xA2, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75,
|
|
||||||
0x73, 0xF7, 0xF7, 0xFD, 0xF7, 0xF7, 0xF7, 0xF7, 0x21, 0x72, 0xEF, 0x21, 0x65, 0xFD, 0xC2, 0x02,
|
|
||||||
0x52, 0x69, 0x6C, 0xFF, 0x72, 0xFF, 0x4E, 0x49, 0x66, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73, 0x74,
|
|
||||||
0x75, 0xFF, 0x42, 0xFF, 0x58, 0xFF, 0x74, 0xFF, 0xA2, 0xFF, 0xAF, 0xFF, 0xC6, 0xFF, 0xD4, 0xFF,
|
|
||||||
0xF4, 0xFF, 0xF7, 0xC2, 0x00, 0x61, 0x67, 0x6E, 0xFF, 0x16, 0xFF, 0xE4, 0x41, 0x75, 0xFE, 0xA7,
|
|
||||||
0x21, 0x67, 0xFC, 0x41, 0x65, 0xFF, 0x09, 0x21, 0x74, 0xFC, 0xA0, 0x02, 0x71, 0x21, 0x75, 0xFD,
|
|
||||||
0x21, 0x6F, 0xFD, 0x21, 0x61, 0xFD, 0xA0, 0x02, 0x72, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0x21,
|
|
||||||
0x69, 0xFD, 0xA4, 0x00, 0x61, 0x6E, 0x63, 0x75, 0x76, 0xDE, 0xE5, 0xF1, 0xFD, 0xA0, 0x00, 0x61,
|
|
||||||
0xC7, 0x00, 0x42, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xFE, 0x87, 0xFE, 0xA8, 0xFE, 0xC8,
|
|
||||||
0xFF, 0xC3, 0xFF, 0xF2, 0xFF, 0xFD, 0xFF, 0xFD, 0x42, 0x61, 0x74, 0xFD, 0xF4, 0xFE, 0x2F, 0x43,
|
|
||||||
0x64, 0x67, 0x70, 0xFE, 0x54, 0xFE, 0x54, 0xFE, 0x54, 0xC8, 0x00, 0x61, 0x62, 0x65, 0x6D, 0x6E,
|
|
||||||
0x70, 0x73, 0x72, 0x67, 0xFD, 0xAA, 0xFD, 0xB6, 0xFD, 0xD7, 0xFF, 0xEF, 0xFE, 0x34, 0xFE, 0x3D,
|
|
||||||
0xFF, 0xF6, 0xFE, 0x5B, 0xA0, 0x03, 0x01, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD,
|
|
||||||
0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x00, 0x71, 0x6D, 0xFD, 0x47, 0xA2,
|
|
||||||
0xAA, 0xA9, 0xA8, 0xAE, 0xB4, 0xBB, 0xFE, 0x47, 0xFE, 0x47, 0xFF, 0xFB, 0xFE, 0x47, 0xFE, 0x47,
|
|
||||||
0xFE, 0x47, 0xFE, 0x47, 0xA0, 0x02, 0x22, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x61, 0xFD,
|
|
||||||
0x21, 0x6D, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x02, 0x51, 0x43,
|
|
||||||
0x63, 0x74, 0x75, 0xFE, 0x28, 0xFE, 0x28, 0xFF, 0xFD, 0x41, 0x61, 0xFF, 0x4D, 0x44, 0x61, 0x6F,
|
|
||||||
0x73, 0x75, 0xFF, 0xF2, 0xFF, 0xFC, 0xFE, 0x25, 0xFE, 0x1A, 0x22, 0x61, 0x69, 0xDF, 0xF3, 0xA0,
|
|
||||||
0x03, 0x42, 0x21, 0x65, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x75,
|
|
||||||
0xFD, 0x21, 0x65, 0xFD, 0x21, 0x66, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x76, 0xFD,
|
|
||||||
0x21, 0xA8, 0xFD, 0xA1, 0x00, 0x71, 0xC3, 0xFD, 0xA0, 0x02, 0x92, 0x21, 0x70, 0xFD, 0x21, 0x6C,
|
|
||||||
0xFD, 0x21, 0x61, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x03, 0x31, 0xA0, 0x04, 0x42, 0x21, 0x63, 0xFD,
|
|
||||||
0xA0, 0x04, 0x61, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0xAE, 0xFD, 0x21,
|
|
||||||
0xC3, 0xFD, 0x21, 0x61, 0xFD, 0x22, 0x73, 0x6D, 0xE8, 0xFD, 0x21, 0x65, 0xFB, 0x21, 0x72, 0xFD,
|
|
||||||
0xA2, 0x04, 0x31, 0x73, 0x74, 0xD7, 0xFD, 0x41, 0x65, 0xFD, 0xD5, 0x21, 0x69, 0xFC, 0xA1, 0x02,
|
|
||||||
0x52, 0x6C, 0xFD, 0xA0, 0x01, 0x31, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21,
|
|
||||||
0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x23, 0x6E, 0x6F, 0x6D, 0xDB, 0xE9, 0xFD, 0xA0, 0x04, 0x31, 0x21,
|
|
||||||
0x6C, 0xFD, 0x44, 0x68, 0x69, 0x6F, 0x75, 0xFF, 0x91, 0xFF, 0xA2, 0xFF, 0xF3, 0xFF, 0xFD, 0x41,
|
|
||||||
0x61, 0xFF, 0x9B, 0x21, 0x6F, 0xFC, 0x21, 0x79, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x63, 0xFD, 0x41,
|
|
||||||
0x6F, 0xFE, 0x7B, 0xA0, 0x04, 0x73, 0x21, 0x72, 0xFD, 0xA0, 0x04, 0xA2, 0x21, 0x6C, 0xF7, 0x21,
|
|
||||||
0x6C, 0xFD, 0x21, 0x65, 0xFD, 0xA0, 0x04, 0x72, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x24, 0x63,
|
|
||||||
0x6D, 0x74, 0x73, 0xE8, 0xEB, 0xF4, 0xFD, 0xA0, 0x04, 0xF3, 0x21, 0x72, 0xFD, 0xA1, 0x04, 0xC3,
|
|
||||||
0x67, 0xFD, 0x21, 0xA9, 0xFB, 0x21, 0x62, 0xE0, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x74,
|
|
||||||
0xD7, 0x21, 0x75, 0xD4, 0x23, 0x6E, 0x72, 0x78, 0xF7, 0xFA, 0xFD, 0x21, 0x6E, 0xB8, 0x21, 0x69,
|
|
||||||
0xB5, 0x21, 0x6F, 0xC4, 0x22, 0x65, 0x76, 0xF7, 0xFD, 0xC6, 0x05, 0x23, 0x64, 0x67, 0x6C, 0x6E,
|
|
||||||
0x72, 0x73, 0xFF, 0xAA, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xAA, 0xFF, 0xE5, 0x41, 0xA9,
|
|
||||||
0xFF, 0x95, 0x21, 0xC3, 0xFC, 0x41, 0x69, 0xFF, 0x97, 0x42, 0x6D, 0x70, 0xFF, 0x9C, 0xFF, 0x9C,
|
|
||||||
0x41, 0x66, 0xFF, 0x98, 0x45, 0x64, 0x6C, 0x70, 0x72, 0x75, 0xFF, 0xEE, 0xFF, 0x7F, 0xFF, 0xF1,
|
|
||||||
0xFF, 0xF5, 0xFF, 0xFC, 0xA0, 0x04, 0xC2, 0x21, 0x93, 0xFD, 0xA0, 0x05, 0x23, 0x21, 0x6E, 0xFD,
|
|
||||||
0xCA, 0x01, 0xC1, 0x61, 0x63, 0xC3, 0x65, 0x69, 0x6F, 0xC5, 0x70, 0x74, 0x75, 0xFF, 0x7E, 0xFF,
|
|
||||||
0x75, 0xFF, 0x92, 0xFF, 0xA4, 0xFF, 0xB9, 0xFF, 0xE4, 0xFF, 0xF7, 0xFF, 0x75, 0xFF, 0x75, 0xFF,
|
|
||||||
0xFD, 0x44, 0x61, 0x69, 0x6F, 0x73, 0xFD, 0xC5, 0xFF, 0x3E, 0xFD, 0xC5, 0xFF, 0xDF, 0x21, 0xA9,
|
|
||||||
0xF3, 0x41, 0xA9, 0xFC, 0x86, 0x41, 0x64, 0xFC, 0x82, 0x22, 0xC3, 0x69, 0xF8, 0xFC, 0x41, 0x64,
|
|
||||||
0xFE, 0x4E, 0x41, 0x69, 0xFC, 0x75, 0x41, 0x6D, 0xFC, 0x71, 0x21, 0x6F, 0xFC, 0x24, 0x63, 0x6C,
|
|
||||||
0x6D, 0x74, 0xEC, 0xF1, 0xF5, 0xFD, 0x41, 0x6E, 0xFC, 0x61, 0x41, 0x68, 0xFC, 0x92, 0x23, 0x61,
|
|
||||||
0x65, 0x73, 0xEF, 0xF8, 0xFC, 0xC4, 0x01, 0xE2, 0x61, 0x69, 0x6F, 0x75, 0xFC, 0x5A, 0xFC, 0x5A,
|
|
||||||
0xFC, 0x5A, 0xFC, 0x5A, 0x21, 0x73, 0xF1, 0x41, 0x6C, 0xFB, 0xFC, 0x45, 0x61, 0xC3, 0x69, 0x79,
|
|
||||||
0x6F, 0xFE, 0xE1, 0xFF, 0xB3, 0xFF, 0xE3, 0xFF, 0xF9, 0xFF, 0xFC, 0x48, 0x61, 0x65, 0xC3, 0x69,
|
|
||||||
0x6F, 0x73, 0x74, 0x75, 0xFC, 0x74, 0xFC, 0x90, 0xFC, 0xBE, 0xFC, 0xCB, 0xFC, 0xE2, 0xFC, 0xF0,
|
|
||||||
0xFD, 0x10, 0xFD, 0x13, 0xC2, 0x00, 0x61, 0x67, 0x6E, 0xFC, 0x35, 0xFF, 0xE7, 0x41, 0x64, 0xFE,
|
|
||||||
0x6A, 0x21, 0x69, 0xFC, 0x41, 0x61, 0xFC, 0x3B, 0x21, 0x63, 0xFC, 0x21, 0x69, 0xFD, 0x22, 0x63,
|
|
||||||
0x66, 0xF3, 0xFD, 0x41, 0x6D, 0xFC, 0x29, 0x22, 0x69, 0x75, 0xF7, 0xFC, 0x21, 0x6E, 0xFB, 0x41,
|
|
||||||
0x73, 0xFB, 0x25, 0x21, 0x6F, 0xFC, 0x42, 0x6B, 0x72, 0xFC, 0x16, 0xFF, 0xFD, 0x41, 0x73, 0xFB,
|
|
||||||
0xE2, 0x42, 0x65, 0x6F, 0xFF, 0xFC, 0xFB, 0xDE, 0x21, 0x72, 0xF9, 0x41, 0xA9, 0xFD, 0xED, 0x21,
|
|
||||||
0xC3, 0xFC, 0x21, 0x73, 0xFD, 0x44, 0x64, 0x69, 0x70, 0x76, 0xFF, 0xF3, 0xFF, 0xFD, 0xFD, 0xE3,
|
|
||||||
0xFB, 0xCA, 0x41, 0x6E, 0xFD, 0xD6, 0x41, 0x74, 0xFD, 0xD2, 0x21, 0x6E, 0xFC, 0x42, 0x63, 0x64,
|
|
||||||
0xFD, 0xCB, 0xFB, 0xB2, 0x24, 0x61, 0x65, 0x69, 0x6F, 0xE1, 0xEE, 0xF6, 0xF9, 0x41, 0x78, 0xFD,
|
|
||||||
0xBB, 0x24, 0x67, 0x63, 0x6C, 0x72, 0xAB, 0xB5, 0xF3, 0xFC, 0x41, 0x68, 0xFE, 0xCA, 0x21, 0x6F,
|
|
||||||
0xFC, 0xC1, 0x01, 0xC1, 0x6E, 0xFD, 0xF2, 0x41, 0x73, 0xFE, 0xBD, 0x41, 0x73, 0xFE, 0xBF, 0x44,
|
|
||||||
0x61, 0x65, 0x69, 0x75, 0xFF, 0xF2, 0xFF, 0xF8, 0xFE, 0xB5, 0xFF, 0xFC, 0x41, 0x61, 0xFA, 0xA5,
|
|
||||||
0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x61, 0xFD, 0x23, 0x67, 0x73, 0x74, 0xD5, 0xE6, 0xFD,
|
|
||||||
0x21, 0xA9, 0xF9, 0xA0, 0x01, 0x11, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21,
|
|
||||||
0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0xC3, 0xFA, 0xC6, 0x21, 0x64, 0xFC, 0x42, 0xA9, 0xAF, 0xFA,
|
|
||||||
0xBC, 0xFF, 0xFD, 0x47, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0x73, 0xFA, 0xA4, 0xFA, 0xA4, 0xFF,
|
|
||||||
0xF9, 0xFA, 0xA4, 0xFA, 0xA4, 0xFA, 0xA4, 0xFA, 0xA4, 0x21, 0x6F, 0xEA, 0x21, 0x6E, 0xFD, 0x44,
|
|
||||||
0x61, 0xC3, 0x69, 0x6F, 0xFF, 0x82, 0xFF, 0xC1, 0xFF, 0xD3, 0xFF, 0xFD, 0x41, 0x68, 0xFA, 0xA5,
|
|
||||||
0x21, 0x74, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x06, 0x22, 0x21, 0xA9, 0xFD, 0x41,
|
|
||||||
0xA9, 0xFC, 0x27, 0x21, 0xC3, 0xFC, 0x21, 0x63, 0xFD, 0xA0, 0x07, 0x82, 0x21, 0x68, 0xFD, 0x21,
|
|
||||||
0x64, 0xFD, 0x24, 0x67, 0xC3, 0x73, 0x75, 0xE4, 0xEA, 0xF4, 0xFD, 0x41, 0x61, 0xFD, 0x8E, 0xC2,
|
|
||||||
0x01, 0x72, 0x6C, 0x75, 0xFF, 0xFC, 0xFA, 0x4B, 0x47, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x73,
|
|
||||||
0xFF, 0xF7, 0xFA, 0x53, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA, 0x3F, 0x21, 0xA9,
|
|
||||||
0xEA, 0x22, 0x6F, 0xC3, 0xD1, 0xFD, 0x41, 0xA9, 0xFA, 0xB9, 0x21, 0xC3, 0xFC, 0x43, 0x66, 0x6D,
|
|
||||||
0x72, 0xFA, 0xB2, 0xFF, 0xFD, 0xFA, 0xB5, 0x41, 0x73, 0xFC, 0xC1, 0x42, 0x68, 0x74, 0xFA, 0xA4,
|
|
||||||
0xFC, 0xBD, 0x21, 0x70, 0xF9, 0x23, 0x61, 0x69, 0x6F, 0xE8, 0xF2, 0xFD, 0x41, 0xA8, 0xFA, 0x93,
|
|
||||||
0x42, 0x65, 0xC3, 0xFA, 0x8F, 0xFF, 0xFC, 0x21, 0x68, 0xF9, 0x42, 0x63, 0x73, 0xFF, 0xFD, 0xF9,
|
|
||||||
0xED, 0x41, 0xA9, 0xFA, 0xAB, 0x21, 0xC3, 0xFC, 0x43, 0x61, 0x68, 0x65, 0xFF, 0xF2, 0xFF, 0xFD,
|
|
||||||
0xFA, 0x28, 0x43, 0x6E, 0x72, 0x74, 0xFF, 0xD3, 0xFF, 0xF6, 0xFA, 0x21, 0xA0, 0x01, 0xC1, 0x21,
|
|
||||||
0x61, 0xFD, 0x21, 0x74, 0xFD, 0xC6, 0x00, 0x71, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0xFB, 0x81,
|
|
||||||
0xFB, 0x81, 0xFF, 0x57, 0xFB, 0x81, 0xFB, 0x81, 0xFB, 0x81, 0x22, 0x6E, 0x72, 0xE8, 0xEB, 0x41,
|
|
||||||
0x73, 0xFE, 0xE4, 0xA0, 0x07, 0x22, 0x21, 0x61, 0xFD, 0xA2, 0x01, 0x12, 0x73, 0x74, 0xFA, 0xFD,
|
|
||||||
0x43, 0x6F, 0x73, 0x75, 0xFF, 0xEF, 0xFF, 0xF9, 0xF9, 0x61, 0x21, 0x69, 0xF6, 0x21, 0x72, 0xFD,
|
|
||||||
0x21, 0xA9, 0xFD, 0xA0, 0x07, 0x42, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFD, 0x21,
|
|
||||||
0x61, 0xFD, 0x21, 0x6C, 0xFD, 0xA1, 0x00, 0x71, 0x61, 0xFD, 0x41, 0x61, 0xFE, 0xA9, 0x21, 0x69,
|
|
||||||
0xFC, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x74, 0xFF, 0x95, 0x21, 0x65, 0xFC, 0x21, 0x74,
|
|
||||||
0xFD, 0x41, 0x6E, 0xFD, 0x23, 0x45, 0x68, 0x69, 0x6F, 0x72, 0x73, 0xF9, 0x7C, 0xFF, 0xFC, 0xFD,
|
|
||||||
0x25, 0xF9, 0x7C, 0xF9, 0x52, 0x21, 0x74, 0xF0, 0x22, 0x6E, 0x73, 0xE6, 0xFD, 0x41, 0x6E, 0xFB,
|
|
||||||
0xFD, 0x21, 0x61, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x79, 0xFD,
|
|
||||||
0x41, 0x6C, 0xFA, 0xE6, 0x21, 0x64, 0xFC, 0x21, 0x64, 0xFD, 0x49, 0x72, 0x61, 0x65, 0xC3, 0x68,
|
|
||||||
0x6C, 0x6F, 0x73, 0x75, 0xFE, 0xF7, 0xFF, 0x48, 0xFF, 0x70, 0xFF, 0x96, 0xFF, 0xAB, 0xFF, 0xBA,
|
|
||||||
0xFF, 0xDE, 0xFF, 0xF3, 0xFF, 0xFD, 0x41, 0x6E, 0xF9, 0x2B, 0x21, 0x67, 0xFC, 0x41, 0x6C, 0xFB,
|
|
||||||
0x17, 0x21, 0x6C, 0xFC, 0x22, 0x61, 0x69, 0xF6, 0xFD, 0x41, 0x67, 0xFE, 0x7D, 0x21, 0x6E, 0xFC,
|
|
||||||
0x41, 0x72, 0xFB, 0xF2, 0x41, 0x65, 0xFF, 0x18, 0x21, 0x6C, 0xFC, 0x42, 0x72, 0x75, 0xFB, 0xE7,
|
|
||||||
0xFF, 0xFD, 0x41, 0x68, 0xFB, 0xEA, 0xA0, 0x08, 0x02, 0x21, 0x74, 0xFD, 0xA1, 0x02, 0x93, 0x6C,
|
|
||||||
0xFD, 0xA0, 0x08, 0x53, 0xA1, 0x08, 0x23, 0x72, 0xFD, 0x21, 0xA9, 0xFB, 0x41, 0x6E, 0xF9, 0x80,
|
|
||||||
0x21, 0x69, 0xFC, 0x42, 0x6D, 0x6E, 0xFF, 0xFD, 0xF9, 0x79, 0x42, 0x69, 0x75, 0xFF, 0xF9, 0xF9,
|
|
||||||
0x72, 0x41, 0x72, 0xFB, 0x57, 0x45, 0x61, 0xC3, 0x69, 0x6C, 0x75, 0xFF, 0xD7, 0xFF, 0xE4, 0xFD,
|
|
||||||
0x7D, 0xFF, 0xF5, 0xFF, 0xFC, 0xA0, 0x08, 0x83, 0xA1, 0x02, 0x93, 0x74, 0xFD, 0x21, 0x75, 0xB9,
|
|
||||||
0x21, 0x6C, 0xB6, 0xA3, 0x02, 0x93, 0x61, 0x6C, 0x74, 0xFA, 0xFD, 0xB3, 0xA0, 0x08, 0x23, 0x21,
|
|
||||||
0xA9, 0xFD, 0x42, 0x66, 0x74, 0xFB, 0x26, 0xFB, 0x26, 0x42, 0x6D, 0x6E, 0xF9, 0x06, 0xFF, 0xF9,
|
|
||||||
0x42, 0x66, 0x78, 0xFB, 0x18, 0xFB, 0x18, 0x46, 0x61, 0x65, 0xC3, 0x68, 0x69, 0x6F, 0xFF, 0xD1,
|
|
||||||
0xFF, 0xDC, 0xFF, 0xE8, 0xF9, 0x25, 0xFF, 0xF2, 0xFF, 0xF9, 0x22, 0x62, 0x72, 0xAB, 0xED, 0x41,
|
|
||||||
0x76, 0xFB, 0x50, 0x21, 0x75, 0xFC, 0x48, 0x74, 0x79, 0x61, 0x65, 0x63, 0x68, 0x75, 0x6F, 0xFF,
|
|
||||||
0x4E, 0xFF, 0x57, 0xFF, 0x5A, 0xFF, 0x65, 0xFF, 0x6C, 0xF8, 0xBF, 0xFF, 0xF4, 0xFF, 0xFD, 0xC3,
|
|
||||||
0x00, 0x61, 0x6E, 0x75, 0x76, 0xF9, 0xD1, 0xF9, 0xE4, 0xF9, 0xF0, 0x41, 0x68, 0xF8, 0x9A, 0x43,
|
|
||||||
0x63, 0x6E, 0x74, 0xF9, 0xD7, 0xF9, 0xD7, 0xF9, 0xD7, 0x41, 0x6E, 0xF9, 0xCD, 0x22, 0x61, 0x6F,
|
|
||||||
0xF2, 0xFC, 0x21, 0x69, 0xFB, 0x43, 0x61, 0x68, 0x72, 0xFC, 0x52, 0xF8, 0x80, 0xFF, 0xFD, 0x41,
|
|
||||||
0x2E, 0xFE, 0x2D, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFD, 0x21, 0x65, 0xFD, 0x41, 0x62, 0xFD, 0xD2, 0x21, 0x6F, 0xFC, 0x21, 0x6E, 0xFD, 0x21,
|
|
||||||
0x6F, 0xFD, 0x42, 0x73, 0x74, 0xF7, 0xFF, 0xF7, 0xFF, 0x42, 0x65, 0x69, 0xF7, 0xF8, 0xFF, 0xF9,
|
|
||||||
0x41, 0x78, 0xFD, 0xFC, 0xA2, 0x02, 0x72, 0x6C, 0x75, 0xF5, 0xFC, 0x41, 0x72, 0xFD, 0xF1, 0x42,
|
|
||||||
0xA9, 0xA8, 0xFD, 0x4A, 0xFF, 0xFC, 0xC2, 0x02, 0x72, 0x6C, 0x72, 0xFD, 0xE6, 0xFD, 0xE6, 0x41,
|
|
||||||
0x69, 0xF7, 0xD2, 0xA1, 0x02, 0x72, 0x66, 0xFC, 0x41, 0x73, 0xFD, 0xD4, 0xA1, 0x01, 0xB1, 0x73,
|
|
||||||
0xFC, 0x41, 0x72, 0xFA, 0xC2, 0x47, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x74, 0xFF, 0xCF, 0xFF,
|
|
||||||
0xDA, 0xFF, 0xE1, 0xFF, 0xEE, 0xF9, 0x51, 0xFF, 0xF7, 0xFF, 0xFC, 0x21, 0xA9, 0xEA, 0x41, 0x70,
|
|
||||||
0xF8, 0x3E, 0x42, 0x69, 0x6F, 0xF8, 0x3A, 0xF8, 0x3A, 0x21, 0x73, 0xF9, 0x41, 0x75, 0xF8, 0x30,
|
|
||||||
0x44, 0x61, 0x69, 0x6F, 0x72, 0xFF, 0xEE, 0xFF, 0xF9, 0xFF, 0xFC, 0xF8, 0x8C, 0x41, 0x63, 0xF8,
|
|
||||||
0x22, 0x41, 0x72, 0xF8, 0x1B, 0x41, 0x64, 0xF8, 0x17, 0x21, 0x6E, 0xFC, 0x21, 0x65, 0xFD, 0x41,
|
|
||||||
0x73, 0xF8, 0x0D, 0x21, 0x6E, 0xFC, 0x24, 0x65, 0x69, 0x6C, 0x6F, 0xE7, 0xEB, 0xF6, 0xFD, 0x41,
|
|
||||||
0x69, 0xF8, 0x73, 0x21, 0x75, 0xFC, 0xC1, 0x01, 0xE2, 0x65, 0xFA, 0x36, 0x41, 0x64, 0xF6, 0xDA,
|
|
||||||
0x44, 0x62, 0x67, 0x6E, 0x74, 0xF6, 0xD6, 0xF6, 0xD6, 0xFF, 0xFC, 0xF6, 0xD6, 0x42, 0x6E, 0x72,
|
|
||||||
0xF6, 0xC9, 0xF6, 0xC9, 0x21, 0xA9, 0xF9, 0x42, 0x6D, 0x70, 0xF6, 0xBF, 0xF6, 0xBF, 0x42, 0x63,
|
|
||||||
0x70, 0xF6, 0xB8, 0xF6, 0xB8, 0xA0, 0x07, 0xA2, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x74,
|
|
||||||
0xF7, 0x22, 0x63, 0x6E, 0xFD, 0xF4, 0xA2, 0x00, 0xC2, 0x65, 0x69, 0xF5, 0xFB, 0xC7, 0x01, 0xE2,
|
|
||||||
0x61, 0xC3, 0x69, 0x6F, 0x72, 0x75, 0x79, 0xFF, 0xC3, 0xFF, 0xD7, 0xFF, 0xDA, 0xFF, 0xE1, 0xFF,
|
|
||||||
0xF9, 0xF6, 0x99, 0xF6, 0x99, 0xC5, 0x02, 0x52, 0x63, 0x70, 0x71, 0x73, 0x74, 0xFF, 0x6B, 0xFF,
|
|
||||||
0x91, 0xFF, 0x9E, 0xFF, 0xA1, 0xFF, 0xE8, 0x21, 0x73, 0xEE, 0x42, 0xC3, 0x65, 0xFF, 0x41, 0xFF,
|
|
||||||
0xFD, 0x41, 0x74, 0xF7, 0x02, 0x21, 0x61, 0xFC, 0x53, 0x61, 0xC3, 0x62, 0x63, 0x64, 0x65, 0x69,
|
|
||||||
0x6D, 0x70, 0x73, 0x6F, 0x6B, 0x74, 0x67, 0x6E, 0x72, 0x6C, 0x75, 0x79, 0xF8, 0xB1, 0xF8, 0xE6,
|
|
||||||
0xF9, 0x32, 0xF9, 0xCA, 0xFB, 0x03, 0xF7, 0x50, 0xFB, 0x2C, 0xFC, 0x27, 0xFD, 0x92, 0xFE, 0x6E,
|
|
||||||
0xFE, 0x87, 0xFE, 0x93, 0xFE, 0xAD, 0xFE, 0xCA, 0xFE, 0xD7, 0xFF, 0xF2, 0xFF, 0xFD, 0xF8, 0x85,
|
|
||||||
0xF8, 0x85, 0xA0, 0x00, 0x81, 0x41, 0xAE, 0xFE, 0x87, 0xA0, 0x02, 0x31, 0x21, 0x2E, 0xFD, 0x21,
|
|
||||||
0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x42, 0x74, 0x65, 0xF8, 0x91, 0xFF, 0xFD, 0x23, 0x68, 0xC3, 0x73,
|
|
||||||
0xE6, 0xE9, 0xF9, 0x21, 0x68, 0xDF, 0xA0, 0x00, 0xA2, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21,
|
|
||||||
0x64, 0xFD, 0x21, 0xA8, 0xFD, 0xA0, 0x00, 0xE1, 0x21, 0x6C, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6F,
|
|
||||||
0xFD, 0xA0, 0x00, 0xF2, 0x21, 0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6C, 0xFD, 0x22, 0x63, 0x61,
|
|
||||||
0xF1, 0xFD, 0xA0, 0x00, 0xE2, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3,
|
|
||||||
0xFD, 0x21, 0x68, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x2E, 0xF6, 0x46, 0x21, 0x74,
|
|
||||||
0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x41, 0x2E, 0xF8, 0xC6, 0x21, 0x74,
|
|
||||||
0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x66, 0xFD, 0x21, 0x69, 0xFD, 0x23, 0x65, 0x69, 0x74, 0xD1, 0xE1, 0xFD, 0x41, 0x74, 0xFE,
|
|
||||||
0x84, 0x21, 0x73, 0xFC, 0x41, 0x72, 0xF8, 0xDB, 0x21, 0x61, 0xFC, 0x22, 0x6F, 0x70, 0xF6, 0xFD,
|
|
||||||
0x41, 0x73, 0xF5, 0xD8, 0x21, 0x69, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x21, 0x68, 0xFD, 0xA0, 0x06, 0x41, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x41,
|
|
||||||
0x2E, 0xFF, 0x33, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x22, 0x69, 0x65, 0xF3, 0xFD, 0x22, 0x63,
|
|
||||||
0x6D, 0xE5, 0xFB, 0xA0, 0x02, 0x02, 0x21, 0x6F, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xEA, 0x22,
|
|
||||||
0x74, 0x6D, 0xFA, 0xFD, 0x41, 0x65, 0xFF, 0x1E, 0xA0, 0x03, 0x21, 0x21, 0x2E, 0xFD, 0x21, 0x74,
|
|
||||||
0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x21, 0x75, 0xFD, 0x22, 0x63, 0x71, 0xDE, 0xFD, 0x21, 0x73, 0xC8, 0x21, 0x6F,
|
|
||||||
0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6C, 0xF8, 0x6B, 0x21, 0x69, 0xFC, 0xA0, 0x05, 0xE1, 0x21, 0x2E,
|
|
||||||
0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD,
|
|
||||||
0x21, 0x67, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x41, 0x6D, 0xFF, 0xA3, 0x4E, 0x62, 0x64,
|
|
||||||
0xC3, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x63, 0x67, 0x76, 0x6D, 0x69, 0x75, 0xFE, 0xCF, 0xFE, 0xD6,
|
|
||||||
0xFE, 0xE5, 0xFF, 0x00, 0xFF, 0x49, 0xFF, 0x5E, 0xFF, 0x91, 0xFF, 0xA2, 0xFF, 0xC9, 0xFF, 0xD4,
|
|
||||||
0xFF, 0xDB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFC, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB,
|
|
||||||
0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xA0, 0x02,
|
|
||||||
0x41, 0x21, 0x2E, 0xFD, 0xA0, 0x00, 0x41, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0xA3, 0x00, 0xE1,
|
|
||||||
0x2E, 0x73, 0x6E, 0xF1, 0xF4, 0xFD, 0x23, 0x2E, 0x73, 0x6E, 0xE8, 0xEB, 0xF4, 0xA1, 0x00, 0xE2,
|
|
||||||
0x65, 0xF9, 0xA0, 0x02, 0xF1, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x42, 0x74,
|
|
||||||
0x6D, 0xFF, 0xFD, 0xFE, 0xB6, 0xA1, 0x00, 0xE1, 0x75, 0xF9, 0xC2, 0x00, 0xE2, 0x65, 0x75, 0xFF,
|
|
||||||
0xDC, 0xFE, 0xAD, 0x49, 0x61, 0xC3, 0x65, 0x69, 0x6C, 0x6F, 0x72, 0x75, 0x79, 0xFE, 0x62, 0xFF,
|
|
||||||
0xA5, 0xFF, 0xCA, 0xFE, 0x62, 0xFF, 0xDA, 0xFF, 0xF2, 0xFF, 0xF7, 0xFE, 0x62, 0xFE, 0x62, 0x43,
|
|
||||||
0x65, 0x69, 0x75, 0xFE, 0x23, 0xFC, 0x9D, 0xFC, 0x9D, 0x41, 0x69, 0xF4, 0xB7, 0xA0, 0x05, 0x92,
|
|
||||||
0x21, 0x65, 0xFD, 0x21, 0x75, 0xFD, 0x22, 0x65, 0x71, 0xF7, 0xFD, 0x21, 0x69, 0xFB, 0x43, 0x65,
|
|
||||||
0x68, 0x72, 0xFE, 0x04, 0xFF, 0xEB, 0xFF, 0xFD, 0x21, 0x72, 0xE5, 0x21, 0x74, 0xFD, 0x21, 0x63,
|
|
||||||
0xFD, 0x21, 0x74, 0xDC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0xA9, 0xFD,
|
|
||||||
0x41, 0x75, 0xF7, 0x4F, 0x21, 0x71, 0xFC, 0x44, 0x65, 0xC3, 0x69, 0x6F, 0xFF, 0xE7, 0xFF, 0xF6,
|
|
||||||
0xFC, 0x55, 0xFF, 0xFD, 0x21, 0x67, 0xB9, 0x21, 0x72, 0xFD, 0x41, 0x74, 0xF7, 0x35, 0x22, 0x65,
|
|
||||||
0x69, 0xF9, 0xFC, 0xC1, 0x01, 0xC2, 0x65, 0xF4, 0x00, 0x21, 0x70, 0xFA, 0x21, 0x6F, 0xFD, 0x21,
|
|
||||||
0x63, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x6C, 0xF6, 0xCF, 0x21, 0x6C, 0xFC, 0x21,
|
|
||||||
0x69, 0xFD, 0x41, 0x6C, 0xFE, 0x92, 0x21, 0x61, 0xFC, 0x41, 0x74, 0xFE, 0x0B, 0x21, 0x6F, 0xFC,
|
|
||||||
0x22, 0x76, 0x70, 0xF6, 0xFD, 0x42, 0x69, 0x65, 0xFF, 0xFB, 0xFD, 0x8D, 0x21, 0x75, 0xF9, 0x48,
|
|
||||||
0x63, 0x64, 0x6C, 0x6E, 0x70, 0x6D, 0x71, 0x72, 0xFF, 0x60, 0xFF, 0x7F, 0xFF, 0xA8, 0xFF, 0xBF,
|
|
||||||
0xFF, 0xD6, 0xFF, 0xE0, 0xFF, 0xFD, 0xFE, 0x65, 0x45, 0xA7, 0xA9, 0xA2, 0xA8, 0xB4, 0xFD, 0x8D,
|
|
||||||
0xFF, 0xE7, 0xFE, 0xA1, 0xFE, 0xA1, 0xFE, 0xA1, 0xA0, 0x02, 0xC3, 0x21, 0x74, 0xFD, 0x21, 0x75,
|
|
||||||
0xFD, 0x41, 0x69, 0xFA, 0xC0, 0x41, 0x2E, 0xF3, 0xB5, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21,
|
|
||||||
0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0xAA, 0xFD, 0x21, 0xC3, 0xFD, 0xA3, 0x00, 0xE1, 0x6F, 0x70,
|
|
||||||
0x72, 0xE3, 0xE6, 0xFD, 0xA0, 0x06, 0x51, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD,
|
|
||||||
0x44, 0x2E, 0x73, 0x6E, 0x76, 0xFE, 0x9E, 0xFE, 0xA1, 0xFE, 0xAA, 0xFF, 0xFD, 0x42, 0x2E, 0x73,
|
|
||||||
0xFE, 0x91, 0xFE, 0x94, 0xA0, 0x03, 0x63, 0x21, 0x63, 0xFD, 0xA0, 0x03, 0x93, 0x21, 0x74, 0xFD,
|
|
||||||
0x21, 0xA9, 0xFD, 0x22, 0x61, 0xC3, 0xF4, 0xFD, 0x21, 0x72, 0xFB, 0xA2, 0x00, 0x81, 0x65, 0x6F,
|
|
||||||
0xE2, 0xFD, 0xC2, 0x00, 0x81, 0x65, 0x6F, 0xFF, 0xDB, 0xFB, 0x6A, 0x41, 0x64, 0xF5, 0x75, 0x21,
|
|
||||||
0x6E, 0xFC, 0x21, 0x65, 0xFD, 0xCD, 0x00, 0xE2, 0x2E, 0x62, 0x65, 0x67, 0x6C, 0x6D, 0x6E, 0x70,
|
|
||||||
0x72, 0x73, 0x74, 0x77, 0x69, 0xFE, 0x59, 0xFE, 0x5F, 0xFF, 0xBB, 0xFE, 0x5F, 0xFF, 0xE6, 0xFE,
|
|
||||||
0x5F, 0xFE, 0x5F, 0xFE, 0x5F, 0xFF, 0xED, 0xFE, 0x5F, 0xFE, 0x5F, 0xFE, 0x5F, 0xFF, 0xFD, 0x41,
|
|
||||||
0x6C, 0xF2, 0xB8, 0xA1, 0x00, 0xE1, 0x6C, 0xFC, 0xA0, 0x03, 0xC2, 0xC9, 0x00, 0xE2, 0x2E, 0x62,
|
|
||||||
0x65, 0x66, 0x67, 0x68, 0x70, 0x73, 0x74, 0xFE, 0x23, 0xFE, 0x29, 0xFE, 0x3B, 0xFE, 0x29, 0xFE,
|
|
||||||
0x29, 0xFF, 0xFD, 0xFE, 0x29, 0xFE, 0x29, 0xFE, 0x29, 0xC2, 0x00, 0xE2, 0x65, 0x61, 0xFE, 0x1D,
|
|
||||||
0xFC, 0xEE, 0xA0, 0x03, 0xE1, 0x22, 0x63, 0x71, 0xFD, 0xFD, 0xA0, 0x03, 0xF2, 0x21, 0x63, 0xF5,
|
|
||||||
0x21, 0x72, 0xF2, 0x22, 0x6F, 0x75, 0xFA, 0xFD, 0x21, 0x73, 0xFB, 0x27, 0x63, 0x64, 0x70, 0x72,
|
|
||||||
0x73, 0x75, 0x78, 0xEA, 0xEF, 0xE7, 0xE7, 0xFD, 0xE7, 0xE7, 0xA0, 0x04, 0x12, 0x21, 0xA9, 0xFD,
|
|
||||||
0x23, 0x66, 0x6E, 0x78, 0xD2, 0xD2, 0xD2, 0x41, 0x62, 0xFC, 0x3B, 0x21, 0x72, 0xFC, 0x41, 0x69,
|
|
||||||
0xFF, 0x5D, 0x41, 0x2E, 0xFD, 0xE0, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x42,
|
|
||||||
0x67, 0x65, 0xFF, 0xFD, 0xF4, 0xBE, 0x21, 0x6E, 0xF9, 0x21, 0x69, 0xFD, 0x41, 0x76, 0xF4, 0xB4,
|
|
||||||
0x21, 0x69, 0xFC, 0x24, 0x75, 0x66, 0x74, 0x6E, 0xD8, 0xDB, 0xF6, 0xFD, 0x41, 0x69, 0xF2, 0xCF,
|
|
||||||
0x21, 0x74, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6C, 0xF4, 0x97, 0x21, 0x75, 0xFC,
|
|
||||||
0x21, 0x70, 0xFD, 0x21, 0x74, 0xC9, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x70, 0xFD, 0xC7,
|
|
||||||
0x00, 0xE1, 0x61, 0xC3, 0x65, 0x6E, 0x67, 0x72, 0x6D, 0xFF, 0x8C, 0xFF, 0x9E, 0xFF, 0xA1, 0xFF,
|
|
||||||
0xD4, 0xFF, 0xE7, 0xFF, 0xF1, 0xFF, 0xFD, 0x41, 0x93, 0xFB, 0xFE, 0x41, 0x72, 0xF2, 0x88, 0xA1,
|
|
||||||
0x00, 0xE1, 0x72, 0xFC, 0xC1, 0x00, 0xE1, 0x72, 0xFE, 0x7D, 0x41, 0x64, 0xF2, 0x79, 0x21, 0x69,
|
|
||||||
0xFC, 0x4D, 0x61, 0xC3, 0x65, 0x68, 0x69, 0x6B, 0x6C, 0x6F, 0xC5, 0x72, 0x75, 0x79, 0x63, 0xFE,
|
|
||||||
0x8A, 0xFD, 0x27, 0xFD, 0x4C, 0xFE, 0xE4, 0xFF, 0x12, 0xFF, 0x1A, 0xFF, 0x38, 0xFF, 0xCE, 0xFF,
|
|
||||||
0xE6, 0xFD, 0x5C, 0xFF, 0xEE, 0xFF, 0xF3, 0xFF, 0xFD, 0x41, 0x63, 0xFC, 0x7B, 0xC3, 0x00, 0xE1,
|
|
||||||
0x61, 0x6B, 0x65, 0xFF, 0xFC, 0xFD, 0x17, 0xFD, 0x29, 0x41, 0x63, 0xFF, 0x53, 0x21, 0x69, 0xFC,
|
|
||||||
0x21, 0x66, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x00, 0xE1, 0x6E, 0xFD, 0x41, 0x74, 0xF2, 0x5A, 0xA1,
|
|
||||||
0x00, 0x91, 0x65, 0xFC, 0x21, 0x6C, 0xFB, 0xC3, 0x00, 0xE1, 0x6C, 0x6D, 0x74, 0xFF, 0xFD, 0xFC,
|
|
||||||
0x45, 0xFB, 0x1A, 0x41, 0x6C, 0xFF, 0x29, 0x21, 0x61, 0xFC, 0x21, 0x76, 0xFD, 0x41, 0x61, 0xF2,
|
|
||||||
0xF5, 0x21, 0xA9, 0xFC, 0x21, 0xC3, 0xFD, 0x21, 0x72, 0xFD, 0x22, 0x6F, 0x74, 0xF0, 0xFD, 0xA0,
|
|
||||||
0x04, 0xC3, 0x21, 0x67, 0xFD, 0x21, 0xA2, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65,
|
|
||||||
0xFD, 0xA2, 0x00, 0xE1, 0x6E, 0x79, 0xE9, 0xFD, 0x41, 0x6E, 0xFF, 0x2B, 0x21, 0x6F, 0xFC, 0xA1,
|
|
||||||
0x00, 0xE1, 0x63, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xFB, 0x41, 0xFF, 0xFB,
|
|
||||||
0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xC2, 0x00, 0xE1, 0x2E, 0x73, 0xFC,
|
|
||||||
0x84, 0xFC, 0x87, 0x41, 0x6F, 0xFB, 0x3F, 0x42, 0x6D, 0x73, 0xFF, 0xFC, 0xFB, 0x3E, 0x41, 0x73,
|
|
||||||
0xFB, 0x34, 0x22, 0xA9, 0xA8, 0xF5, 0xFC, 0x21, 0xC3, 0xFB, 0xA0, 0x02, 0xA2, 0x4A, 0x75, 0x69,
|
|
||||||
0x6F, 0x61, 0xC3, 0x65, 0x6E, 0xC5, 0x73, 0x79, 0xFF, 0x69, 0xFF, 0x7A, 0xFF, 0xB4, 0xFB, 0x08,
|
|
||||||
0xFF, 0xC7, 0xFF, 0xDD, 0xFF, 0xFA, 0xFF, 0x0A, 0xFF, 0xFD, 0xFB, 0x08, 0x41, 0x63, 0xF3, 0x54,
|
|
||||||
0x21, 0x69, 0xFC, 0x41, 0x67, 0xFE, 0x89, 0x21, 0x72, 0xFC, 0x21, 0x75, 0xFD, 0x41, 0x61, 0xF3,
|
|
||||||
0x46, 0xC4, 0x00, 0xE1, 0x74, 0x67, 0x73, 0x6D, 0xFF, 0xEF, 0xF1, 0x62, 0xFF, 0xF9, 0xFF, 0xFC,
|
|
||||||
0x47, 0xA9, 0xA2, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xFF, 0xF1, 0xFA, 0xC5, 0xFA, 0xC5, 0xFA, 0xC5,
|
|
||||||
0xFA, 0xC5, 0xFA, 0xC5, 0xFA, 0xC5, 0x41, 0x67, 0xF1, 0x3D, 0xC2, 0x00, 0xE1, 0x6E, 0x6D, 0xFF,
|
|
||||||
0xFC, 0xFB, 0x62, 0x42, 0x65, 0x69, 0xFA, 0x7F, 0xF8, 0xF9, 0xC5, 0x00, 0xE1, 0x6C, 0x70, 0x2E,
|
|
||||||
0x73, 0x6E, 0xFF, 0xF9, 0xFB, 0x5A, 0xFB, 0xF4, 0xFB, 0xF7, 0xFC, 0x00, 0xC1, 0x00, 0xE1, 0x6C,
|
|
||||||
0xFB, 0x48, 0x41, 0x6D, 0xF1, 0x11, 0x41, 0x61, 0xF0, 0xC1, 0x21, 0x6F, 0xFC, 0x21, 0x69, 0xFD,
|
|
||||||
0xC3, 0x00, 0xE1, 0x6D, 0x69, 0x64, 0xFB, 0x2C, 0xFF, 0xF2, 0xFF, 0xFD, 0x41, 0x68, 0xF8, 0xC0,
|
|
||||||
0xA1, 0x00, 0xE1, 0x74, 0xFC, 0xA0, 0x07, 0xC2, 0x21, 0x72, 0xFD, 0x43, 0x2E, 0x73, 0x75, 0xFB,
|
|
||||||
0xB3, 0xFB, 0xB6, 0xFF, 0xFD, 0x21, 0x64, 0xF3, 0xA2, 0x00, 0xE2, 0x65, 0x79, 0xF3, 0xFD, 0x4A,
|
|
||||||
0xC3, 0x69, 0x63, 0x6D, 0x65, 0x75, 0x61, 0x79, 0x68, 0x6F, 0xFF, 0x81, 0xFF, 0x9B, 0xFB, 0x39,
|
|
||||||
0xFB, 0x39, 0xFF, 0xAB, 0xFF, 0xBD, 0xFF, 0xD1, 0xFF, 0xE1, 0xFF, 0xF9, 0xFA, 0x46, 0xA0, 0x03,
|
|
||||||
0x11, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x22, 0x63, 0x7A,
|
|
||||||
0xFD, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x64, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x76,
|
|
||||||
0xFD, 0x21, 0x6E, 0xE9, 0x21, 0x69, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0xA9, 0xFD, 0x42, 0xC3, 0x73,
|
|
||||||
0xFF, 0xFD, 0xF3, 0x42, 0x21, 0xA9, 0xF9, 0x41, 0x6E, 0xFA, 0x3D, 0x21, 0x69, 0xFC, 0x21, 0x6D,
|
|
||||||
0xFD, 0x21, 0xA9, 0xFD, 0x41, 0x74, 0xF4, 0xB0, 0x22, 0xC3, 0x73, 0xF9, 0xFC, 0xC5, 0x00, 0xE2,
|
|
||||||
0x69, 0x75, 0xC3, 0x6F, 0x65, 0xFF, 0xD1, 0xFD, 0xED, 0xFF, 0xE7, 0xFF, 0xFB, 0xFB, 0x49, 0x41,
|
|
||||||
0x65, 0xF0, 0x5C, 0x21, 0x6C, 0xFC, 0x42, 0x62, 0x63, 0xFF, 0xFD, 0xF0, 0x55, 0x21, 0x61, 0xF9,
|
|
||||||
0x21, 0x6E, 0xFD, 0xC3, 0x00, 0xE1, 0x67, 0x70, 0x73, 0xFF, 0xFD, 0xFC, 0x3E, 0xFC, 0x3E, 0x41,
|
|
||||||
0x6D, 0xF2, 0x05, 0x44, 0x61, 0x65, 0x69, 0x6F, 0xF2, 0x01, 0xF2, 0x01, 0xF2, 0x01, 0xFF, 0xFC,
|
|
||||||
0x21, 0x6C, 0xF3, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x06, 0xD2, 0x21, 0xA9, 0xFD, 0x21,
|
|
||||||
0xC3, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0xA2, 0x00, 0xE1, 0x70, 0x6C,
|
|
||||||
0xEB, 0xFD, 0x42, 0xA9, 0xA8, 0xF5, 0x47, 0xF5, 0x47, 0x48, 0x76, 0x61, 0x65, 0xC3, 0x69, 0x6F,
|
|
||||||
0x73, 0x75, 0xFD, 0xEE, 0xF1, 0x6D, 0xF1, 0x6D, 0xFF, 0xF9, 0xF1, 0x6D, 0xF1, 0x6D, 0xF1, 0x6D,
|
|
||||||
0xF1, 0x6D, 0x21, 0x79, 0xE7, 0x41, 0x65, 0xFC, 0xAD, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x21,
|
|
||||||
0x73, 0xFD, 0xA2, 0x00, 0xE1, 0x6C, 0x61, 0xF0, 0xFD, 0xC2, 0x00, 0xE2, 0x75, 0x65, 0xF9, 0x7E,
|
|
||||||
0xFA, 0xAD, 0x43, 0x6D, 0x74, 0x68, 0xFE, 0x5B, 0xF1, 0xA4, 0xEF, 0x15, 0xC4, 0x00, 0xE1, 0x72,
|
|
||||||
0x2E, 0x73, 0x6E, 0xFF, 0xF6, 0xFA, 0x82, 0xFA, 0x85, 0xFA, 0x8E, 0x41, 0x6C, 0xEF, 0x95, 0x21,
|
|
||||||
0x75, 0xFC, 0xA0, 0x06, 0xF3, 0x21, 0x71, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0xA2, 0x00,
|
|
||||||
0xE1, 0x6E, 0x72, 0xF1, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF9, 0x00, 0xFF,
|
|
||||||
0xF9, 0xF9, 0x00, 0xF9, 0x00, 0xF9, 0x00, 0xF9, 0x00, 0xF9, 0x00, 0xC1, 0x00, 0x81, 0x65, 0xFB,
|
|
||||||
0xB2, 0x41, 0x73, 0xEF, 0x26, 0x21, 0x6F, 0xFC, 0x21, 0x74, 0xFD, 0xA0, 0x07, 0x62, 0x21, 0xA9,
|
|
||||||
0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x73, 0xF4, 0xA2, 0x00, 0x41, 0x61, 0x69, 0xFA,
|
|
||||||
0xFD, 0xC8, 0x00, 0xE2, 0x2E, 0x65, 0x6C, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0xFA, 0x1D, 0xFA, 0x35,
|
|
||||||
0xFF, 0xDA, 0xFA, 0x23, 0xFF, 0xE7, 0xFF, 0xDA, 0xFA, 0x23, 0xFF, 0xF9, 0x41, 0xA9, 0xF8, 0xC6,
|
|
||||||
0x41, 0x75, 0xF8, 0xC2, 0x22, 0xC3, 0x65, 0xF8, 0xFC, 0x41, 0x68, 0xF8, 0xB9, 0x21, 0x63, 0xFC,
|
|
||||||
0x21, 0x79, 0xFD, 0x41, 0x72, 0xF8, 0xAF, 0x22, 0xA8, 0xA9, 0xFC, 0xFC, 0x21, 0xC3, 0xFB, 0x4D,
|
|
||||||
0x72, 0x75, 0x61, 0x69, 0x6F, 0x6C, 0x65, 0xC3, 0x68, 0x6E, 0x73, 0x74, 0x79, 0xFE, 0xAE, 0xFE,
|
|
||||||
0xD4, 0xFF, 0x0C, 0xFC, 0x95, 0xFF, 0x43, 0xFF, 0x4A, 0xFF, 0x5D, 0xFF, 0x86, 0xFF, 0xC2, 0xFF,
|
|
||||||
0xE5, 0xFF, 0xF1, 0xFF, 0xFD, 0xF8, 0x86, 0x41, 0x63, 0xF1, 0xA8, 0x21, 0x6F, 0xFC, 0x41, 0x64,
|
|
||||||
0xF1, 0xA1, 0x21, 0x69, 0xFC, 0x41, 0x67, 0xF1, 0x9A, 0x41, 0x67, 0xF0, 0xB7, 0x21, 0x6C, 0xFC,
|
|
||||||
0x41, 0x6C, 0xF1, 0x8F, 0x23, 0x69, 0x75, 0x6F, 0xF1, 0xF9, 0xFC, 0x41, 0x67, 0xF8, 0x89, 0x21,
|
|
||||||
0x69, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x42, 0x65, 0x69, 0xFF, 0xFD, 0xF6, 0x84, 0x42,
|
|
||||||
0x74, 0x6F, 0xF9, 0xAC, 0xFF, 0xE1, 0x41, 0x74, 0xF8, 0x1F, 0x21, 0x61, 0xFC, 0x21, 0x6D, 0xFD,
|
|
||||||
0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0x26, 0x6E, 0x63, 0x64, 0x74, 0x73, 0x66, 0xB5, 0xBC, 0xCE,
|
|
||||||
0xE2, 0xE9, 0xFD, 0x41, 0xA9, 0xF8, 0xB0, 0x42, 0x61, 0x6F, 0xF8, 0xAC, 0xF8, 0xAC, 0x22, 0xC3,
|
|
||||||
0x69, 0xF5, 0xF9, 0x42, 0x65, 0x68, 0xF7, 0xCF, 0xFF, 0xFB, 0x41, 0x74, 0xFC, 0xE0, 0x21, 0x61,
|
|
||||||
0xFC, 0x22, 0x63, 0x74, 0xF2, 0xFD, 0x41, 0x2E, 0xF0, 0xE1, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD,
|
|
||||||
0x21, 0x65, 0xFD, 0x21, 0x63, 0xFD, 0x42, 0x73, 0x6E, 0xFF, 0xFD, 0xF1, 0x19, 0x41, 0x6E, 0xF1,
|
|
||||||
0x12, 0x22, 0x69, 0x61, 0xF5, 0xFC, 0x42, 0x75, 0x6F, 0xFF, 0x68, 0xF9, 0xD4, 0x22, 0x6D, 0x70,
|
|
||||||
0xF4, 0xF9, 0xA0, 0x00, 0xA1, 0x21, 0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x72, 0xF7, 0x21, 0x68,
|
|
||||||
0xFD, 0x21, 0x74, 0xFD, 0x22, 0x6C, 0x72, 0xF4, 0xFD, 0x41, 0x6C, 0xF7, 0x69, 0x41, 0x72, 0xFA,
|
|
||||||
0x24, 0x41, 0x74, 0xFA, 0xF9, 0x21, 0x63, 0xFC, 0x21, 0x79, 0xDA, 0x22, 0x61, 0x78, 0xFA, 0xFD,
|
|
||||||
0x41, 0x61, 0xF2, 0x17, 0x49, 0x6E, 0x73, 0x6D, 0x61, 0xC3, 0x6C, 0x62, 0x6F, 0x76, 0xFF, 0x72,
|
|
||||||
0xFF, 0x9D, 0xFF, 0xC9, 0xFF, 0xE0, 0xF7, 0x7E, 0xFF, 0xE5, 0xFF, 0xE9, 0xFF, 0xF7, 0xFF, 0xFC,
|
|
||||||
0x41, 0x70, 0xF8, 0x13, 0x43, 0x65, 0x6F, 0x68, 0xF7, 0x3E, 0xFF, 0xFC, 0xF8, 0x0F, 0x41, 0x69,
|
|
||||||
0xF5, 0xAE, 0x22, 0x63, 0x74, 0xF2, 0xFC, 0xA0, 0x05, 0xB3, 0x21, 0x72, 0xFD, 0x21, 0x76, 0xFD,
|
|
||||||
0x41, 0x65, 0xFE, 0xF9, 0x21, 0x72, 0xFC, 0x22, 0x69, 0x74, 0xF6, 0xFD, 0x41, 0x61, 0xFF, 0xA5,
|
|
||||||
0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0xC2, 0x01, 0x71, 0x63, 0x69, 0xED, 0x74, 0xED, 0x74, 0x21,
|
|
||||||
0x61, 0xF7, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x45, 0x73, 0x6E, 0x75, 0x78, 0x72, 0xFF, 0xCA,
|
|
||||||
0xFF, 0xDF, 0xFF, 0xEB, 0xFF, 0xFD, 0xF8, 0x31, 0xC1, 0x00, 0xE1, 0x6D, 0xF7, 0xC4, 0x41, 0x61,
|
|
||||||
0xF9, 0xFD, 0x41, 0x6D, 0xFA, 0xAA, 0x21, 0x69, 0xFC, 0x21, 0x72, 0xFD, 0xA2, 0x00, 0xE1, 0x63,
|
|
||||||
0x74, 0xF2, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF6, 0xF2, 0xFF, 0xF9, 0xF6,
|
|
||||||
0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0x41, 0x68, 0xFB, 0xD1, 0x41, 0x70, 0xED,
|
|
||||||
0x6E, 0x21, 0x6F, 0xFC, 0x43, 0x73, 0x63, 0x74, 0xFA, 0x6A, 0xFF, 0xFD, 0xF8, 0x57, 0x41, 0x69,
|
|
||||||
0xFE, 0x77, 0x41, 0x2E, 0xEE, 0x5F, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x70,
|
|
||||||
0xFD, 0xA3, 0x00, 0xE1, 0x73, 0x6C, 0x61, 0xD3, 0xDD, 0xFD, 0xA0, 0x05, 0x52, 0x21, 0x6C, 0xFD,
|
|
||||||
0x21, 0x64, 0xFA, 0x21, 0x75, 0xFD, 0x22, 0x61, 0x6F, 0xF7, 0xFD, 0x41, 0x6E, 0xF7, 0xEF, 0x21,
|
|
||||||
0x65, 0xFC, 0x4D, 0x27, 0x61, 0xC3, 0x64, 0x65, 0x69, 0x68, 0x6C, 0x6F, 0x72, 0x73, 0x75, 0x79,
|
|
||||||
0xF6, 0x83, 0xFF, 0x76, 0xFF, 0x91, 0xFF, 0xA7, 0xF7, 0xEB, 0xFF, 0xDF, 0xFF, 0xF4, 0xFF, 0xFD,
|
|
||||||
0xF6, 0x83, 0xF7, 0xFB, 0xFB, 0x78, 0xF6, 0x83, 0xF6, 0x83, 0x41, 0x63, 0xFA, 0x33, 0x41, 0x72,
|
|
||||||
0xF6, 0xA6, 0xA1, 0x01, 0xC2, 0x61, 0xFC, 0x41, 0x73, 0xEF, 0xDE, 0xC2, 0x05, 0x23, 0x63, 0x74,
|
|
||||||
0xF0, 0x03, 0xFF, 0xFC, 0x45, 0x70, 0x61, 0x68, 0x6F, 0x75, 0xFF, 0xEE, 0xFF, 0xF7, 0xEC, 0xAD,
|
|
||||||
0xF0, 0x56, 0xF0, 0x56, 0x21, 0x73, 0xF0, 0x21, 0x6E, 0xFD, 0xC4, 0x00, 0xE2, 0x69, 0x75, 0x61,
|
|
||||||
0x65, 0xFA, 0x40, 0xFF, 0xD0, 0xFF, 0xFD, 0xF7, 0x9C, 0x41, 0x79, 0xFB, 0x9D, 0x21, 0x68, 0xFC,
|
|
||||||
0xC3, 0x00, 0xE1, 0x6E, 0x6D, 0x63, 0xFB, 0x66, 0xF6, 0xCC, 0xFF, 0xFD, 0x41, 0x6D, 0xFB, 0xEE,
|
|
||||||
0x21, 0x61, 0xFC, 0x21, 0x72, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x70, 0xFD, 0x41,
|
|
||||||
0x6D, 0xEE, 0x61, 0x21, 0x61, 0xFC, 0x42, 0x74, 0x2E, 0xFF, 0xFD, 0xF7, 0x48, 0xC5, 0x00, 0xE1,
|
|
||||||
0x72, 0x6D, 0x73, 0x2E, 0x6E, 0xFB, 0x39, 0xFF, 0xEF, 0xFF, 0xF9, 0xF7, 0x41, 0xF7, 0x4D, 0xC2,
|
|
||||||
0x00, 0x81, 0x69, 0x65, 0xF3, 0x22, 0xF8, 0x9E, 0x41, 0x73, 0xEB, 0xD9, 0x21, 0x6F, 0xFC, 0x21,
|
|
||||||
0x6D, 0xFD, 0x44, 0x2E, 0x73, 0x72, 0x75, 0xF7, 0x1C, 0xF7, 0x1F, 0xFF, 0xFD, 0xFB, 0x66, 0xC7,
|
|
||||||
0x00, 0xE2, 0x72, 0x2E, 0x65, 0x6C, 0x6D, 0x6E, 0x73, 0xFF, 0xE0, 0xF7, 0x0F, 0xFF, 0xF3, 0xF7,
|
|
||||||
0x15, 0xF7, 0x15, 0xF7, 0x15, 0xF7, 0x15, 0x41, 0x62, 0xF9, 0x76, 0x41, 0x73, 0xEC, 0x06, 0x21,
|
|
||||||
0x67, 0xFC, 0xC3, 0x00, 0xE1, 0x72, 0x6D, 0x6E, 0xFF, 0xF5, 0xF6, 0x4A, 0xFF, 0xFD, 0xC2, 0x00,
|
|
||||||
0xE1, 0x6D, 0x72, 0xF6, 0x3E, 0xF9, 0x8D, 0x42, 0x62, 0x70, 0xEB, 0x8A, 0xEB, 0x8A, 0x44, 0x65,
|
|
||||||
0x69, 0x6F, 0x73, 0xEB, 0x83, 0xEB, 0x83, 0xFF, 0xF9, 0xEB, 0x83, 0x21, 0xA9, 0xF3, 0x21, 0xC3,
|
|
||||||
0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x48, 0xA2, 0xA0, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF5,
|
|
||||||
0x5F, 0xF5, 0x5F, 0xFF, 0xFB, 0xF5, 0x5F, 0xF5, 0x5F, 0xF5, 0x5F, 0xF5, 0x5F, 0xF5, 0x5F, 0x41,
|
|
||||||
0x74, 0xF1, 0x2A, 0x21, 0x6E, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x68, 0xFD, 0x41, 0x6C, 0xFA, 0x2E,
|
|
||||||
0x4B, 0x72, 0x61, 0x65, 0x68, 0x75, 0x6F, 0xC3, 0x63, 0x69, 0x74, 0x79, 0xFF, 0x0A, 0xFF, 0x20,
|
|
||||||
0xFF, 0x4D, 0xFF, 0x7F, 0xFF, 0xA2, 0xFF, 0xAE, 0xFF, 0xD6, 0xFF, 0xF9, 0xF5, 0x35, 0xFF, 0xFC,
|
|
||||||
0xF5, 0x35, 0xC1, 0x00, 0xE1, 0x63, 0xF8, 0xEB, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB,
|
|
||||||
0xF5, 0x0D, 0xFF, 0xFA, 0xF5, 0x0D, 0xF5, 0x0D, 0xF5, 0x0D, 0xF5, 0x0D, 0xF5, 0x0D, 0x41, 0x75,
|
|
||||||
0xFF, 0x01, 0x21, 0x68, 0xFC, 0xC2, 0x00, 0xE1, 0x72, 0x63, 0xF5, 0x32, 0xFF, 0xFD, 0xC2, 0x00,
|
|
||||||
0xE2, 0x65, 0x61, 0xF6, 0x58, 0xF3, 0x41, 0x41, 0x74, 0xF6, 0x64, 0xC2, 0x00, 0xE2, 0x65, 0x69,
|
|
||||||
0xF6, 0x4B, 0xFF, 0xFC, 0x4A, 0x61, 0xC3, 0x65, 0x69, 0x6C, 0x6F, 0x72, 0x73, 0x75, 0x79, 0xFD,
|
|
||||||
0xC4, 0xFF, 0xC4, 0xF6, 0x39, 0xFF, 0xE1, 0xFF, 0xEA, 0xF4, 0xD1, 0xFF, 0xF7, 0xF9, 0xC6, 0xFD,
|
|
||||||
0xC4, 0xF4, 0xD1, 0x45, 0x61, 0x65, 0x69, 0x6F, 0x79, 0xF4, 0xCF, 0xF4, 0xCF, 0xF4, 0xCF, 0xF4,
|
|
||||||
0xCF, 0xF4, 0xCF, 0x41, 0x75, 0xFA, 0x87, 0x21, 0x71, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x6C, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x21, 0x64, 0xFD, 0x42, 0x6D, 0x6E, 0xF2, 0xE6, 0xFF, 0xFD, 0xC2, 0x00, 0xE2,
|
|
||||||
0x65, 0x61, 0xF5, 0xF9, 0xFF, 0xF9, 0xC1, 0x00, 0xE1, 0x65, 0xF5, 0xF0, 0x4C, 0x61, 0xC3, 0x65,
|
|
||||||
0x68, 0x69, 0x6C, 0x6E, 0x6F, 0x72, 0x75, 0x73, 0x79, 0xF4, 0x79, 0xF5, 0xBC, 0xF5, 0xE1, 0xFF,
|
|
||||||
0xC7, 0xF7, 0xA7, 0xF5, 0xF1, 0xF5, 0xF1, 0xF4, 0x79, 0xFF, 0xF1, 0xFF, 0xFA, 0xF9, 0x6E, 0xF4,
|
|
||||||
0x79, 0x41, 0x69, 0xEF, 0xBB, 0x21, 0x75, 0xFC, 0x42, 0x71, 0x2E, 0xFF, 0xFD, 0xF5, 0xA6, 0xC5,
|
|
||||||
0x00, 0xE1, 0x72, 0x6D, 0x73, 0x2E, 0x6E, 0xEA, 0xD7, 0xF6, 0x80, 0xFF, 0xF9, 0xF5, 0x9F, 0xF5,
|
|
||||||
0xAB, 0x41, 0x69, 0xF6, 0xD1, 0x42, 0x6C, 0x73, 0xFF, 0xFC, 0xEB, 0x02, 0xA0, 0x02, 0xD2, 0x21,
|
|
||||||
0x68, 0xFD, 0x42, 0xC3, 0x61, 0xFA, 0x3F, 0xFF, 0xFD, 0xC2, 0x06, 0x02, 0x6F, 0x73, 0xF5, 0x12,
|
|
||||||
0xF5, 0x12, 0x21, 0x72, 0xF7, 0x21, 0x65, 0xFD, 0xC5, 0x00, 0xE1, 0x63, 0x62, 0x6D, 0x72, 0x70,
|
|
||||||
0xFD, 0xB2, 0xFF, 0xDD, 0xF4, 0xC4, 0xFF, 0xEA, 0xFF, 0xFD, 0x41, 0x6C, 0xFC, 0x26, 0xA1, 0x00,
|
|
||||||
0xE2, 0x75, 0xFC, 0x21, 0x72, 0xFB, 0x41, 0x61, 0xF4, 0x0C, 0x21, 0x69, 0xFC, 0x21, 0x74, 0xFD,
|
|
||||||
0x41, 0x6D, 0xF4, 0x02, 0x21, 0x72, 0xFC, 0x41, 0x6C, 0xF3, 0xFB, 0x41, 0x6F, 0xF8, 0xC3, 0x22,
|
|
||||||
0x65, 0x72, 0xF8, 0xFC, 0x45, 0x6F, 0x61, 0x65, 0x68, 0x69, 0xFF, 0xDF, 0xFF, 0xE9, 0xFF, 0xF0,
|
|
||||||
0xFB, 0x48, 0xFF, 0xFB, 0x41, 0x6F, 0xF6, 0x5E, 0x42, 0x6C, 0x76, 0xFF, 0xFC, 0xF3, 0xDA, 0x41,
|
|
||||||
0x76, 0xF3, 0xD3, 0x22, 0x61, 0x6F, 0xF5, 0xFC, 0x41, 0x70, 0xFB, 0x11, 0x41, 0xA9, 0xFB, 0x17,
|
|
||||||
0x21, 0xC3, 0xFC, 0x41, 0x70, 0xF3, 0xBF, 0xC3, 0x00, 0xE2, 0x2E, 0x65, 0x73, 0xF4, 0xF7, 0xF6,
|
|
||||||
0x66, 0xF4, 0xFD, 0x24, 0x61, 0x6C, 0x6F, 0x68, 0xE5, 0xED, 0xF0, 0xF4, 0x41, 0x6D, 0xF9, 0x29,
|
|
||||||
0xC6, 0x00, 0xE2, 0x2E, 0x65, 0x6D, 0x6F, 0x72, 0x73, 0xF4, 0xDE, 0xF4, 0xF6, 0xF4, 0xE4, 0xFF,
|
|
||||||
0xFC, 0xF4, 0xE4, 0xF4, 0xE4, 0x41, 0x64, 0xF3, 0x8D, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0x21,
|
|
||||||
0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6E, 0xF3, 0x7D, 0x21, 0x69, 0xFC, 0xA0, 0x07, 0xE2, 0x21,
|
|
||||||
0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0xA9,
|
|
||||||
0xFD, 0x41, 0x67, 0xFF, 0x5F, 0x41, 0x6B, 0xF3, 0x5D, 0x42, 0x63, 0x6D, 0xFF, 0xFC, 0xFF, 0x62,
|
|
||||||
0x41, 0x74, 0xFA, 0x90, 0x21, 0x63, 0xFC, 0x42, 0x6F, 0x75, 0xFF, 0x81, 0xFF, 0xFD, 0x41, 0x65,
|
|
||||||
0xF3, 0x44, 0x21, 0x6C, 0xFC, 0x27, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x72, 0x79, 0xBD, 0xC4, 0xD9,
|
|
||||||
0xDC, 0xE4, 0xF2, 0xFD, 0x4D, 0x65, 0x75, 0x70, 0x6C, 0x61, 0xC3, 0x63, 0x68, 0x69, 0x6F, 0xC5,
|
|
||||||
0x74, 0x79, 0xFE, 0xCB, 0xFF, 0x04, 0xFF, 0x40, 0xFF, 0x5F, 0xF3, 0x11, 0xF4, 0x54, 0xFF, 0x7F,
|
|
||||||
0xFF, 0x8C, 0xF3, 0x11, 0xF3, 0x11, 0xF7, 0x13, 0xFF, 0xF1, 0xF3, 0x11, 0x41, 0x69, 0xF3, 0x97,
|
|
||||||
0x21, 0x6E, 0xFC, 0x21, 0x6F, 0xFD, 0x22, 0x6D, 0x73, 0xFD, 0xF6, 0x21, 0x6F, 0xFB, 0x21, 0x6E,
|
|
||||||
0xFD, 0x41, 0x75, 0xED, 0x66, 0x41, 0x73, 0xEC, 0x54, 0x21, 0x64, 0xFC, 0x21, 0x75, 0xFD, 0x41,
|
|
||||||
0x6F, 0xF6, 0xA4, 0x42, 0x73, 0x70, 0xEA, 0xC3, 0xFF, 0xFC, 0x21, 0x69, 0xF9, 0x43, 0x6D, 0x62,
|
|
||||||
0x6E, 0xF3, 0x6F, 0xFF, 0xEF, 0xFF, 0xFD, 0x41, 0x67, 0xF3, 0x5C, 0x21, 0x6E, 0xFC, 0x21, 0x6F,
|
|
||||||
0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x65, 0xFA, 0x82, 0x21, 0x74, 0xFC, 0x41, 0x6E, 0xFA, 0xEA, 0x21,
|
|
||||||
0x6F, 0xFC, 0x42, 0x73, 0x74, 0xF7, 0x88, 0xF7, 0x88, 0x41, 0x6F, 0xF7, 0x81, 0x21, 0x72, 0xFC,
|
|
||||||
0x21, 0xA9, 0xFD, 0x41, 0x6D, 0xF7, 0x77, 0x41, 0x75, 0xF7, 0x73, 0x42, 0x64, 0x74, 0xF7, 0x6F,
|
|
||||||
0xFF, 0xFC, 0x41, 0x6E, 0xF7, 0x68, 0x21, 0x6F, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x74, 0xFD, 0x21,
|
|
||||||
0x63, 0xFD, 0x22, 0x61, 0x69, 0xE9, 0xFD, 0x25, 0x61, 0xC3, 0x69, 0x6F, 0x72, 0xCB, 0xD9, 0xDC,
|
|
||||||
0xDC, 0xFB, 0x21, 0x74, 0xF5, 0x41, 0x61, 0xE9, 0x22, 0x21, 0x79, 0xFC, 0x4B, 0x67, 0x70, 0x6D,
|
|
||||||
0x72, 0x62, 0x63, 0x64, 0xC3, 0x69, 0x73, 0x78, 0xFF, 0x72, 0xFF, 0x75, 0xFF, 0x91, 0xF3, 0x5D,
|
|
||||||
0xFF, 0xA5, 0xFF, 0xAC, 0xFD, 0x10, 0xF2, 0x46, 0xFF, 0xB3, 0xFF, 0xF6, 0xFF, 0xFD, 0x41, 0x6E,
|
|
||||||
0xE8, 0xBD, 0xA1, 0x00, 0xE1, 0x67, 0xFC, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x72, 0xFF, 0xFB,
|
|
||||||
0xF3, 0x86, 0xF2, 0x1E, 0xF2, 0x1E, 0xF2, 0x1E, 0xF2, 0x3B, 0xA0, 0x01, 0x71, 0x21, 0xA9, 0xFD,
|
|
||||||
0x21, 0xC3, 0xFD, 0x41, 0x74, 0xE8, 0x44, 0x21, 0x70, 0xFC, 0x22, 0x69, 0x6F, 0xF6, 0xFD, 0xA1,
|
|
||||||
0x00, 0xE1, 0x6D, 0xFB, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF1, 0xF1, 0xFF, 0xFB,
|
|
||||||
0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0x41, 0xA9, 0xE9, 0x74, 0xC7, 0x06,
|
|
||||||
0x02, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73, 0x75, 0xF2, 0xCD, 0xF2, 0xCD, 0xFF, 0xFC, 0xF2, 0xCD,
|
|
||||||
0xF2, 0xCD, 0xF2, 0xCD, 0xF2, 0xCD, 0x21, 0x72, 0xE8, 0x47, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73,
|
|
||||||
0x75, 0xE9, 0xBD, 0xE9, 0xBD, 0xED, 0x93, 0xE9, 0xBD, 0xE9, 0xBD, 0xE9, 0xBD, 0xE9, 0xBD, 0x22,
|
|
||||||
0x65, 0x6F, 0xE7, 0xEA, 0xA1, 0x00, 0xE1, 0x70, 0xFB, 0x47, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75,
|
|
||||||
0x79, 0xF1, 0x9C, 0xFF, 0xAB, 0xF6, 0x71, 0xF4, 0xCA, 0xF1, 0x9C, 0xFA, 0x8F, 0xFF, 0xFB, 0x41,
|
|
||||||
0x76, 0xF3, 0xC0, 0x41, 0x76, 0xE8, 0x54, 0x41, 0x78, 0xE8, 0x50, 0x22, 0x6F, 0x61, 0xF8, 0xFC,
|
|
||||||
0x21, 0x69, 0xFB, 0x41, 0x72, 0xF2, 0x20, 0x21, 0x74, 0xFC, 0x45, 0x63, 0x65, 0x76, 0x6E, 0x73,
|
|
||||||
0xF2, 0x5E, 0xFF, 0xE5, 0xF2, 0x5E, 0xFF, 0xF6, 0xFF, 0xFD, 0x42, 0x6E, 0x73, 0xE9, 0xBA, 0xE9,
|
|
||||||
0xBA, 0x21, 0x69, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xC2, 0x00, 0xE1,
|
|
||||||
0x63, 0x6E, 0xF3, 0x82, 0xFF, 0xFD, 0xC2, 0x00, 0xE1, 0x6C, 0x64, 0xF4, 0x69, 0xF9, 0xE8, 0x41,
|
|
||||||
0x74, 0xF7, 0x1B, 0x21, 0x6F, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0x69, 0xFD, 0x42, 0x72, 0x2E, 0xFF,
|
|
||||||
0xFD, 0xF2, 0x88, 0x42, 0x69, 0x74, 0xEF, 0x79, 0xFF, 0xF9, 0xC3, 0x00, 0xE1, 0x6E, 0x2E, 0x73,
|
|
||||||
0xFF, 0xF9, 0xF2, 0x74, 0xF2, 0x77, 0x41, 0x69, 0xE7, 0x51, 0x21, 0x6B, 0xFC, 0x21, 0x73, 0xFD,
|
|
||||||
0x21, 0x6F, 0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB,
|
|
||||||
0xF0, 0xFD, 0xFF, 0xFB, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0, 0xFD, 0x41, 0x6D,
|
|
||||||
0xE9, 0xDD, 0x21, 0x61, 0xFC, 0x21, 0x74, 0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x48, 0x61, 0x69,
|
|
||||||
0x65, 0xC3, 0x6F, 0x72, 0x75, 0x79, 0xFF, 0x90, 0xFF, 0x99, 0xFF, 0xBD, 0xFF, 0xDB, 0xFF, 0xFB,
|
|
||||||
0xF2, 0x50, 0xF0, 0xD8, 0xF0, 0xD8, 0xA0, 0x01, 0xD1, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x42,
|
|
||||||
0x69, 0x75, 0xFF, 0xFD, 0xF0, 0xF8, 0x41, 0x72, 0xF6, 0xE9, 0xA1, 0x00, 0xE1, 0x77, 0xFC, 0x48,
|
|
||||||
0xA2, 0xA0, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6,
|
|
||||||
0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0x41, 0x2E, 0xE6, 0x8A, 0x21, 0x74, 0xFC, 0x21,
|
|
||||||
0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x4A, 0x69, 0x6C, 0x61, 0xC3, 0x65, 0x6F, 0x73, 0x75, 0x79, 0x6D,
|
|
||||||
0xF3, 0xAE, 0xFF, 0xCA, 0xFF, 0xD5, 0xFF, 0xDA, 0xF1, 0xE8, 0xF0, 0x80, 0xF8, 0x95, 0xF0, 0x80,
|
|
||||||
0xF0, 0x80, 0xFF, 0xFD, 0x41, 0x6C, 0xF3, 0x8B, 0x42, 0x69, 0x65, 0xFF, 0xFC, 0xF9, 0xD3, 0xC1,
|
|
||||||
0x00, 0xE2, 0x2E, 0xF1, 0xAF, 0x49, 0x61, 0xC3, 0x65, 0x68, 0x69, 0x6F, 0x72, 0x75, 0x79, 0xF0,
|
|
||||||
0x50, 0xF1, 0x93, 0xF1, 0xB8, 0xFF, 0xFA, 0xF0, 0x50, 0xF0, 0x50, 0xF0, 0x6D, 0xF0, 0x50, 0xF0,
|
|
||||||
0x50, 0x42, 0x61, 0x65, 0xF0, 0x76, 0xF1, 0xA5, 0xA1, 0x00, 0xE1, 0x75, 0xF9, 0x41, 0x69, 0xFA,
|
|
||||||
0x32, 0x21, 0x72, 0xFC, 0xA1, 0x00, 0xE1, 0x74, 0xFD, 0xA0, 0x01, 0xF2, 0x21, 0x2E, 0xFD, 0x22,
|
|
||||||
0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x74, 0xFB, 0x21, 0x61, 0xFD, 0x4A, 0x75, 0x61, 0xC3, 0x65, 0x69,
|
|
||||||
0x6F, 0xC5, 0x73, 0x78, 0x79, 0xFF, 0xEA, 0xF0, 0x0B, 0xF1, 0x4E, 0xF1, 0x73, 0xF0, 0x0B, 0xF0,
|
|
||||||
0x0B, 0xF4, 0x0D, 0xFF, 0xFD, 0xF8, 0x58, 0xF0, 0x0B, 0x41, 0x68, 0xF8, 0x39, 0x21, 0x74, 0xFC,
|
|
||||||
0x42, 0x73, 0x6C, 0xFF, 0xFD, 0xF8, 0x38, 0x41, 0x6F, 0xFD, 0x5C, 0x21, 0x74, 0xFC, 0x22, 0x61,
|
|
||||||
0x73, 0xF2, 0xFD, 0x42, 0xA9, 0xA8, 0xEF, 0xD2, 0xEF, 0xD2, 0x47, 0x61, 0x65, 0xC3, 0x69, 0x6F,
|
|
||||||
0x75, 0x79, 0xEF, 0xCB, 0xF1, 0x33, 0xFF, 0xF9, 0xEF, 0xCB, 0xEF, 0xCB, 0xEF, 0xCB, 0xEF, 0xCB,
|
|
||||||
0x5D, 0x27, 0x2E, 0x61, 0x62, 0xC3, 0x63, 0x6A, 0x6D, 0x72, 0x70, 0x69, 0x65, 0x64, 0x74, 0x66,
|
|
||||||
0x67, 0x73, 0x6F, 0x77, 0x68, 0x75, 0x76, 0x6C, 0x78, 0x6B, 0x71, 0x6E, 0x79, 0x7A, 0xE7, 0xD0,
|
|
||||||
0xEF, 0x48, 0xF0, 0xCD, 0xF1, 0x53, 0xF2, 0x28, 0xF3, 0xD1, 0xF3, 0xFD, 0xF4, 0xAD, 0xF5, 0x6F,
|
|
||||||
0xF7, 0x2F, 0xF8, 0x34, 0xF8, 0x98, 0xF9, 0x32, 0xFA, 0x80, 0xFA, 0xE4, 0xFB, 0x3C, 0xFC, 0xA4,
|
|
||||||
0xFD, 0x6C, 0xFD, 0x97, 0xFE, 0x19, 0xFE, 0x4A, 0xFE, 0xDD, 0xFF, 0x35, 0xFF, 0x58, 0xFF, 0x65,
|
|
||||||
0xFF, 0x88, 0xFF, 0xAA, 0xFF, 0xDE, 0xFF, 0xEA,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SerializedHyphenationPatterns fr_patterns = {
|
|
||||||
0x1AF0u,
|
|
||||||
fr_trie_data,
|
|
||||||
sizeof(fr_trie_data),
|
|
||||||
};
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t it_trie_data[] = {
|
|
||||||
0x17, 0x0C, 0x33, 0x35, 0x0C, 0x29, 0x22, 0x0D, 0x3E, 0x0B, 0x47, 0x20, 0x0D, 0x16, 0x0B, 0x34,
|
|
||||||
0x0D, 0x21, 0x0C, 0x3D, 0x1F, 0x0C, 0x2A, 0x17, 0x2A, 0x0B, 0x02, 0x0C, 0x01, 0x02, 0x16, 0x02,
|
|
||||||
0x0D, 0x0C, 0x0C, 0x0D, 0x03, 0x0C, 0x01, 0x0C, 0x0E, 0x0D, 0x04, 0x02, 0x0B, 0xA0, 0x00, 0x42,
|
|
||||||
0x21, 0x6E, 0xFD, 0xA0, 0x00, 0x72, 0x21, 0x6E, 0xFD, 0xA1, 0x00, 0x61, 0x6D, 0xFD, 0x21, 0x69,
|
|
||||||
0xFB, 0x21, 0x74, 0xFD, 0x22, 0x70, 0x6E, 0xEC, 0xFD, 0xA0, 0x00, 0x91, 0x21, 0x6F, 0xFD, 0x21,
|
|
||||||
0x69, 0xFD, 0xA0, 0x00, 0xA2, 0x21, 0x73, 0xFD, 0x21, 0x70, 0xFD, 0xA0, 0x00, 0xC2, 0x21, 0x6D,
|
|
||||||
0xFD, 0x21, 0x75, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x72, 0xFD, 0xA0, 0x00, 0xE1, 0x21, 0x6F, 0xFD,
|
|
||||||
0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0xA3, 0x01, 0x11, 0x61, 0x69, 0x6F, 0xDF,
|
|
||||||
0xEE, 0xFD, 0xA0, 0x00, 0xF2, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x63,
|
|
||||||
0xFD, 0x21, 0x73, 0xFD, 0xA1, 0x01, 0x11, 0x69, 0xFD, 0xA0, 0x01, 0x12, 0x21, 0x75, 0xFD, 0x21,
|
|
||||||
0x65, 0xFD, 0x21, 0x78, 0xFD, 0xA0, 0x01, 0x32, 0x21, 0x6B, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x00,
|
|
||||||
0x71, 0x21, 0x65, 0xFD, 0x22, 0x61, 0x65, 0xF7, 0xFD, 0x21, 0x72, 0xFB, 0xA0, 0x01, 0x52, 0x21,
|
|
||||||
0x61, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x70, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x71, 0x21, 0x6F,
|
|
||||||
0xFD, 0x21, 0x63, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xFD, 0xA0, 0x00, 0x61, 0x21, 0x6F, 0xFD,
|
|
||||||
0x21, 0x74, 0xFD, 0x41, 0x70, 0xFF, 0x50, 0x21, 0x6F, 0xFC, 0x21, 0x74, 0xFD, 0x22, 0x70, 0x72,
|
|
||||||
0xF3, 0xFD, 0x21, 0x61, 0xE8, 0x21, 0x72, 0xFD, 0xA0, 0x00, 0xF1, 0x22, 0x6C, 0x72, 0xFD, 0xFD,
|
|
||||||
0x21, 0x69, 0xE3, 0x21, 0x6C, 0xFD, 0x41, 0x65, 0xFF, 0x43, 0xA0, 0x01, 0x11, 0x25, 0x61, 0x68,
|
|
||||||
0x6F, 0x72, 0x73, 0xE8, 0xEE, 0xF6, 0xF9, 0xFD, 0xA0, 0x01, 0x82, 0x21, 0x72, 0xFD, 0x21, 0x63,
|
|
||||||
0xFD, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x65, 0xFD, 0xA0, 0x01, 0xA2, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x72, 0xFD, 0x21, 0x61, 0xFD, 0x41, 0x75, 0xFF, 0x4C, 0x42, 0x6C, 0x72, 0xFF, 0xFC, 0xFF,
|
|
||||||
0x48, 0x21, 0x62, 0xF9, 0x22, 0x68, 0x75, 0xEF, 0xFD, 0x47, 0x63, 0x64, 0x6C, 0x6E, 0x70, 0x72,
|
|
||||||
0x74, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x5C, 0x21,
|
|
||||||
0x73, 0xEA, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0xA1, 0x01, 0x11, 0x72, 0xFD, 0x41, 0x6E, 0xFF,
|
|
||||||
0x15, 0x21, 0x67, 0xFC, 0xA0, 0x01, 0xC2, 0x21, 0x74, 0xFD, 0x21, 0x6C, 0xFD, 0x22, 0x61, 0x65,
|
|
||||||
0xF4, 0xFD, 0x52, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x6C, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74,
|
|
||||||
0x77, 0x68, 0x6A, 0x6B, 0x7A, 0xFE, 0xC2, 0xFE, 0xCD, 0xFE, 0xF7, 0xFF, 0x12, 0xFF, 0x20, 0xFF,
|
|
||||||
0x37, 0xFF, 0x46, 0xFF, 0x55, 0xFF, 0x6B, 0xFF, 0x8B, 0xFF, 0xA5, 0xFF, 0xC2, 0xFF, 0xE6, 0xFF,
|
|
||||||
0xFB, 0xFF, 0x88, 0xFF, 0x88, 0xFF, 0x88, 0xFF, 0x88, 0xA0, 0x01, 0xE2, 0xA0, 0x00, 0xD1, 0x24,
|
|
||||||
0x61, 0x65, 0x6F, 0x75, 0xFD, 0xFD, 0xFD, 0xFD, 0x21, 0x6F, 0xF4, 0x21, 0x61, 0xF1, 0xA0, 0x01,
|
|
||||||
0xE1, 0x21, 0x2E, 0xFD, 0x24, 0x69, 0x75, 0x79, 0x74, 0xEB, 0xF4, 0xF7, 0xFD, 0x21, 0x75, 0xDF,
|
|
||||||
0xA0, 0x00, 0x51, 0x22, 0x69, 0x77, 0xFA, 0xFD, 0x21, 0x69, 0xD7, 0xAE, 0x02, 0x01, 0x62, 0x63,
|
|
||||||
0x64, 0x66, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x76, 0x6C, 0x72, 0x2E, 0x27, 0xE3, 0xE3, 0xE3, 0xE3,
|
|
||||||
0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xF5, 0xF5, 0xE3, 0xE3, 0x22, 0x2E, 0x27, 0xC4, 0xC7, 0xC6,
|
|
||||||
0x00, 0x51, 0x68, 0x2E, 0x27, 0x62, 0x72, 0x6E, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xFB, 0xFF, 0xBF,
|
|
||||||
0xFE, 0xFB, 0xFF, 0xBF, 0xD0, 0x02, 0x01, 0x62, 0x63, 0x64, 0x66, 0x6B, 0x6D, 0x6E, 0x71, 0x73,
|
|
||||||
0x74, 0x7A, 0x68, 0x6C, 0x72, 0x2E, 0x27, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF,
|
|
||||||
0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xEB, 0xFF,
|
|
||||||
0xBC, 0xFF, 0xBC, 0xFF, 0xAA, 0xFF, 0xAA, 0xCE, 0x02, 0x01, 0x62, 0x64, 0x67, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x2E, 0x27, 0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x77,
|
|
||||||
0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x89, 0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x77, 0xFF, 0x77,
|
|
||||||
0xFF, 0x77, 0xFF, 0x77, 0xCA, 0x02, 0x01, 0x62, 0x67, 0x66, 0x6E, 0x6C, 0x72, 0x73, 0x74, 0x2E,
|
|
||||||
0x27, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x5C, 0xFF, 0x5C, 0xFF, 0x4A, 0xFF,
|
|
||||||
0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xA0, 0x02, 0x12, 0xA1, 0x00, 0x51, 0x74, 0xFD, 0xD1, 0x02, 0x01,
|
|
||||||
0x62, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x7A, 0x2E,
|
|
||||||
0x27, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0xFB, 0xFF, 0x33, 0xFF, 0x21, 0xFF,
|
|
||||||
0x33, 0xFF, 0x21, 0xFF, 0x33, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0x21, 0xFF, 0x21, 0xFF,
|
|
||||||
0x21, 0xFF, 0x21, 0x41, 0x70, 0xFD, 0x4D, 0xCB, 0x02, 0x01, 0x62, 0x64, 0x68, 0x69, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x72, 0x76, 0x2E, 0x27, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFF, 0xFC, 0xFE, 0xF9, 0xFE,
|
|
||||||
0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xFE, 0xE7, 0xC2, 0x02, 0x01, 0x2E, 0x27,
|
|
||||||
0xFE, 0xC3, 0xFE, 0xC3, 0xCB, 0x02, 0x01, 0x67, 0x66, 0x68, 0x6B, 0x6C, 0x6D, 0x72, 0x73, 0x74,
|
|
||||||
0x2E, 0x27, 0xFE, 0xBA, 0xFE, 0xBA, 0xFE, 0xCC, 0xFE, 0xBA, 0xFE, 0xCC, 0xFE, 0xBA, 0xFE, 0xCC,
|
|
||||||
0xFE, 0xBA, 0xFE, 0xBA, 0xFE, 0xBA, 0xFE, 0xBA, 0xA0, 0x02, 0x33, 0x42, 0x2E, 0x27, 0xFE, 0x93,
|
|
||||||
0xFE, 0x93, 0xD5, 0x02, 0x01, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x7A, 0x2E, 0x27, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C,
|
|
||||||
0xFF, 0xF6, 0xFE, 0x8C, 0xFE, 0x9E, 0xFE, 0x9E, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C,
|
|
||||||
0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C,
|
|
||||||
0xFE, 0x8C, 0xFF, 0xF9, 0xCF, 0x02, 0x01, 0x62, 0x63, 0x66, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72,
|
|
||||||
0x73, 0x74, 0x76, 0x77, 0x2E, 0x27, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A,
|
|
||||||
0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A, 0xFE, 0x4A,
|
|
||||||
0xFE, 0x4A, 0xFE, 0x4A, 0xA0, 0x02, 0x62, 0xA1, 0x01, 0xE1, 0x6E, 0xFD, 0x21, 0x72, 0xF8, 0x21,
|
|
||||||
0x65, 0xFD, 0xA1, 0x01, 0xE1, 0x66, 0xFD, 0x41, 0x74, 0xFE, 0x07, 0x21, 0x69, 0xFC, 0x21, 0x65,
|
|
||||||
0xFD, 0xD3, 0x02, 0x01, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72,
|
|
||||||
0x73, 0x74, 0x76, 0x7A, 0x68, 0x2E, 0x27, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFF,
|
|
||||||
0xE6, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFF,
|
|
||||||
0xF1, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0, 0x02, 0x82,
|
|
||||||
0xA1, 0x01, 0xE1, 0x65, 0xFD, 0x21, 0x63, 0xF8, 0xA1, 0x01, 0xE1, 0x69, 0xFD, 0xCB, 0x02, 0x01,
|
|
||||||
0x64, 0x68, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x7A, 0x2E, 0x27, 0xFD, 0xB1, 0xFD, 0xC3, 0xFD,
|
|
||||||
0xC3, 0xFF, 0xF3, 0xFD, 0xB1, 0xFD, 0xC3, 0xFF, 0xFB, 0xFD, 0xB1, 0xFD, 0xB1, 0xFD, 0xB1, 0xFD,
|
|
||||||
0xB1, 0xC3, 0x02, 0x01, 0x71, 0x2E, 0x27, 0xFD, 0x8D, 0xFD, 0x8D, 0xFD, 0x8D, 0xA0, 0x02, 0x53,
|
|
||||||
0xA1, 0x01, 0xE1, 0x73, 0xFD, 0xD5, 0x02, 0x01, 0x62, 0x63, 0x64, 0x66, 0x68, 0x67, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x78, 0x77, 0x7A, 0x2E, 0x27, 0xFD, 0x79, 0xFD,
|
|
||||||
0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x8B, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD,
|
|
||||||
0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFF, 0xFB, 0xFD, 0x79, 0xFD, 0x79, 0xFD,
|
|
||||||
0x79, 0xFD, 0x79, 0xFD, 0x79, 0xFD, 0x79, 0x43, 0x6D, 0x2E, 0x27, 0xFD, 0x37, 0xFD, 0x37, 0xFD,
|
|
||||||
0x37, 0xA0, 0x02, 0xC2, 0xA1, 0x02, 0x32, 0x6D, 0xFD, 0x41, 0x6E, 0xFE, 0x8F, 0x4B, 0x62, 0x63,
|
|
||||||
0x64, 0x66, 0x67, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x76, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD,
|
|
||||||
0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xFD, 0x21, 0xA0,
|
|
||||||
0x02, 0xE1, 0x22, 0x2E, 0x27, 0xFD, 0xFD, 0xC7, 0x02, 0xA2, 0x68, 0x73, 0x70, 0x74, 0x7A, 0x2E,
|
|
||||||
0x27, 0xFF, 0xC0, 0xFF, 0xCD, 0xFF, 0xD2, 0xFF, 0xD6, 0xFC, 0xF7, 0xFF, 0xF8, 0xFF, 0xFB, 0xC1,
|
|
||||||
0x00, 0x51, 0x2E, 0xFC, 0xDF, 0x41, 0x68, 0xFF, 0x18, 0xA1, 0x00, 0x51, 0x63, 0xFC, 0xC1, 0x01,
|
|
||||||
0xE1, 0x73, 0xFE, 0xB6, 0xC2, 0x00, 0x51, 0x6B, 0x73, 0xFC, 0xCA, 0xFC, 0x06, 0xD2, 0x02, 0x01,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x7A,
|
|
||||||
0x2E, 0x27, 0xFC, 0xC1, 0xFC, 0xC1, 0xFC, 0xC1, 0xFC, 0xC1, 0xFC, 0xC1, 0xFF, 0xE2, 0xFC, 0xD3,
|
|
||||||
0xFC, 0xC1, 0xFC, 0xC1, 0xFC, 0xC1, 0xFC, 0xD3, 0xFF, 0xEC, 0xFF, 0xF1, 0xFC, 0xC1, 0xFC, 0xC1,
|
|
||||||
0xFF, 0xF7, 0xFC, 0xC1, 0xFE, 0x2E, 0xC6, 0x02, 0x01, 0x63, 0x6C, 0x72, 0x76, 0x2E, 0x27, 0xFC,
|
|
||||||
0x88, 0xFC, 0x9A, 0xFC, 0x9A, 0xFC, 0x88, 0xFC, 0x88, 0xFD, 0xF5, 0x41, 0x72, 0xFB, 0xAF, 0xA0,
|
|
||||||
0x02, 0xF2, 0xC5, 0x02, 0x01, 0x68, 0x61, 0x79, 0x2E, 0x27, 0xFC, 0x7E, 0xFF, 0xF9, 0xFF, 0xFD,
|
|
||||||
0xFC, 0x6C, 0xFC, 0x6C, 0xCA, 0x02, 0x01, 0x62, 0x63, 0x66, 0x68, 0x6D, 0x70, 0x74, 0x77, 0x2E,
|
|
||||||
0x27, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC,
|
|
||||||
0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0x42, 0x6F, 0x69, 0xFC, 0x48, 0xFC, 0x27, 0xCB, 0x02, 0x01, 0x62,
|
|
||||||
0x64, 0x6C, 0x6E, 0x70, 0x74, 0x73, 0x76, 0x7A, 0x2E, 0x27, 0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32,
|
|
||||||
0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32, 0xFC, 0x32, 0xFD, 0x9F,
|
|
||||||
0x5A, 0x2E, 0x27, 0x61, 0x65, 0x6F, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xFB, 0xC2, 0xFB, 0xF9, 0xFC,
|
|
||||||
0x14, 0xFC, 0x23, 0xFC, 0x28, 0xFC, 0x2B, 0xFC, 0x64, 0xFC, 0x97, 0xFC, 0xC4, 0xFC, 0xED, 0xFD,
|
|
||||||
0x27, 0xFD, 0x4B, 0xFD, 0x54, 0xFD, 0x82, 0xFD, 0xC4, 0xFE, 0x11, 0xFE, 0x5D, 0xFE, 0x81, 0xFE,
|
|
||||||
0x95, 0xFF, 0x17, 0xFF, 0x4D, 0xFF, 0x86, 0xFF, 0xA2, 0xFF, 0xB4, 0xFF, 0xD5, 0xFF, 0xDC,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SerializedHyphenationPatterns it_patterns = {
|
|
||||||
0x5C0u,
|
|
||||||
it_trie_data,
|
|
||||||
sizeof(it_trie_data),
|
|
||||||
};
|
|
||||||
@@ -1,987 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t pl_trie_data[] = {
|
|
||||||
0x3A, 0x26, 0x30, 0x48, 0x17, 0x3E, 0x0F, 0x52, 0x67, 0x0C, 0x44, 0x16, 0x0C, 0x0D, 0x16, 0x0D,
|
|
||||||
0x22, 0x23, 0x21, 0x35, 0x16, 0x35, 0x0C, 0x21, 0x0C, 0x5C, 0x0D, 0x1C, 0x20, 0x0D, 0x21, 0x0E,
|
|
||||||
0x34, 0x34, 0x0B, 0x3F, 0x16, 0x3F, 0x0C, 0x49, 0x0C, 0x53, 0x16, 0x70, 0x0D, 0x49, 0x16, 0x3E,
|
|
||||||
0x0D, 0x3F, 0x0E, 0x21, 0x0E, 0x0D, 0x21, 0x16, 0x22, 0x17, 0x22, 0x0D, 0x23, 0x0E, 0x0C, 0x34,
|
|
||||||
0x0D, 0x35, 0x0E, 0x7B, 0x16, 0x84, 0x0D, 0x5C, 0x17, 0x16, 0x0D, 0x0C, 0x0F, 0x16, 0x16, 0x0B,
|
|
||||||
0x0C, 0x0B, 0x2C, 0x17, 0x0E, 0x18, 0x0F, 0x0C, 0x2B, 0x0C, 0x52, 0x0D, 0x66, 0x2A, 0x0C, 0x0D,
|
|
||||||
0x2B, 0x18, 0x2B, 0x18, 0x0D, 0x2C, 0x19, 0x0C, 0x53, 0x0C, 0x2A, 0x17, 0x20, 0x0C, 0x21, 0x20,
|
|
||||||
0x21, 0x0E, 0x0C, 0x22, 0x0D, 0x0C, 0x0C, 0x22, 0x0F, 0x5F, 0x2B, 0x16, 0x2D, 0x16, 0x2D, 0x0C,
|
|
||||||
0x36, 0x0D, 0x2B, 0x0C, 0x16, 0x2B, 0x0C, 0x0C, 0x2B, 0x16, 0x0D, 0x41, 0x16, 0x34, 0x0C, 0x2C,
|
|
||||||
0x0D, 0x2C, 0x21, 0x3E, 0x0C, 0x3F, 0x20, 0x40, 0x0D, 0x0C, 0x4B, 0x52, 0x0C, 0x53, 0x20, 0x53,
|
|
||||||
0x0E, 0x53, 0x0E, 0x16, 0x53, 0x0C, 0x0C, 0x0D, 0x54, 0x0F, 0x54, 0x0F, 0x16, 0x54, 0x0F, 0x0C,
|
|
||||||
0x5D, 0x16, 0x5D, 0x0C, 0x7A, 0x17, 0x0E, 0x16, 0x17, 0x10, 0x17, 0x0C, 0x18, 0x11, 0x21, 0x0C,
|
|
||||||
0x0C, 0x2B, 0x0D, 0x23, 0x16, 0x23, 0x0C, 0x2A, 0x0D, 0x20, 0x16, 0x22, 0x0F, 0x0C, 0x2A, 0x16,
|
|
||||||
0x34, 0x0B, 0x20, 0x34, 0x17, 0x21, 0x0C, 0x0C, 0x0D, 0x22, 0x0D, 0x0E, 0x0C, 0x0D, 0x22, 0x0F,
|
|
||||||
0x16, 0x22, 0x17, 0x0C, 0x2B, 0x34, 0x0F, 0x35, 0x20, 0x2C, 0x0B, 0x2C, 0x0C, 0x35, 0x0E, 0x0D,
|
|
||||||
0x35, 0x0E, 0x16, 0x35, 0x0C, 0x0C, 0x0D, 0x36, 0x0F, 0x36, 0x0F, 0x16, 0x36, 0x0F, 0x0E, 0x48,
|
|
||||||
0x0D, 0x49, 0x0E, 0x3F, 0x2B, 0x0C, 0x21, 0x0E, 0x0D, 0x16, 0x21, 0x0E, 0x0D, 0x0C, 0x22, 0x0F,
|
|
||||||
0x0E, 0x0D, 0x36, 0x0F, 0x0C, 0x41, 0x0C, 0x48, 0x0B, 0x66, 0x0D, 0x2A, 0x16, 0x17, 0x20, 0x11,
|
|
||||||
0x4A, 0x0F, 0x18, 0x0D, 0x0C, 0x18, 0x17, 0x22, 0x0D, 0x20, 0x48, 0x21, 0x3E, 0x17, 0x21, 0x0C,
|
|
||||||
0x0D, 0x68, 0x34, 0x2B, 0x2B, 0x0E, 0x22, 0x0D, 0x0C, 0x0B, 0x0C, 0x52, 0x17, 0x36, 0x21, 0x15,
|
|
||||||
0x01, 0x16, 0x02, 0x15, 0x02, 0x0B, 0x02, 0x1F, 0x03, 0x0C, 0x03, 0x0C, 0x16, 0x03, 0x0C, 0x0C,
|
|
||||||
0x04, 0x17, 0x04, 0x0D, 0x08, 0x16, 0x0D, 0x16, 0x16, 0x0D, 0x16, 0x0D, 0x3E, 0x0B, 0x20, 0x0B,
|
|
||||||
0x20, 0x0C, 0x0D, 0x20, 0x17, 0x29, 0x16, 0x11, 0x16, 0x0F, 0x20, 0x0B, 0x0C, 0x2A, 0x0B, 0x16,
|
|
||||||
0x15, 0x18, 0x0E, 0x0D, 0x20, 0x0F, 0x16, 0x11, 0x3F, 0x18, 0x16, 0x0D, 0x16, 0x16, 0x0D, 0x17,
|
|
||||||
0x0C, 0x20, 0x15, 0x17, 0x0C, 0x0D, 0x16, 0x0C, 0x0B, 0xA0, 0x00, 0x41, 0x21, 0x87, 0xFD, 0x25,
|
|
||||||
0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xA0, 0x00, 0x61, 0xA0, 0x00, 0x72,
|
|
||||||
0x21, 0x87, 0xFD, 0x21, 0xC4, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0xA1, 0x00, 0x61, 0x69,
|
|
||||||
0xFD, 0xB5, 0x00, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xDB, 0xDE, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9,
|
|
||||||
0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xFB, 0xE9, 0xE9, 0x21, 0x87,
|
|
||||||
0xD3, 0xD5, 0x00, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xFF, 0xAB, 0xFF, 0xAE, 0xFF, 0xB9, 0xFF,
|
|
||||||
0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF,
|
|
||||||
0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF, 0xB9, 0xFF,
|
|
||||||
0xB9, 0xFF, 0xB9, 0xA0, 0x00, 0xB1, 0xA1, 0x00, 0x92, 0x72, 0xFD, 0x21, 0x64, 0xFB, 0x21, 0xB3,
|
|
||||||
0xFD, 0xA1, 0x00, 0x61, 0xC3, 0xFD, 0xA0, 0x00, 0xC2, 0x21, 0x77, 0xFD, 0x21, 0x6F, 0xFD, 0x21,
|
|
||||||
0x82, 0xFD, 0x21, 0xC5, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x61, 0xFD, 0xA1, 0x00, 0x61, 0x69, 0xFD,
|
|
||||||
0xD5, 0x00, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xFF, 0x3C, 0xFF, 0x3F, 0xFF, 0x4A, 0xFF, 0x4A,
|
|
||||||
0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A,
|
|
||||||
0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0xE1, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0x4A, 0xFF, 0xFB, 0xFF, 0x4A,
|
|
||||||
0xFF, 0x4A, 0xA0, 0x00, 0xE1, 0x21, 0xBA, 0xFD, 0xA1, 0x00, 0x61, 0xC5, 0xFD, 0xD5, 0x00, 0x51,
|
|
||||||
0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x76, 0x77, 0x78, 0x7A, 0xFE, 0xEF, 0xFE, 0xF2, 0xFE, 0xFD, 0xFE, 0xFD, 0xFF, 0xFB, 0xFE,
|
|
||||||
0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE,
|
|
||||||
0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0xFE, 0xFD, 0x45,
|
|
||||||
0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x71, 0xFF, 0xBE, 0xFF, 0x02, 0xA0,
|
|
||||||
0x00, 0xF3, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x01, 0x51, 0x21, 0x74, 0xFD, 0xA1, 0x01,
|
|
||||||
0x41, 0x70, 0xFD, 0xA0, 0x01, 0x41, 0xA1, 0x01, 0x41, 0x75, 0xF2, 0xA2, 0x01, 0x41, 0x70, 0x72,
|
|
||||||
0xED, 0xED, 0xA6, 0x01, 0x22, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xEC, 0xF1, 0xF4, 0xF9, 0xF1,
|
|
||||||
0xF1, 0xA0, 0x00, 0x91, 0xA5, 0x01, 0x61, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xFD, 0xFD, 0xFD, 0xFD,
|
|
||||||
0xFD, 0x21, 0x6F, 0xF3, 0x21, 0x72, 0xFD, 0x25, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xEA, 0xEA, 0xEA,
|
|
||||||
0xEA, 0xEA, 0x21, 0x79, 0xF5, 0x21, 0x74, 0xFD, 0xA0, 0x01, 0x72, 0x21, 0x82, 0xFD, 0xA1, 0x01,
|
|
||||||
0x92, 0x7A, 0xFA, 0xA0, 0x01, 0x92, 0x29, 0xC5, 0x62, 0x6B, 0x6D, 0x61, 0x65, 0x69, 0x6F, 0x75,
|
|
||||||
0xF5, 0xF8, 0xFD, 0xFD, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0x21, 0x79, 0xED, 0x21, 0x63, 0xFD, 0xA0,
|
|
||||||
0x01, 0xB2, 0x21, 0x68, 0xD6, 0xA0, 0x01, 0xD2, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x61,
|
|
||||||
0xFD, 0x21, 0x72, 0xFD, 0x47, 0x63, 0x61, 0x65, 0x69, 0x6F, 0x74, 0x75, 0xFF, 0xEE, 0xFF, 0x9D,
|
|
||||||
0xFF, 0x9D, 0xFF, 0x9D, 0xFF, 0x9D, 0xFF, 0xFD, 0xFF, 0x9D, 0xA1, 0x01, 0xB2, 0x6F, 0xEA, 0x23,
|
|
||||||
0x67, 0x6B, 0x74, 0xD0, 0xD0, 0xFB, 0x46, 0x62, 0x64, 0x65, 0x6E, 0x72, 0x75, 0xFF, 0x4F, 0xFF,
|
|
||||||
0x6C, 0xFF, 0x8E, 0xFF, 0x9F, 0xFF, 0xC6, 0xFF, 0xF9, 0x41, 0x87, 0xFD, 0xE1, 0x45, 0x82, 0x84,
|
|
||||||
0x9B, 0xBA, 0xBC, 0xFD, 0xDD, 0xFD, 0xDD, 0xFD, 0xDD, 0xFD, 0xDD, 0xFD, 0xDD, 0xA0, 0x00, 0x51,
|
|
||||||
0xA0, 0x02, 0x22, 0x21, 0x6E, 0xFD, 0x21, 0x63, 0xFA, 0x21, 0x6B, 0xF7, 0x41, 0x68, 0xFF, 0x45,
|
|
||||||
0xA0, 0x02, 0x41, 0xA0, 0x02, 0x52, 0x21, 0x6A, 0xFD, 0xA1, 0x02, 0x41, 0x62, 0xFD, 0x41, 0x77,
|
|
||||||
0xFF, 0x33, 0xA8, 0x02, 0x02, 0x61, 0x65, 0x69, 0x63, 0x6D, 0x6F, 0x77, 0x7A, 0xE1, 0xE4, 0xE7,
|
|
||||||
0xEA, 0xEE, 0xF7, 0xEE, 0xFC, 0x21, 0x7A, 0xED, 0xC1, 0x00, 0x51, 0x7A, 0xFD, 0x92, 0xB6, 0x01,
|
|
||||||
0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70,
|
|
||||||
0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xAB, 0xAF, 0xBF, 0xBF, 0xBF, 0xF7, 0xBF, 0xBF, 0xBF,
|
|
||||||
0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xFA, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xA0, 0x02, 0x72,
|
|
||||||
0x21, 0x9B, 0xFD, 0xA0, 0x02, 0x92, 0x22, 0xC5, 0x6B, 0xFA, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x82,
|
|
||||||
0xFD, 0x21, 0xC5, 0xFD, 0x41, 0x7A, 0xFD, 0x35, 0xA1, 0x00, 0x51, 0x72, 0xFC, 0xA0, 0x02, 0xB2,
|
|
||||||
0x21, 0x77, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6B, 0xFD, 0xA0, 0x02, 0xD2, 0x21, 0x72, 0xFD, 0x21,
|
|
||||||
0x6B, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x82, 0xFD, 0x21, 0xC5, 0xFD, 0x22, 0x6E, 0x70, 0xEB, 0xFD,
|
|
||||||
0x21, 0x65, 0xFB, 0x21, 0x6B, 0xDA, 0x21, 0x6F, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x72, 0xFD, 0xA0,
|
|
||||||
0x02, 0xF2, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0xA1, 0x00, 0x72, 0x73, 0xFD, 0x21, 0x68, 0xFB,
|
|
||||||
0x21, 0x63, 0xFD, 0xA0, 0x03, 0x12, 0x21, 0x9B, 0xFD, 0x21, 0xC5, 0xFD, 0x22, 0x65, 0x6F, 0xF4,
|
|
||||||
0xFD, 0x21, 0x72, 0xFB, 0x21, 0x65, 0xFD, 0xA0, 0x03, 0x52, 0x22, 0x85, 0x99, 0xFD, 0xFD, 0xA4,
|
|
||||||
0x03, 0x32, 0xC4, 0x61, 0x65, 0x6F, 0xFB, 0xF8, 0xF8, 0xF8, 0x21, 0x72, 0xF5, 0x21, 0xB3, 0xFD,
|
|
||||||
0x21, 0xC3, 0xFD, 0xC4, 0x00, 0x51, 0x61, 0x6B, 0x74, 0x77, 0xFF, 0xB9, 0xFC, 0xC7, 0xFF, 0xE1,
|
|
||||||
0xFF, 0xFD, 0xD7, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xFE, 0xD7, 0xFE, 0xDB,
|
|
||||||
0xFF, 0x6F, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFF, 0x76, 0xFF, 0x9E,
|
|
||||||
0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB,
|
|
||||||
0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFE, 0xEB, 0xFF, 0xF1, 0x41, 0x72, 0xFF, 0x79, 0x42, 0x74,
|
|
||||||
0x77, 0xFF, 0xFC, 0xFF, 0x2F, 0x21, 0x6F, 0xF9, 0x21, 0x67, 0xFD, 0xA1, 0x00, 0x61, 0x75, 0xFD,
|
|
||||||
0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFF, 0xFB, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A,
|
|
||||||
0x41, 0x65, 0xFF, 0x16, 0x21, 0x6C, 0xFC, 0xA0, 0x03, 0x73, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD,
|
|
||||||
0x21, 0x75, 0xF7, 0xA2, 0x02, 0x41, 0x62, 0x77, 0xFA, 0xFD, 0x42, 0x6C, 0x72, 0xFE, 0x66, 0xFE,
|
|
||||||
0x66, 0xC4, 0x02, 0x02, 0x61, 0x65, 0x79, 0x6F, 0xFF, 0xF2, 0xFF, 0xF9, 0xFE, 0x5F, 0xFE, 0x6F,
|
|
||||||
0x21, 0x7A, 0xF1, 0xA0, 0x03, 0xA2, 0x21, 0x87, 0xFD, 0xA0, 0x03, 0xC2, 0xA1, 0x03, 0xA2, 0x6B,
|
|
||||||
0xFD, 0x24, 0x82, 0x9B, 0xBA, 0xBC, 0xFB, 0xF2, 0xF2, 0xF2, 0xC1, 0x02, 0x41, 0x7A, 0xFD, 0x87,
|
|
||||||
0xA1, 0x01, 0xB2, 0x72, 0xFA, 0xC2, 0x01, 0xB2, 0x68, 0x7A, 0xFE, 0x3B, 0xFE, 0x3B, 0x42, 0xBA,
|
|
||||||
0xBC, 0xFD, 0x73, 0xFD, 0x73, 0xC2, 0x01, 0xB2, 0xC5, 0x7A, 0xFF, 0xF9, 0xFE, 0x2B, 0x41, 0x7A,
|
|
||||||
0xFD, 0x63, 0xA1, 0x01, 0xB2, 0x72, 0xFC, 0xA0, 0x03, 0xE2, 0xA1, 0x01, 0xB2, 0x74, 0xFD, 0xA1,
|
|
||||||
0x01, 0xB2, 0x6E, 0xF8, 0xA0, 0x04, 0x03, 0xA1, 0x03, 0xE2, 0x6E, 0xFD, 0xA1, 0x01, 0xB2, 0x6B,
|
|
||||||
0xFB, 0xC2, 0x01, 0xB2, 0x63, 0x72, 0xFD, 0xFB, 0xFF, 0xDD, 0x41, 0xBC, 0xFD, 0x37, 0xC3, 0x01,
|
|
||||||
0xB2, 0x73, 0xC5, 0x7A, 0xFF, 0xD9, 0xFF, 0xFC, 0xFD, 0xF2, 0xC3, 0x01, 0xB2, 0x63, 0x6D, 0x7A,
|
|
||||||
0xFD, 0xE2, 0xFD, 0xE6, 0xFD, 0xE6, 0xC2, 0x01, 0xB2, 0x6B, 0x72, 0xFD, 0xDA, 0xFD, 0xDA, 0xA1,
|
|
||||||
0x01, 0xB2, 0x63, 0xB8, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0x72, 0xFF, 0x7D, 0xFF, 0x8C, 0xFF, 0x91, 0xFF, 0xA1,
|
|
||||||
0xFD, 0x5B, 0xFF, 0xAE, 0xFD, 0x5B, 0xFF, 0xB6, 0xFF, 0xBB, 0xFF, 0xC8, 0xFF, 0xCD, 0xFF, 0xDA,
|
|
||||||
0xFF, 0xE6, 0xFF, 0xF2, 0xFF, 0xFB, 0xFD, 0x5B, 0x41, 0x77, 0xFD, 0xFB, 0x21, 0x6F, 0xFC, 0x21,
|
|
||||||
0x67, 0xFD, 0xC2, 0x00, 0x51, 0x6F, 0x7A, 0xFF, 0xFD, 0xFB, 0x48, 0xA0, 0x04, 0x52, 0x22, 0x85,
|
|
||||||
0x99, 0xFD, 0xFD, 0xA4, 0x04, 0x32, 0xC4, 0x61, 0x65, 0x6F, 0xFB, 0xF8, 0xF8, 0xF8, 0x21, 0x6A,
|
|
||||||
0xF5, 0x21, 0xB3, 0xFD, 0xA1, 0x00, 0x51, 0xC3, 0xFD, 0x41, 0x61, 0xFD, 0x57, 0xA0, 0x01, 0x61,
|
|
||||||
0xC5, 0x02, 0x02, 0x65, 0x6F, 0x74, 0x79, 0x7A, 0xFD, 0x50, 0xFD, 0x50, 0xFF, 0xF9, 0xFD, 0x50,
|
|
||||||
0xFF, 0xFD, 0xC2, 0x02, 0x02, 0x65, 0x75, 0xFD, 0x3E, 0xFD, 0x4E, 0x22, 0x73, 0x7A, 0xE5, 0xF7,
|
|
||||||
0xA0, 0x04, 0x72, 0x21, 0x9B, 0xFD, 0x21, 0xC5, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x21,
|
|
||||||
0x63, 0xFD, 0x21, 0x99, 0xFD, 0x21, 0xC4, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x04, 0x92, 0x21, 0x74,
|
|
||||||
0xFD, 0x21, 0x65, 0xFD, 0xA1, 0x04, 0xB2, 0x73, 0xFD, 0x21, 0x87, 0xFB, 0x22, 0xC4, 0x63, 0xFD,
|
|
||||||
0xE0, 0x21, 0x99, 0xFB, 0x21, 0xC4, 0xFD, 0x21, 0x69, 0xFD, 0x22, 0x73, 0x77, 0xDE, 0xFD, 0x21,
|
|
||||||
0x65, 0xFB, 0xA1, 0x00, 0x51, 0x69, 0xFD, 0xD9, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62, 0x63, 0x64,
|
|
||||||
0x65, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77,
|
|
||||||
0x78, 0x79, 0x7A, 0xFC, 0xD2, 0xFE, 0x59, 0xFE, 0x6D, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFE,
|
|
||||||
0x99, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC,
|
|
||||||
0xE6, 0xFF, 0x2D, 0xFC, 0xE6, 0xFF, 0x6B, 0xFC, 0xE6, 0xFC, 0xE6, 0xFC, 0xE6, 0xFF, 0x8D, 0xFC,
|
|
||||||
0xE6, 0xFF, 0xB4, 0xFF, 0xFB, 0xA0, 0x05, 0x61, 0xA1, 0x00, 0xF3, 0x79, 0xFD, 0x21, 0x73, 0xFB,
|
|
||||||
0xA0, 0x04, 0xD5, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD, 0x21,
|
|
||||||
0x65, 0xFD, 0xA0, 0x05, 0x24, 0x21, 0x61, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x05,
|
|
||||||
0x22, 0x21, 0x61, 0xFD, 0x23, 0x65, 0x68, 0x74, 0xF7, 0xFD, 0xFA, 0x45, 0x61, 0x65, 0x69, 0x6F,
|
|
||||||
0x75, 0xFC, 0x75, 0xFC, 0x75, 0xFC, 0x75, 0xFC, 0x75, 0xFC, 0x75, 0x21, 0x6F, 0xF0, 0xA0, 0x01,
|
|
||||||
0xD1, 0x25, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x21, 0x6F, 0xF5, 0x21,
|
|
||||||
0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x65, 0xFD, 0x45, 0x6B, 0x6D, 0x73, 0x67,
|
|
||||||
0x6C, 0xFF, 0xA2, 0xFF, 0xB4, 0xFF, 0xC9, 0xFF, 0xE0, 0xFF, 0xFD, 0xD5, 0x01, 0xF1, 0xC4, 0xC5,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76,
|
|
||||||
0x77, 0x78, 0x7A, 0xFC, 0x0E, 0xFC, 0x12, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC,
|
|
||||||
0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC,
|
|
||||||
0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xFC, 0x22, 0xA0, 0x02, 0x02,
|
|
||||||
0x21, 0x6F, 0xFD, 0x21, 0x75, 0xFA, 0x41, 0x62, 0xFE, 0x56, 0xC2, 0x00, 0x51, 0x75, 0x7A, 0xFF,
|
|
||||||
0xFC, 0xF9, 0xA0, 0xD7, 0x01, 0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6A,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xFB, 0xB6, 0xFB,
|
|
||||||
0xBA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFF, 0xED, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB,
|
|
||||||
0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFF, 0xF0, 0xFB, 0xCA, 0xFF, 0xF7, 0xFB,
|
|
||||||
0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xFB, 0xCA, 0xA0, 0x00, 0x71, 0xC3, 0x04,
|
|
||||||
0x32, 0x6F, 0x61, 0x65, 0xFE, 0x0D, 0xFF, 0xFD, 0xFF, 0xFD, 0x21, 0x72, 0xF4, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x70, 0xFD, 0xD6, 0x01, 0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xFB, 0x56, 0xFB, 0x5A,
|
|
||||||
0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFF, 0xFD, 0xFB, 0x6A,
|
|
||||||
0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A,
|
|
||||||
0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xFB, 0x6A, 0xA0, 0x05, 0x72, 0x21, 0x63, 0xFD, 0xA0, 0x05,
|
|
||||||
0x93, 0xA0, 0x05, 0xC2, 0xA2, 0x05, 0x72, 0x6B, 0x77, 0xFA, 0xFD, 0x41, 0x6E, 0xFD, 0x0C, 0x21,
|
|
||||||
0x61, 0xFC, 0x21, 0x6C, 0xFD, 0xA4, 0x01, 0x22, 0x69, 0x6F, 0x75, 0x66, 0xE6, 0xEF, 0xE3, 0xFD,
|
|
||||||
0xA0, 0x01, 0x22, 0x21, 0x6C, 0xFD, 0x43, 0x6E, 0x73, 0x7A, 0xFF, 0xEF, 0xFF, 0xFD, 0xFE, 0xA5,
|
|
||||||
0x41, 0x82, 0xFB, 0x83, 0x21, 0xC5, 0xFC, 0x21, 0x64, 0xFD, 0xD6, 0x01, 0xF1, 0xC4, 0xC5, 0x61,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76,
|
|
||||||
0x77, 0x78, 0x7A, 0xFA, 0xCF, 0xFA, 0xD3, 0xFF, 0xFD, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA,
|
|
||||||
0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA,
|
|
||||||
0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xFA, 0xE3, 0xA0,
|
|
||||||
0x06, 0x01, 0xA1, 0x05, 0xE2, 0x6F, 0xFD, 0x21, 0x74, 0xFB, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFD,
|
|
||||||
0x21, 0x75, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x82, 0xFD, 0x03, 0xA0, 0x06, 0x93,
|
|
||||||
0x21, 0x69, 0xFD, 0x22, 0x65, 0x79, 0xFA, 0xFA, 0x21, 0x61, 0xF5, 0xA0, 0x06, 0x92, 0x21, 0x6D,
|
|
||||||
0xFD, 0x21, 0x63, 0xFA, 0x21, 0x74, 0xF7, 0x21, 0x67, 0xF4, 0xA7, 0x06, 0x42, 0x67, 0x73, 0x74,
|
|
||||||
0x64, 0x6B, 0x6C, 0x72, 0xE6, 0xE9, 0xEE, 0xF4, 0xF7, 0xFA, 0xFD, 0xA0, 0x06, 0x42, 0xA0, 0x06,
|
|
||||||
0x63, 0xA2, 0x00, 0x71, 0x6C, 0x77, 0xFD, 0xFD, 0xC5, 0x06, 0x13, 0x61, 0x65, 0x6F, 0x79, 0x75,
|
|
||||||
0xFF, 0xE2, 0xFF, 0xF3, 0xFF, 0xF9, 0xFF, 0xF3, 0xFE, 0xC3, 0x21, 0x72, 0xEE, 0x21, 0x74, 0xFD,
|
|
||||||
0x42, 0xC5, 0x6E, 0xFF, 0xA9, 0xFF, 0xFD, 0xA0, 0x06, 0xC2, 0x42, 0x74, 0x77, 0xFA, 0xC2, 0xFF,
|
|
||||||
0xFD, 0x21, 0x6F, 0xF9, 0x21, 0x6B, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0xB3, 0xFD, 0xA0, 0x06, 0xE2,
|
|
||||||
0x21, 0x87, 0xFD, 0x21, 0xC4, 0xFD, 0xC3, 0x00, 0x51, 0xC3, 0x6F, 0x7A, 0xFF, 0xF4, 0xFF, 0xFD,
|
|
||||||
0xF7, 0xD4, 0xD7, 0x01, 0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xF9, 0xE7, 0xF9, 0xEB,
|
|
||||||
0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xFF, 0x74, 0xF9, 0xFB,
|
|
||||||
0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xFF, 0xCE, 0xF9, 0xFB, 0xFF, 0xF4, 0xF9, 0xFB,
|
|
||||||
0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0xF9, 0xFB, 0x41, 0x77, 0xF9, 0x39, 0x21, 0x6F,
|
|
||||||
0xFC, 0x21, 0x64, 0xFD, 0xD6, 0x01, 0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x7A, 0xF9, 0x95, 0xF9,
|
|
||||||
0x99, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9,
|
|
||||||
0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xFF,
|
|
||||||
0xFD, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xF9, 0xA9, 0xA0, 0x01, 0x71, 0x21, 0x70, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x6E, 0xF9, 0xE8, 0x21,
|
|
||||||
0xBC, 0xFC, 0x21, 0xC5, 0xFD, 0xD7, 0x01, 0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68,
|
|
||||||
0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xF9,
|
|
||||||
0x34, 0xF9, 0x38, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xFF,
|
|
||||||
0xF3, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xFF, 0xFD, 0xF9, 0x48, 0xF9,
|
|
||||||
0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xF9, 0x48, 0xA0, 0x07, 0xE2,
|
|
||||||
0x43, 0x85, 0x99, 0x87, 0xFB, 0xAD, 0xFB, 0xAD, 0xFF, 0xFD, 0xA0, 0x07, 0x22, 0x41, 0x62, 0xF8,
|
|
||||||
0xF3, 0xA1, 0x07, 0xE2, 0x75, 0xFC, 0xA0, 0x07, 0xD1, 0x21, 0x6D, 0xFD, 0x21, 0x65, 0xFD, 0x21,
|
|
||||||
0x69, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6F, 0xFD, 0xA1, 0x07, 0xE2, 0x72, 0xFD,
|
|
||||||
0xA0, 0x08, 0x02, 0x24, 0xBA, 0x82, 0x9B, 0xBC, 0xD7, 0xDE, 0xF8, 0xFD, 0xC1, 0x05, 0xC2, 0x72,
|
|
||||||
0xF8, 0xC4, 0x41, 0x68, 0xF8, 0xBE, 0x21, 0x63, 0xFC, 0xA1, 0x05, 0xC2, 0x75, 0xFD, 0x41, 0x7A,
|
|
||||||
0xF8, 0xB2, 0x21, 0x63, 0xFC, 0x21, 0x99, 0xFD, 0xA0, 0x07, 0x43, 0x42, 0x63, 0x74, 0xFF, 0xF3,
|
|
||||||
0xF8, 0xA5, 0x21, 0x70, 0xF9, 0xA0, 0x07, 0xB2, 0x22, 0x85, 0x99, 0xFD, 0xFD, 0x21, 0x7A, 0xF8,
|
|
||||||
0x21, 0x63, 0xFD, 0xA3, 0x02, 0x22, 0xC4, 0x65, 0x79, 0xF5, 0xFD, 0xF2, 0xC5, 0x05, 0xC2, 0xC4,
|
|
||||||
0x77, 0x65, 0x75, 0x7A, 0xFF, 0xD9, 0xFF, 0xDC, 0xFF, 0xE6, 0xF8, 0x8D, 0xFF, 0xF7, 0x41, 0x72,
|
|
||||||
0xF8, 0x75, 0xA1, 0x05, 0xC2, 0x6F, 0xFC, 0x41, 0x6A, 0xFB, 0x16, 0x41, 0x74, 0xFD, 0x46, 0xA1,
|
|
||||||
0x01, 0x92, 0x61, 0xFC, 0x41, 0x87, 0xF7, 0xD4, 0x44, 0x82, 0x9B, 0xBA, 0xBC, 0xF7, 0xD0, 0xF7,
|
|
||||||
0xD0, 0xF7, 0xD0, 0xF7, 0xD0, 0xC2, 0x01, 0x92, 0x68, 0x7A, 0xFC, 0xC6, 0xFC, 0xC6, 0x42, 0xBA,
|
|
||||||
0xBC, 0xF6, 0x95, 0xF6, 0x95, 0xC2, 0x01, 0x92, 0xC5, 0x7A, 0xFF, 0xF9, 0xFC, 0xB6, 0xA0, 0x08,
|
|
||||||
0x42, 0xA3, 0x01, 0x92, 0x63, 0x6E, 0x74, 0xFD, 0xFD, 0xFD, 0x41, 0xBC, 0xF6, 0x79, 0xC2, 0x01,
|
|
||||||
0x92, 0xC5, 0x7A, 0xFF, 0xFC, 0xFC, 0x9D, 0xC1, 0x01, 0x92, 0x7A, 0xFC, 0x94, 0xD1, 0x01, 0x61,
|
|
||||||
0x74, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x77,
|
|
||||||
0x7A, 0xFF, 0xB2, 0xFF, 0xB7, 0xFF, 0xBB, 0xF7, 0x96, 0xFF, 0xC8, 0xFF, 0xD8, 0xF7, 0x96, 0xF7,
|
|
||||||
0x96, 0xF7, 0x96, 0xF7, 0x96, 0xF7, 0x96, 0xF7, 0x96, 0xFF, 0xE4, 0xFF, 0xF1, 0xFF, 0xFA, 0xF7,
|
|
||||||
0x96, 0xF7, 0x96, 0xA0, 0x07, 0x74, 0x21, 0x82, 0xFD, 0xA0, 0x07, 0x73, 0x21, 0x7A, 0xFD, 0x21,
|
|
||||||
0x63, 0xFD, 0x21, 0x69, 0xF7, 0x22, 0x6A, 0x77, 0xFA, 0xFD, 0x21, 0x74, 0xEF, 0x21, 0x6F, 0xFD,
|
|
||||||
0x23, 0xC5, 0x6F, 0x72, 0xE6, 0xF5, 0xFD, 0x21, 0x72, 0xE5, 0x21, 0x6E, 0xDF, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x41, 0x7A, 0xF9, 0xA4, 0x21, 0x72, 0xFC, 0x21, 0x62, 0xFD, 0xA4, 0x01, 0x61,
|
|
||||||
0x62, 0x64, 0x6B, 0x6C, 0xE3, 0xEA, 0xF3, 0xFD, 0x41, 0x6D, 0xFE, 0xFD, 0xA1, 0x01, 0x61, 0x65,
|
|
||||||
0xFC, 0xA0, 0x08, 0x01, 0x21, 0xB3, 0xFD, 0xA0, 0x08, 0x22, 0x21, 0x73, 0xFD, 0x21, 0x79, 0xFD,
|
|
||||||
0x21, 0x7A, 0xF1, 0x21, 0x63, 0xFD, 0x21, 0x79, 0xFD, 0xA5, 0x02, 0x41, 0x69, 0xC3, 0x6D, 0x6F,
|
|
||||||
0x77, 0xE3, 0xEB, 0xF4, 0xE8, 0xFD, 0xC2, 0x05, 0xC2, 0x68, 0x7A, 0xF6, 0xBB, 0xF6, 0xBB, 0x41,
|
|
||||||
0xBA, 0xFB, 0xDC, 0xA1, 0x05, 0xC2, 0xC5, 0xFC, 0xC1, 0x05, 0xC2, 0x7A, 0xF6, 0xA9, 0xC1, 0x05,
|
|
||||||
0xC2, 0x72, 0xF6, 0xA3, 0xD9, 0x07, 0x02, 0xC4, 0xC5, 0x69, 0x6D, 0x72, 0x77, 0x61, 0x65, 0x6F,
|
|
||||||
0x79, 0x7A, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6E, 0x70, 0x73, 0x74, 0x75,
|
|
||||||
0xFE, 0x4C, 0xFE, 0x7F, 0xFE, 0x88, 0xFE, 0x95, 0xFE, 0xC8, 0xFE, 0xDE, 0xFE, 0xE3, 0xFF, 0x39,
|
|
||||||
0xFF, 0xA9, 0xF9, 0xF9, 0xFF, 0xD5, 0xFC, 0x2D, 0xFF, 0xE2, 0xFF, 0xEF, 0xFC, 0x2D, 0xFC, 0x2D,
|
|
||||||
0xFC, 0x2D, 0xFC, 0x2D, 0xFC, 0x2D, 0xFC, 0x2D, 0xFC, 0x2D, 0xFC, 0x2D, 0xFF, 0xF4, 0xFF, 0xFA,
|
|
||||||
0xFC, 0x2D, 0x41, 0x64, 0xF9, 0xAB, 0xA0, 0x05, 0xC1, 0x21, 0x74, 0xFD, 0x23, 0x7A, 0x6B, 0x75,
|
|
||||||
0xF6, 0xFD, 0xFA, 0x42, 0x73, 0x6F, 0xFF, 0x10, 0xFF, 0x16, 0x41, 0x66, 0xF8, 0xDD, 0xC9, 0x01,
|
|
||||||
0x61, 0x66, 0x67, 0x6B, 0x6E, 0x72, 0x73, 0x77, 0x6D, 0x75, 0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0xF5,
|
|
||||||
0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0xFC, 0xF8, 0xD9, 0x44, 0x82, 0x9B, 0xBA,
|
|
||||||
0xBC, 0xFD, 0xC1, 0xFD, 0xC1, 0xFD, 0xC1, 0xFD, 0xC1, 0xC1, 0x03, 0x32, 0x77, 0xF5, 0x0A, 0x21,
|
|
||||||
0x7A, 0xFA, 0xA1, 0x05, 0xC2, 0x65, 0xFD, 0x42, 0xBA, 0xBC, 0xFB, 0x24, 0xFB, 0x24, 0x41, 0x87,
|
|
||||||
0xF7, 0x0F, 0x44, 0x82, 0x9B, 0xBA, 0xBC, 0xF7, 0x0B, 0xF7, 0x0B, 0xF7, 0x0B, 0xF7, 0x0B, 0xC2,
|
|
||||||
0x02, 0x92, 0x68, 0x7A, 0xF4, 0xE4, 0xF4, 0xE4, 0x42, 0xBA, 0xBC, 0xFA, 0x36, 0xFA, 0x36, 0xC2,
|
|
||||||
0x02, 0x92, 0xC5, 0x7A, 0xFF, 0xF9, 0xF4, 0xD4, 0xC1, 0x02, 0x92, 0x7A, 0xF4, 0xCB, 0xC1, 0x02,
|
|
||||||
0x92, 0x6B, 0xF4, 0xC5, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xCA, 0xFF, 0xCE, 0xF6, 0xDF, 0xFF, 0xDB, 0xFF, 0xEB,
|
|
||||||
0xF6, 0xDF, 0xF6, 0xDF, 0xF6, 0xDF, 0xF6, 0xDF, 0xF6, 0xDF, 0xF6, 0xDF, 0xF6, 0xDF, 0xFF, 0xF4,
|
|
||||||
0xFF, 0xF4, 0xFF, 0xFA, 0xF6, 0xDF, 0xF6, 0xDF, 0xC3, 0x05, 0xC2, 0xC5, 0x6F, 0x7A, 0xFF, 0x8F,
|
|
||||||
0xFF, 0xCC, 0xF5, 0x89, 0xA0, 0x08, 0x63, 0x21, 0x87, 0xFD, 0x24, 0x82, 0x9B, 0xBA, 0xBC, 0xFA,
|
|
||||||
0xFA, 0xFA, 0xFA, 0x41, 0x7A, 0xF4, 0x70, 0xD1, 0x08, 0x93, 0xC4, 0xC5, 0x63, 0x64, 0x66, 0x67,
|
|
||||||
0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x72, 0xFF, 0x77, 0xFF, 0x7B, 0xFF,
|
|
||||||
0x88, 0xFF, 0x98, 0xF6, 0x8C, 0xF6, 0x8C, 0xF6, 0x8C, 0xF6, 0x8C, 0xF6, 0x8C, 0xF6, 0x8C, 0xF6,
|
|
||||||
0x8C, 0xF6, 0x8C, 0xF6, 0x8C, 0xFF, 0xA1, 0xF6, 0x8C, 0xF6, 0x8C, 0xFF, 0xFC, 0xC2, 0x08, 0x93,
|
|
||||||
0x68, 0x7A, 0xFA, 0x5E, 0xFA, 0x5E, 0xA0, 0x08, 0xF2, 0x43, 0xBA, 0x9B, 0xBC, 0xF4, 0x2A, 0xF6,
|
|
||||||
0x44, 0xFF, 0xFD, 0xD1, 0x08, 0x93, 0xC5, 0xC4, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xF6, 0xFF, 0x2B, 0xFF, 0x3C, 0xFF, 0x4C, 0xF6,
|
|
||||||
0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6, 0x40, 0xF6,
|
|
||||||
0x40, 0xFF, 0x55, 0xF6, 0x40, 0xF6, 0x40, 0xFA, 0x48, 0xA0, 0x08, 0x93, 0xC1, 0x08, 0x93, 0x7A,
|
|
||||||
0xFA, 0x0F, 0xD1, 0x05, 0xC2, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0x65, 0xFF, 0x68, 0xFF, 0x75, 0xFF, 0xAB, 0xFF, 0xC1,
|
|
||||||
0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFA,
|
|
||||||
0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xC1, 0x08, 0xC3, 0x75, 0xF6, 0x3F, 0x21, 0x7A,
|
|
||||||
0xFA, 0xC2, 0x05, 0xC2, 0x6F, 0x7A, 0xFF, 0xFD, 0xF4, 0xA0, 0xC2, 0x05, 0xC2, 0x6D, 0x7A, 0xF4,
|
|
||||||
0x97, 0xF4, 0x97, 0xC2, 0x05, 0xC2, 0x6B, 0x72, 0xF4, 0x8E, 0xF4, 0x8E, 0x41, 0x7A, 0xF9, 0xAF,
|
|
||||||
0xA1, 0x05, 0xC2, 0x63, 0xFC, 0xC1, 0x05, 0xC2, 0x77, 0xF4, 0x7C, 0xD6, 0x02, 0x01, 0xC4, 0x61,
|
|
||||||
0x65, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x75, 0x77, 0x7A, 0xFC, 0x25, 0xFE, 0x31, 0xFE, 0x43, 0xFE, 0x61, 0xFE, 0x77, 0xFD, 0xBB,
|
|
||||||
0xFE, 0xED, 0xFA, 0x06, 0xFA, 0x06, 0xFA, 0x06, 0xFA, 0x06, 0xFD, 0xD3, 0xFA, 0x06, 0xFA, 0x06,
|
|
||||||
0xFF, 0x97, 0xFA, 0x06, 0xFF, 0xD6, 0xFF, 0xDF, 0xFF, 0xE8, 0xFF, 0xF5, 0xFA, 0x06, 0xFF, 0xFA,
|
|
||||||
0x44, 0x82, 0x9B, 0xBA, 0xBC, 0xF6, 0x93, 0xF6, 0x93, 0xF6, 0x93, 0xF6, 0x93, 0xC2, 0x01, 0xB2,
|
|
||||||
0x63, 0x74, 0xF6, 0xCA, 0xF6, 0xCA, 0xC1, 0x01, 0xB2, 0x72, 0xF4, 0xDA, 0xA0, 0x02, 0x71, 0xC2,
|
|
||||||
0x09, 0x12, 0xC5, 0x64, 0xFC, 0x7F, 0xFF, 0xFD, 0xC2, 0x01, 0x92, 0x70, 0x6B, 0xFC, 0x86, 0xF9,
|
|
||||||
0x33, 0x51, 0x64, 0xC4, 0xC5, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x77, 0x7A, 0xFF, 0xEE, 0xFC, 0x53, 0xFC, 0x57, 0xF4, 0x32, 0xFC, 0x64, 0xF4, 0x32, 0xF4,
|
|
||||||
0x32, 0xF4, 0x32, 0xF4, 0x32, 0xF4, 0x32, 0xFF, 0xF7, 0xF4, 0x32, 0xFC, 0x96, 0xFC, 0x96, 0xF4,
|
|
||||||
0x32, 0xF4, 0x32, 0xF4, 0x32, 0xC2, 0x01, 0xB2, 0x6F, 0x72, 0xFF, 0xCC, 0xF6, 0x69, 0xA0, 0x04,
|
|
||||||
0x32, 0x21, 0x7A, 0xFD, 0xC8, 0x01, 0xB2, 0x63, 0x64, 0x6B, 0x72, 0x74, 0xC5, 0x6F, 0x7A, 0xF6,
|
|
||||||
0x63, 0xF6, 0x63, 0xF6, 0x63, 0xF6, 0x63, 0xF6, 0x63, 0xF6, 0x86, 0xFF, 0xFD, 0xF4, 0x7C, 0xC2,
|
|
||||||
0x01, 0xB2, 0x6D, 0x7A, 0xF4, 0x61, 0xF4, 0x61, 0xC2, 0x01, 0xB2, 0x63, 0x6B, 0xF4, 0x54, 0xF4,
|
|
||||||
0x58, 0xC2, 0x01, 0xB2, 0x6D, 0x77, 0xF9, 0x20, 0xF4, 0x4F, 0x53, 0x64, 0x6A, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x75, 0x77, 0x7A, 0xFC, 0xEA,
|
|
||||||
0xFF, 0x11, 0xF5, 0xEC, 0xFF, 0x56, 0xF6, 0x28, 0xF6, 0x0B, 0xFF, 0x63, 0xF6, 0x28, 0xF3, 0xD5,
|
|
||||||
0xFF, 0x6C, 0xF3, 0xD5, 0xF3, 0xD5, 0xFF, 0xBB, 0xFF, 0xCA, 0xFF, 0xE5, 0xFF, 0xEE, 0xF3, 0xD5,
|
|
||||||
0xF3, 0xD5, 0xFF, 0xF7, 0x41, 0x87, 0xFA, 0xF9, 0xA0, 0x09, 0x32, 0xC4, 0x05, 0xC2, 0x63, 0x6B,
|
|
||||||
0x68, 0x7A, 0xFF, 0xFD, 0xFF, 0xFD, 0xF3, 0x46, 0xF3, 0x46, 0xA0, 0x09, 0x52, 0x42, 0xBA, 0xBC,
|
|
||||||
0xFF, 0xFD, 0xF8, 0x5E, 0x41, 0x7A, 0xF7, 0x8A, 0xA1, 0x02, 0x92, 0x72, 0xFC, 0xC1, 0x02, 0x92,
|
|
||||||
0x72, 0xF2, 0x26, 0x41, 0x68, 0xF7, 0x7B, 0xA1, 0x02, 0x92, 0x63, 0xFC, 0x51, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFD, 0x22,
|
|
||||||
0xFD, 0x26, 0xFF, 0xEC, 0xFD, 0x33, 0xFD, 0x43, 0xF4, 0x37, 0xF4, 0x37, 0xF4, 0x37, 0xFF, 0xF1,
|
|
||||||
0xF4, 0x37, 0xFD, 0x52, 0xFF, 0xFB, 0xFD, 0x4C, 0xFD, 0x4C, 0xFD, 0x52, 0xF4, 0x37, 0xF4, 0x37,
|
|
||||||
0xC3, 0x05, 0xC2, 0xC5, 0x6F, 0x7A, 0xFF, 0xAD, 0xFF, 0xCC, 0xF2, 0xE1, 0xC2, 0x05, 0xC2, 0x63,
|
|
||||||
0x6B, 0xFF, 0x8C, 0xFF, 0x8C, 0xA0, 0x09, 0x92, 0xC1, 0x03, 0x12, 0x75, 0xF4, 0x5F, 0xA0, 0x09,
|
|
||||||
0xE1, 0x44, 0xBA, 0x82, 0x9B, 0xBC, 0xFF, 0xF4, 0xFF, 0xF7, 0xF4, 0x42, 0xFF, 0xFD, 0x41, 0x68,
|
|
||||||
0xF4, 0x49, 0x21, 0x63, 0xFC, 0xA1, 0x02, 0xB2, 0x75, 0xFD, 0x41, 0x7A, 0xF4, 0x3D, 0x21, 0x63,
|
|
||||||
0xFC, 0x21, 0x99, 0xFD, 0x41, 0xBC, 0xF4, 0x33, 0x42, 0xC5, 0x70, 0xFF, 0xFC, 0xF4, 0x2F, 0x42,
|
|
||||||
0x63, 0x74, 0xFF, 0xEB, 0xF4, 0x28, 0x21, 0x70, 0xF9, 0xA3, 0x02, 0xB2, 0xC4, 0x61, 0x65, 0xE8,
|
|
||||||
0xEF, 0xFD, 0x41, 0x6A, 0xF4, 0x15, 0xA2, 0x02, 0xB2, 0x61, 0x6F, 0xFC, 0xFC, 0xA0, 0x09, 0xB3,
|
|
||||||
0x21, 0x63, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x7A, 0xF4, 0x21, 0x72, 0xFD, 0x21,
|
|
||||||
0x74, 0xFD, 0xA2, 0x02, 0x71, 0x63, 0x73, 0xF4, 0xFD, 0x41, 0x87, 0xF3, 0xDA, 0xC2, 0x02, 0xB2,
|
|
||||||
0x68, 0x7A, 0xF6, 0xB1, 0xF6, 0xB1, 0x42, 0xBA, 0xBC, 0xF8, 0x59, 0xF8, 0x59, 0xA1, 0x02, 0xB2,
|
|
||||||
0xC5, 0xF9, 0xC2, 0x02, 0xB2, 0x6D, 0x7A, 0xF6, 0x9C, 0xF6, 0x9C, 0xD5, 0x09, 0x72, 0xC5, 0x6D,
|
|
||||||
0x72, 0x77, 0x6F, 0x75, 0x7A, 0xC4, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6E,
|
|
||||||
0x70, 0x73, 0x74, 0xFF, 0x76, 0xFF, 0x8A, 0xFF, 0xAE, 0xFF, 0xBB, 0xFF, 0xD7, 0xFE, 0x21, 0xF3,
|
|
||||||
0x52, 0xFF, 0xDE, 0xF3, 0x72, 0xFF, 0xE2, 0xFF, 0xF2, 0xF3, 0x72, 0xF3, 0x72, 0xF3, 0x72, 0xF3,
|
|
||||||
0x72, 0xF3, 0x72, 0xF3, 0x72, 0xF3, 0x72, 0xF3, 0x72, 0xFF, 0xF7, 0xF3, 0x72, 0xC2, 0x02, 0x92,
|
|
||||||
0x6D, 0x7A, 0xF0, 0xF6, 0xF0, 0xF6, 0x51, 0x64, 0xC4, 0xC5, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6B,
|
|
||||||
0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xB5, 0xFB, 0xF8, 0xFB, 0xFC, 0xF3, 0x0D,
|
|
||||||
0xFC, 0x09, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D,
|
|
||||||
0xFC, 0x22, 0xFF, 0xF7, 0xF3, 0x0D, 0xF3, 0x0D, 0xF3, 0x0D, 0xA0, 0x02, 0xF1, 0xA1, 0x02, 0xD2,
|
|
||||||
0x6B, 0xFD, 0x44, 0x82, 0x9B, 0xBA, 0xBC, 0xFF, 0xFB, 0xF2, 0xF7, 0xF2, 0xF7, 0xF2, 0xF7, 0xA0,
|
|
||||||
0x0A, 0x12, 0xA0, 0x0A, 0xC2, 0x21, 0x63, 0xFD, 0xA1, 0x0A, 0x32, 0x79, 0xFD, 0x21, 0xBC, 0xFB,
|
|
||||||
0x21, 0xC5, 0xFD, 0xA1, 0x0B, 0x42, 0x75, 0xFD, 0xA0, 0x0A, 0xE3, 0xA0, 0x0B, 0x42, 0x24, 0xBA,
|
|
||||||
0x82, 0xBC, 0x9B, 0xE1, 0xF5, 0xFA, 0xFD, 0xA0, 0x0A, 0x32, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD,
|
|
||||||
0xA1, 0x0B, 0x62, 0x75, 0xFD, 0x21, 0x74, 0xF2, 0x21, 0x61, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x6B,
|
|
||||||
0xE9, 0x21, 0x6C, 0xE6, 0xA0, 0x0A, 0x53, 0xA4, 0x0B, 0x62, 0x61, 0x75, 0x79, 0x7A, 0xF4, 0xF7,
|
|
||||||
0xFA, 0xFD, 0xA1, 0x0B, 0x62, 0x6D, 0xD5, 0xA0, 0x02, 0xD1, 0xA0, 0x0B, 0x13, 0x41, 0x6D, 0xFF,
|
|
||||||
0xA5, 0x41, 0x6A, 0xFF, 0xA1, 0x22, 0x72, 0x6F, 0xB3, 0xFC, 0xC6, 0x02, 0xD2, 0x61, 0x67, 0x69,
|
|
||||||
0x6A, 0x6C, 0x77, 0xFF, 0xF0, 0xFF, 0xAE, 0xFF, 0xF3, 0xFF, 0x98, 0xFF, 0x98, 0xFF, 0xFB, 0x41,
|
|
||||||
0x87, 0xFF, 0x9C, 0xC2, 0x0B, 0x62, 0x68, 0x7A, 0xFF, 0x67, 0xFF, 0x67, 0xA0, 0x0B, 0x81, 0x22,
|
|
||||||
0xBA, 0xBC, 0xFD, 0xFD, 0xC2, 0x0B, 0x62, 0xC5, 0x7A, 0xFF, 0xFB, 0xFF, 0x56, 0xA0, 0x0B, 0x62,
|
|
||||||
0xC1, 0x0B, 0x62, 0x7A, 0xFF, 0x7B, 0xD5, 0x09, 0xF2, 0xC5, 0x6D, 0x72, 0x75, 0x79, 0x7A, 0xC4,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6E, 0x70, 0x73, 0x74, 0x77, 0xFF, 0x78,
|
|
||||||
0xFF, 0x8A, 0xFF, 0xA1, 0xFF, 0xAC, 0xFF, 0xB1, 0xFF, 0xC4, 0xFF, 0xD9, 0xFF, 0xDD, 0xFF, 0xEE,
|
|
||||||
0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xF7,
|
|
||||||
0xFF, 0xF7, 0xFF, 0xFA, 0xFF, 0xF7, 0xFF, 0xF7, 0x41, 0x87, 0xF2, 0x01, 0x41, 0x7A, 0xFF, 0x13,
|
|
||||||
0xA1, 0x06, 0xC2, 0x72, 0xFC, 0xC2, 0x06, 0xC2, 0x68, 0x7A, 0xF6, 0xBA, 0xF6, 0xBA, 0xA0, 0x0A,
|
|
||||||
0x84, 0x21, 0x73, 0xFD, 0x21, 0x6B, 0xFD, 0x41, 0x7A, 0xFE, 0xE3, 0xA1, 0x06, 0xC2, 0x72, 0xFC,
|
|
||||||
0xC2, 0x06, 0xC2, 0x6C, 0x72, 0xF6, 0x9F, 0xF6, 0x9F, 0x41, 0x68, 0xFE, 0xD1, 0xA1, 0x06, 0xC2,
|
|
||||||
0x63, 0xFC, 0x41, 0xBC, 0xFE, 0xC8, 0xC2, 0x06, 0xC2, 0xC5, 0x7A, 0xFF, 0xFC, 0xF6, 0x89, 0xC3,
|
|
||||||
0x06, 0xC2, 0x63, 0x6D, 0x7A, 0xFF, 0xEA, 0xF6, 0x80, 0xF6, 0x80, 0xC2, 0x06, 0xC2, 0x6B, 0x72,
|
|
||||||
0xF6, 0x74, 0xF6, 0x74, 0x53, 0xC5, 0x64, 0xC4, 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFE, 0xAE, 0xFF, 0x62, 0xFF, 0xA4, 0xFF, 0xAC,
|
|
||||||
0xFF, 0xB1, 0xFF, 0xC0, 0xF6, 0xE3, 0xFF, 0xC7, 0xF6, 0xE3, 0xFF, 0xCC, 0xF6, 0xE3, 0xF6, 0xE3,
|
|
||||||
0xF6, 0xE3, 0xFF, 0xD9, 0xFF, 0xE2, 0xFF, 0xEB, 0xFF, 0xF7, 0xF6, 0xE3, 0xF6, 0xE3, 0x21, 0x65,
|
|
||||||
0xC6, 0x21, 0x7A, 0xFD, 0xC2, 0x05, 0xC2, 0x6F, 0x72, 0xFE, 0x32, 0xFF, 0xFD, 0xC1, 0x03, 0x52,
|
|
||||||
0x72, 0xF6, 0x9A, 0x41, 0x9B, 0xF4, 0x6B, 0x41, 0x7A, 0xF6, 0x18, 0x21, 0x72, 0xFC, 0x41, 0x72,
|
|
||||||
0xF4, 0x60, 0xC6, 0x03, 0x32, 0x65, 0x75, 0xC5, 0x62, 0x6D, 0x74, 0xFF, 0xEB, 0xF1, 0x95, 0xFF,
|
|
||||||
0xF1, 0xFF, 0xF9, 0xEF, 0x01, 0xFF, 0xFC, 0x21, 0x7A, 0xEB, 0xC2, 0x05, 0xC2, 0x6F, 0x7A, 0xFF,
|
|
||||||
0xFD, 0xEF, 0xE7, 0x41, 0x65, 0xF1, 0x74, 0xA1, 0x03, 0x32, 0x69, 0xFC, 0x21, 0x62, 0xFB, 0xC2,
|
|
||||||
0x05, 0xC2, 0x75, 0x7A, 0xFF, 0xFD, 0xEF, 0xD2, 0xC2, 0x05, 0xC2, 0x63, 0x77, 0xFB, 0x44, 0xEF,
|
|
||||||
0xC9, 0xC2, 0x02, 0x92, 0x6B, 0x72, 0xEE, 0xC2, 0xEE, 0xC2, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64,
|
|
||||||
0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xF9, 0xC4, 0xF9, 0xC8,
|
|
||||||
0xFC, 0x8E, 0xF9, 0xD5, 0xF9, 0xE5, 0xF0, 0xD9, 0xF0, 0xD9, 0xF0, 0xD9, 0xF0, 0xD9, 0xF0, 0xD9,
|
|
||||||
0xF0, 0xD9, 0xF0, 0xD9, 0xF9, 0xEE, 0xF9, 0xEE, 0xFF, 0xF7, 0xF0, 0xD9, 0xF0, 0xD9, 0xA1, 0x05,
|
|
||||||
0xC2, 0x79, 0xCC, 0x41, 0x87, 0xF4, 0xA8, 0x44, 0x82, 0x9B, 0xBA, 0xBC, 0xF4, 0xA4, 0xF4, 0xA4,
|
|
||||||
0xF4, 0xA4, 0xF4, 0xA4, 0x43, 0x9B, 0xBA, 0xBC, 0xF0, 0x89, 0xF0, 0x89, 0xF0, 0x89, 0xCD, 0x00,
|
|
||||||
0x91, 0xC4, 0xC5, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x70, 0x73, 0x77, 0xF9, 0x70,
|
|
||||||
0xFF, 0xF6, 0xF9, 0x81, 0xF9, 0x91, 0xF0, 0x85, 0xF0, 0x85, 0xF0, 0x85, 0xF0, 0x85, 0xF0, 0x85,
|
|
||||||
0xF0, 0x85, 0xF0, 0x85, 0xF9, 0x9A, 0xF0, 0x85, 0xC2, 0x00, 0x91, 0x68, 0x7A, 0xF4, 0x63, 0xF4,
|
|
||||||
0x63, 0x44, 0xBA, 0x82, 0x9B, 0xBC, 0xEE, 0x32, 0xF0, 0x4C, 0xF0, 0x4C, 0xFA, 0x05, 0xC1, 0x00,
|
|
||||||
0x71, 0x72, 0xFB, 0x0E, 0xD0, 0x00, 0x91, 0xC5, 0xC4, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xED, 0xF9, 0x2A, 0xF9, 0x3B, 0xF9, 0x4B, 0xF0,
|
|
||||||
0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF9,
|
|
||||||
0x54, 0xF0, 0x3F, 0xFF, 0xFA, 0xF4, 0x47, 0xC1, 0x00, 0x91, 0x7A, 0xF4, 0x14, 0xD1, 0x02, 0x41,
|
|
||||||
0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77,
|
|
||||||
0x7A, 0xFF, 0x66, 0xFF, 0x6A, 0xFF, 0x81, 0xFF, 0xAB, 0xFF, 0xC7, 0xEE, 0xE4, 0xEE, 0xE4, 0xEE,
|
|
||||||
0xE4, 0xEE, 0xE4, 0xEE, 0xE4, 0xEE, 0xE4, 0xEE, 0xE4, 0xFF, 0xFA, 0xFF, 0xFA, 0xEE, 0xE4, 0xEE,
|
|
||||||
0xE4, 0xEE, 0xE4, 0x53, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70,
|
|
||||||
0x72, 0x73, 0x74, 0x75, 0x77, 0x7A, 0x6F, 0xFB, 0x61, 0xF8, 0x99, 0xF4, 0x3E, 0xFB, 0x68, 0xFB,
|
|
||||||
0xCD, 0xF4, 0x3E, 0xF4, 0x3E, 0xF4, 0x3E, 0xF4, 0x3E, 0xF4, 0x3E, 0xFB, 0xD9, 0xFE, 0x91, 0xFE,
|
|
||||||
0xC7, 0xFE, 0xDC, 0xFA, 0x20, 0xFE, 0xE5, 0xFF, 0x2B, 0xFA, 0x32, 0xFF, 0xCA, 0x21, 0x65, 0xC6,
|
|
||||||
0xD8, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0xEF, 0x09, 0xEF, 0x0D, 0xFA,
|
|
||||||
0xEA, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xF3, 0x40, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xFF,
|
|
||||||
0xFD, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF,
|
|
||||||
0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xEF, 0x1D, 0xA0, 0x06, 0x11, 0x21, 0x87,
|
|
||||||
0xFD, 0x41, 0x6F, 0xEF, 0xA8, 0x21, 0x69, 0xFC, 0xA1, 0x06, 0x11, 0x6D, 0xFD, 0x23, 0x9B, 0xBA,
|
|
||||||
0xBC, 0xFB, 0xEE, 0xEE, 0x42, 0x85, 0x99, 0xF3, 0x94, 0xF3, 0x94, 0xA0, 0x05, 0x92, 0xA1, 0x05,
|
|
||||||
0x72, 0x7A, 0xFD, 0x21, 0x63, 0xFB, 0xA2, 0x03, 0xA2, 0xC4, 0x6F, 0xEE, 0xFD, 0x44, 0x82, 0x9B,
|
|
||||||
0xBA, 0xBC, 0xFF, 0xF9, 0xF0, 0x56, 0xF0, 0x56, 0xF0, 0x56, 0x41, 0x67, 0xF3, 0x6E, 0x21, 0x7A,
|
|
||||||
0xFC, 0xA1, 0x01, 0xB2, 0x75, 0xFD, 0x41, 0x87, 0xF3, 0x62, 0x41, 0x6F, 0xF3, 0x5E, 0x22, 0xC4,
|
|
||||||
0x73, 0xF8, 0xFC, 0x41, 0x84, 0xF3, 0x55, 0x42, 0xC5, 0x6E, 0xFF, 0xFC, 0xF3, 0x51, 0x41, 0xBA,
|
|
||||||
0xF3, 0x4A, 0x42, 0xC5, 0x7A, 0xFF, 0xFC, 0xF3, 0x46, 0x21, 0x64, 0xB2, 0x22, 0x85, 0x99, 0xAF,
|
|
||||||
0xFD, 0x21, 0x7A, 0xAA, 0x22, 0x63, 0x74, 0xA7, 0xA7, 0x41, 0x6E, 0xFF, 0xA2, 0xA4, 0x0B, 0x93,
|
|
||||||
0xC4, 0x65, 0x75, 0x79, 0xEF, 0xF4, 0xF7, 0xFC, 0xA4, 0x01, 0x61, 0x61, 0x6F, 0x79, 0x7A, 0xC6,
|
|
||||||
0xCF, 0xDA, 0xF5, 0xC3, 0x05, 0xC2, 0x6E, 0x68, 0x7A, 0xFA, 0x45, 0xED, 0x8E, 0xED, 0x8E, 0xC2,
|
|
||||||
0x05, 0xC2, 0xC5, 0x7A, 0xF7, 0x88, 0xED, 0x82, 0xA0, 0x0C, 0x52, 0x41, 0xBC, 0xF2, 0xA0, 0xC4,
|
|
||||||
0x05, 0xC2, 0x74, 0xC5, 0x6D, 0x7A, 0xFF, 0xF9, 0xFF, 0xFC, 0xF4, 0xBA, 0xED, 0x72, 0x41, 0x68,
|
|
||||||
0xF2, 0x8D, 0xC2, 0x05, 0xC2, 0x63, 0x7A, 0xFF, 0xFC, 0xED, 0x5F, 0xC1, 0x05, 0xC2, 0x6B, 0xED,
|
|
||||||
0x56, 0xC1, 0x05, 0xC2, 0x77, 0xFA, 0x07, 0xD1, 0x00, 0x81, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66,
|
|
||||||
0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xF9, 0xFD, 0xF7, 0x35, 0xF2,
|
|
||||||
0xDA, 0xFF, 0xBC, 0xFF, 0xC8, 0xF2, 0xDA, 0xF2, 0xDA, 0xF2, 0xDA, 0xF2, 0xDA, 0xF2, 0xDA, 0xF2,
|
|
||||||
0xDA, 0xF2, 0xDA, 0xFF, 0xD8, 0xFF, 0xEB, 0xFF, 0xF4, 0xF2, 0xDA, 0xFF, 0xFA, 0xC1, 0x00, 0x81,
|
|
||||||
0x62, 0xF2, 0xA4, 0x41, 0x7A, 0xED, 0x5C, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD,
|
|
||||||
0xC1, 0x01, 0xB2, 0x7A, 0xED, 0xC0, 0xA0, 0x0C, 0x23, 0x21, 0x6D, 0xFD, 0xD5, 0x00, 0xF2, 0xC5,
|
|
||||||
0x6C, 0x72, 0x65, 0x69, 0xC4, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x6F, 0x70,
|
|
||||||
0x73, 0x74, 0x75, 0x77, 0xFF, 0x01, 0xFF, 0x15, 0xFF, 0x5C, 0xFF, 0xAB, 0xFF, 0xE1, 0xEF, 0x5A,
|
|
||||||
0xEF, 0x79, 0xEF, 0x89, 0xED, 0x43, 0xED, 0x43, 0xED, 0x43, 0xED, 0x43, 0xED, 0x43, 0xED, 0x43,
|
|
||||||
0xED, 0x43, 0xFF, 0xF1, 0xED, 0x43, 0xFF, 0xF4, 0xED, 0x43, 0xFF, 0xFD, 0xED, 0x43, 0x41, 0x6F,
|
|
||||||
0xED, 0x01, 0xC2, 0x01, 0xB2, 0x75, 0x7A, 0xF2, 0x36, 0xFF, 0xFC, 0x41, 0x74, 0xF2, 0x2D, 0x21,
|
|
||||||
0x99, 0xFC, 0x41, 0x70, 0xF2, 0x26, 0x41, 0x6E, 0xF2, 0x22, 0x21, 0x69, 0xFC, 0x21, 0x62, 0xFD,
|
|
||||||
0x41, 0x69, 0xF2, 0x18, 0x41, 0xC4, 0xFE, 0xB2, 0xC2, 0x02, 0x41, 0x65, 0x77, 0xFF, 0xFC, 0xF2,
|
|
||||||
0x10, 0xA6, 0x01, 0xB2, 0xC4, 0x61, 0x6F, 0x75, 0x77, 0x7A, 0xDE, 0xE1, 0xEC, 0xDA, 0xEF, 0xF7,
|
|
||||||
0xA0, 0x0C, 0x02, 0xA1, 0x0B, 0xC2, 0x72, 0xFD, 0xA1, 0x0B, 0xC2, 0x6D, 0xF8, 0x22, 0x61, 0x65,
|
|
||||||
0xF6, 0xFB, 0xA1, 0x0C, 0x92, 0x69, 0xFB, 0xC1, 0x05, 0xC2, 0x6B, 0xF4, 0x06, 0xC1, 0x05, 0xC2,
|
|
||||||
0x63, 0xFE, 0xF1, 0xC2, 0x05, 0xC2, 0xC5, 0x7A, 0xFE, 0xD8, 0xEC, 0x4E, 0xC2, 0x05, 0xC2, 0x63,
|
|
||||||
0x6B, 0xFE, 0xE2, 0xEC, 0x45, 0xD1, 0x00, 0x81, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xF8, 0xEF, 0xF6, 0x27, 0xF1, 0xCC, 0xF5,
|
|
||||||
0x81, 0xFE, 0xBA, 0xF1, 0xCC, 0xF1, 0xCC, 0xF1, 0xCC, 0xF1, 0xCC, 0xF1, 0xCC, 0xFF, 0xE2, 0xFF,
|
|
||||||
0xE8, 0xFF, 0xEE, 0xF5, 0x93, 0xFF, 0xF7, 0xF1, 0xCC, 0xF1, 0xCC, 0xA0, 0x0C, 0x72, 0x43, 0xBA,
|
|
||||||
0x9B, 0xBC, 0xEC, 0xC2, 0xEE, 0x65, 0xFF, 0xFD, 0x41, 0x75, 0xEF, 0x55, 0xC1, 0x01, 0xB2, 0x65,
|
|
||||||
0xF8, 0x35, 0x41, 0x73, 0xEC, 0x3D, 0x42, 0x63, 0x6D, 0xFE, 0xDD, 0xFE, 0xF0, 0xD8, 0x00, 0xF2,
|
|
||||||
0x69, 0x72, 0x7A, 0x65, 0xC5, 0xC4, 0x61, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x77, 0xFF, 0x35, 0xFF, 0x64, 0xFF, 0x85, 0xFF, 0xA8,
|
|
||||||
0xFF, 0xE1, 0xEE, 0x49, 0xFF, 0xEB, 0xFF, 0xEF, 0xEE, 0x68, 0xEE, 0x78, 0xEC, 0x32, 0xEC, 0x32,
|
|
||||||
0xEC, 0x32, 0xEC, 0x32, 0xEE, 0x85, 0xEC, 0x32, 0xEC, 0x32, 0xEC, 0x32, 0xFF, 0xF5, 0xEC, 0x32,
|
|
||||||
0xFE, 0xE3, 0xEC, 0x32, 0xFF, 0xF9, 0xEC, 0x32, 0x41, 0x82, 0xF1, 0x48, 0x21, 0xC5, 0xFC, 0x21,
|
|
||||||
0x68, 0xFD, 0xA1, 0x02, 0x01, 0x63, 0xFD, 0xA0, 0x0B, 0xE2, 0x21, 0x6E, 0xFD, 0x21, 0x9B, 0xFD,
|
|
||||||
0xA0, 0x0C, 0xB2, 0x21, 0x6F, 0xFD, 0x22, 0xC5, 0x65, 0xF7, 0xFD, 0x41, 0x7A, 0xEE, 0xD2, 0xC1,
|
|
||||||
0x06, 0x11, 0x72, 0xEE, 0x0F, 0xC2, 0x02, 0x01, 0x68, 0x7A, 0xFF, 0xFA, 0xFD, 0x46, 0xA0, 0x02,
|
|
||||||
0x01, 0x41, 0x6B, 0xF1, 0xE6, 0x21, 0x6F, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0xB3,
|
|
||||||
0xFD, 0x41, 0x72, 0xEC, 0x6C, 0x21, 0x74, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0xA2, 0x02,
|
|
||||||
0x01, 0xC3, 0x6E, 0xF0, 0xFD, 0x41, 0x6D, 0xF0, 0xCC, 0xC2, 0x02, 0x01, 0x61, 0x72, 0xFF, 0xFC,
|
|
||||||
0xFD, 0x12, 0x41, 0x68, 0xEB, 0xEE, 0xA1, 0x02, 0x01, 0x63, 0xFC, 0xA0, 0x0C, 0xD2, 0x21, 0xBC,
|
|
||||||
0xFD, 0x21, 0x99, 0xBD, 0x21, 0xC4, 0xFD, 0xA0, 0x07, 0x02, 0x23, 0xC5, 0x74, 0x7A, 0xF4, 0xFA,
|
|
||||||
0xFD, 0xA0, 0x05, 0xE2, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0xA1, 0x04, 0x32, 0x73, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFB, 0x21, 0x65, 0xFD, 0xC2, 0x02, 0x01, 0x69, 0x7A, 0xFF, 0xFD, 0xFC, 0xD6, 0x52, 0xC4,
|
|
||||||
0xC5, 0x62, 0x64, 0x74, 0x6C, 0x61, 0x63, 0x66, 0x67, 0x68, 0x6B, 0x6D, 0x70, 0x72, 0x73, 0x77,
|
|
||||||
0x7A, 0xFC, 0xD0, 0xFC, 0xDF, 0xFD, 0xFE, 0xFF, 0x0F, 0xFF, 0x64, 0xFF, 0x78, 0xFF, 0x7D, 0xFF,
|
|
||||||
0x87, 0xFF, 0x90, 0xFF, 0xB0, 0xFF, 0x90, 0xFF, 0xBB, 0xFF, 0x90, 0xFF, 0xC8, 0xFF, 0xDC, 0xFF,
|
|
||||||
0xF7, 0xFF, 0x90, 0xFF, 0x90, 0x41, 0x77, 0xF1, 0x42, 0x21, 0x6F, 0xFC, 0x21, 0x6B, 0xFD, 0x21,
|
|
||||||
0x73, 0xFD, 0xA1, 0x00, 0x61, 0x61, 0xFD, 0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFF, 0xFB, 0xE9,
|
|
||||||
0x23, 0xE9, 0x23, 0xE9, 0x23, 0xE9, 0x23, 0xC1, 0x00, 0x71, 0x72, 0xE9, 0x9C, 0x41, 0x72, 0xF9,
|
|
||||||
0x4A, 0x41, 0x64, 0xE9, 0x92, 0xA2, 0x00, 0x71, 0x62, 0x6D, 0xF8, 0xFC, 0x41, 0x6B, 0xE9, 0x9A,
|
|
||||||
0x21, 0x6D, 0xFC, 0x21, 0x79, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x72, 0xFD, 0x42, 0x85, 0x99, 0xF0,
|
|
||||||
0xCF, 0xF0, 0xCF, 0x41, 0x7A, 0xF0, 0xC8, 0x21, 0x63, 0xFC, 0xC7, 0x06, 0xE2, 0x6B, 0x6D, 0x6F,
|
|
||||||
0x70, 0xC4, 0x65, 0x79, 0xFF, 0xCD, 0xEF, 0x91, 0xFF, 0xDB, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xFD,
|
|
||||||
0xF0, 0xC1, 0x21, 0x82, 0xE8, 0x21, 0xC5, 0xFD, 0x21, 0xB3, 0xFD, 0xC1, 0x00, 0x51, 0x68, 0xE8,
|
|
||||||
0xBF, 0x41, 0x6B, 0xEC, 0x49, 0x21, 0x6F, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x82, 0xFD, 0xA0, 0x0C,
|
|
||||||
0xF3, 0xA1, 0x02, 0x22, 0x6E, 0xFD, 0xC5, 0x02, 0x02, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xEA, 0xDA,
|
|
||||||
0xFF, 0xFB, 0xEA, 0xDA, 0xEA, 0xDA, 0xEA, 0xDA, 0x41, 0x6B, 0xEA, 0x67, 0xA0, 0x0D, 0x22, 0x21,
|
|
||||||
0x6C, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFD, 0x42, 0x72, 0x73, 0xEA, 0x09, 0xEA, 0x09, 0x21,
|
|
||||||
0x65, 0xF9, 0x21, 0x65, 0xFD, 0xA0, 0x0D, 0x43, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x65,
|
|
||||||
0xFD, 0x21, 0x70, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0x26, 0xC5, 0x72, 0x63, 0x65, 0x70,
|
|
||||||
0x7A, 0xB1, 0xBC, 0xCE, 0xDB, 0xE8, 0xFD, 0x41, 0x74, 0xEA, 0x2E, 0x21, 0x65, 0xFC, 0xA1, 0x0D,
|
|
||||||
0x72, 0x73, 0xFD, 0x21, 0x87, 0xFB, 0x41, 0x9B, 0xEB, 0x13, 0x21, 0xC5, 0xFC, 0x21, 0x6F, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x22, 0xC4, 0x63, 0xF0, 0xFD, 0x21, 0x99, 0xFB, 0x41, 0x77, 0xEA, 0xF8, 0x21,
|
|
||||||
0x72, 0xFC, 0x41, 0x6E, 0xEC, 0xDA, 0x23, 0xC4, 0x65, 0x6F, 0xF2, 0xF9, 0xFC, 0xC1, 0x03, 0xA2,
|
|
||||||
0x6B, 0xE9, 0xA4, 0x41, 0x63, 0xEC, 0x06, 0x45, 0x82, 0x9B, 0xBA, 0xBC, 0x84, 0xFF, 0xF6, 0xEB,
|
|
||||||
0xFC, 0xEB, 0xFC, 0xEB, 0xFC, 0xFF, 0xFC, 0xC1, 0x07, 0xE2, 0x75, 0xEA, 0x39, 0xA0, 0x0E, 0x83,
|
|
||||||
0x21, 0x64, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0xC3, 0xFD, 0xA1, 0x07, 0xE2, 0x72, 0xFD, 0x44, 0xBA,
|
|
||||||
0x82, 0x9B, 0xBC, 0xF1, 0x2C, 0xFF, 0xE9, 0xFF, 0xFB, 0xF1, 0x52, 0x41, 0x77, 0xEA, 0x15, 0xA1,
|
|
||||||
0x05, 0xC2, 0x61, 0xFC, 0xC1, 0x02, 0x22, 0x6E, 0xF1, 0x71, 0x21, 0x7A, 0xFA, 0x22, 0x63, 0x74,
|
|
||||||
0xFD, 0xEE, 0x21, 0x99, 0xFB, 0x41, 0xBC, 0xE9, 0xFB, 0x21, 0xC5, 0xFC, 0x21, 0xB3, 0xFD, 0x41,
|
|
||||||
0x69, 0xF1, 0x49, 0x42, 0xC5, 0x70, 0xFF, 0xF2, 0xE9, 0xED, 0x41, 0xB3, 0xE9, 0xE6, 0x44, 0xC3,
|
|
||||||
0x61, 0x6F, 0x79, 0xFF, 0xFC, 0xE9, 0xE2, 0xE9, 0xE2, 0xE9, 0xE2, 0xA0, 0x03, 0x32, 0x44, 0xC5,
|
|
||||||
0x62, 0x63, 0x7A, 0xFF, 0xD7, 0xFF, 0xF0, 0xF1, 0x20, 0xFF, 0xFD, 0x41, 0x67, 0xE9, 0xC5, 0x21,
|
|
||||||
0x7A, 0xFC, 0x41, 0x65, 0xE9, 0xBE, 0xC9, 0x05, 0xC2, 0xC4, 0xC3, 0x77, 0x61, 0x65, 0x6F, 0x75,
|
|
||||||
0x79, 0x7A, 0xFF, 0xBC, 0xFF, 0xC6, 0xFF, 0xC9, 0xFF, 0xCD, 0xF1, 0x1C, 0xFF, 0xE8, 0xFF, 0xF9,
|
|
||||||
0xFF, 0xF5, 0xFF, 0xFC, 0x42, 0x6A, 0x72, 0xE9, 0x9F, 0xE9, 0x9C, 0x21, 0xB3, 0xF9, 0x41, 0x6A,
|
|
||||||
0xE9, 0x92, 0x43, 0x69, 0x6A, 0x72, 0xE9, 0x8E, 0xE9, 0x8E, 0xF0, 0xDC, 0xA3, 0x05, 0xC2, 0xC3,
|
|
||||||
0x61, 0x6F, 0xEF, 0xF2, 0xF6, 0x41, 0x70, 0xF2, 0x81, 0xA1, 0x01, 0x61, 0x6C, 0xFC, 0xA0, 0x0D,
|
|
||||||
0xD5, 0xA1, 0x01, 0x92, 0x73, 0xFD, 0x41, 0x68, 0xE7, 0xBD, 0xA1, 0x01, 0x92, 0x63, 0xFC, 0xC2,
|
|
||||||
0x01, 0x92, 0x63, 0x7A, 0xFF, 0xF7, 0xED, 0xDC, 0xC1, 0x01, 0x92, 0x6B, 0xE8, 0xD0, 0xD2, 0x01,
|
|
||||||
0x61, 0x6A, 0x6B, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x77, 0x7A, 0xEB, 0xFF, 0xFF, 0xE3, 0xF0, 0xF6, 0xF0, 0xFA, 0xE8, 0xD5, 0xF1, 0x07, 0xF1,
|
|
||||||
0x17, 0xE8, 0xD5, 0xE8, 0xD5, 0xE8, 0xD5, 0xE8, 0xD5, 0xE8, 0xD5, 0xFF, 0xEC, 0xF1, 0x30, 0xFF,
|
|
||||||
0xF1, 0xFF, 0xFA, 0xE8, 0xD5, 0xE8, 0xD5, 0x42, 0x75, 0x6E, 0xEB, 0xC6, 0xED, 0xFA, 0x41, 0xB3,
|
|
||||||
0xF1, 0x3E, 0x41, 0x64, 0xF1, 0x37, 0x21, 0x61, 0xFC, 0x41, 0x6A, 0xF1, 0x36, 0x41, 0x61, 0xF1,
|
|
||||||
0x2F, 0x45, 0xC3, 0x69, 0x6F, 0x72, 0x73, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFC, 0xF1,
|
|
||||||
0x52, 0x41, 0x63, 0xF1, 0x18, 0x21, 0x6F, 0xFC, 0x21, 0x68, 0xFD, 0x42, 0x6D, 0x64, 0xF1, 0x0E,
|
|
||||||
0xEA, 0xDC, 0x41, 0x67, 0xF1, 0x07, 0x21, 0x99, 0xFC, 0x41, 0x73, 0xF1, 0x00, 0x22, 0xC4, 0x65,
|
|
||||||
0xF9, 0xFC, 0x43, 0x69, 0x6E, 0x72, 0xF1, 0x1B, 0xF0, 0xF7, 0xFF, 0xFB, 0x41, 0x65, 0xF0, 0xF3,
|
|
||||||
0x21, 0x69, 0xFC, 0x41, 0x77, 0xF0, 0xE6, 0x21, 0x79, 0xFC, 0x41, 0x6B, 0xF0, 0xDF, 0x21, 0x61,
|
|
||||||
0xFC, 0x21, 0x69, 0xFD, 0x21, 0x6E, 0xFD, 0x42, 0x69, 0x74, 0xFF, 0xFD, 0xF0, 0xF0, 0xCA, 0x01,
|
|
||||||
0x61, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x70, 0x72, 0x73, 0x6C, 0xFF, 0xA3, 0xFF, 0xBA, 0xFF,
|
|
||||||
0xBD, 0xF0, 0xCB, 0xF0, 0xCB, 0xFF, 0xD4, 0xFF, 0xE2, 0xFF, 0xE9, 0xFF, 0xF9, 0xF0, 0xFC, 0x41,
|
|
||||||
0x61, 0xF0, 0xAA, 0x41, 0x6C, 0xF0, 0xA6, 0x21, 0x61, 0xFC, 0xA0, 0x04, 0x02, 0x21, 0x6E, 0xFD,
|
|
||||||
0xA1, 0x07, 0x73, 0x79, 0xFD, 0x21, 0x7A, 0xFB, 0x21, 0x63, 0xFD, 0x22, 0x74, 0x7A, 0xE4, 0xFD,
|
|
||||||
0xC5, 0x01, 0x61, 0x63, 0x64, 0x70, 0x72, 0x73, 0xF0, 0x8C, 0xF0, 0x8C, 0xFF, 0xDF, 0xFF, 0xE7,
|
|
||||||
0xFF, 0xFB, 0x41, 0x72, 0xEF, 0xB3, 0x41, 0x74, 0xEF, 0xAF, 0x43, 0x6B, 0x6D, 0x73, 0xFF, 0xF8,
|
|
||||||
0xEF, 0xAB, 0xFF, 0xFC, 0x41, 0x69, 0xEF, 0xA1, 0x43, 0x85, 0x87, 0x99, 0xEF, 0x9D, 0xEF, 0x9D,
|
|
||||||
0xEF, 0x9D, 0x41, 0x82, 0xEF, 0x93, 0x41, 0x6E, 0xEF, 0x8F, 0x48, 0xC4, 0xC5, 0x63, 0x65, 0x6C,
|
|
||||||
0x6D, 0x6F, 0x73, 0xFF, 0xEE, 0xFF, 0xF8, 0xEF, 0x8B, 0xFF, 0xFC, 0xEF, 0x8B, 0xEF, 0x8B, 0xEF,
|
|
||||||
0x8B, 0xEF, 0x8B, 0x21, 0x69, 0xE7, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x42, 0x6C, 0x6D, 0xFF,
|
|
||||||
0xFD, 0xEF, 0x69, 0x42, 0x65, 0x6F, 0xFF, 0xF9, 0xF0, 0x65, 0xA0, 0x0E, 0x23, 0x21, 0x72, 0xFD,
|
|
||||||
0xC5, 0x03, 0xA2, 0x61, 0x62, 0x65, 0x69, 0x77, 0xFF, 0xAA, 0xFF, 0xB4, 0xEF, 0x55, 0xFF, 0xF3,
|
|
||||||
0xFF, 0xFD, 0x41, 0x77, 0xE9, 0xD5, 0x21, 0xB3, 0xFC, 0xC1, 0x05, 0xC2, 0xC5, 0xF1, 0x2E, 0xDA,
|
|
||||||
0x07, 0x02, 0xC4, 0xC5, 0x6D, 0x6E, 0x72, 0x77, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x7A, 0xC3,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x70, 0x73, 0x74, 0xEE, 0xD1, 0xFD, 0xAF,
|
|
||||||
0xEF, 0x1A, 0xFD, 0xC0, 0xFE, 0x17, 0xFE, 0x4D, 0xFE, 0x5A, 0xFE, 0x7F, 0xFE, 0xB8, 0xFF, 0x2F,
|
|
||||||
0xFF, 0x71, 0xEA, 0x7E, 0xFF, 0xE1, 0xFF, 0xF7, 0xEC, 0xB2, 0xF0, 0x67, 0xFF, 0xFA, 0xEC, 0xB2,
|
|
||||||
0xEC, 0xB2, 0xEC, 0xB2, 0xEC, 0xB2, 0xEC, 0xB2, 0xEC, 0xB2, 0xEC, 0xB2, 0xF2, 0x8B, 0xEC, 0xB2,
|
|
||||||
0xC2, 0x02, 0x41, 0x64, 0x74, 0xE9, 0x39, 0xE9, 0x39, 0xC1, 0x02, 0x41, 0x72, 0xF2, 0x43, 0xA2,
|
|
||||||
0x01, 0xB2, 0x7A, 0x68, 0xF1, 0xFA, 0xA0, 0x0D, 0x94, 0x21, 0x73, 0xFD, 0x21, 0x6B, 0xFD, 0xC2,
|
|
||||||
0x01, 0xB2, 0x6C, 0x72, 0xE7, 0x71, 0xE7, 0x71, 0xC1, 0x01, 0xB2, 0x73, 0xE9, 0x4F, 0xC1, 0x03,
|
|
||||||
0xE2, 0x6B, 0xEA, 0xB7, 0xC2, 0x01, 0xB2, 0x70, 0x6B, 0xFF, 0xFA, 0xE7, 0x5C, 0xC3, 0x01, 0xB2,
|
|
||||||
0x63, 0x6F, 0x72, 0xE7, 0x4F, 0xED, 0x8D, 0xE9, 0x31, 0x41, 0xBC, 0xE9, 0xE4, 0x41, 0x99, 0xFE,
|
|
||||||
0xBD, 0xA0, 0x0E, 0xD2, 0xC4, 0x03, 0xE2, 0xC4, 0x77, 0x66, 0x6D, 0xFF, 0xF9, 0xFF, 0xFD, 0xEA,
|
|
||||||
0x91, 0xEA, 0x91, 0xC1, 0x04, 0x32, 0x75, 0xE9, 0xA8, 0x21, 0x7A, 0xFA, 0xC7, 0x01, 0xB2, 0xC5,
|
|
||||||
0x63, 0x66, 0x6E, 0x74, 0x6F, 0x7A, 0xFF, 0xDD, 0xE9, 0x0B, 0xE9, 0x0B, 0xE9, 0x0B, 0xFF, 0xE8,
|
|
||||||
0xFF, 0xFD, 0xE7, 0x24, 0xA0, 0x0E, 0x53, 0x41, 0x6D, 0xE8, 0xB2, 0x21, 0x6F, 0xFC, 0x4B, 0x64,
|
|
||||||
0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0xE8, 0xAB, 0xE8, 0xAB, 0xE8, 0xAB,
|
|
||||||
0xE8, 0xAB, 0xFF, 0xF6, 0xE8, 0xAB, 0xE8, 0xAB, 0xE8, 0xAB, 0xE8, 0xAB, 0xFF, 0xFD, 0xE8, 0xAB,
|
|
||||||
0xC4, 0x01, 0xB2, 0x74, 0x63, 0x6D, 0x7A, 0xFF, 0xDE, 0xE6, 0xDC, 0xE6, 0xE0, 0xE6, 0xE0, 0xA0,
|
|
||||||
0x0E, 0xF2, 0x41, 0x75, 0xE6, 0x41, 0xC3, 0x01, 0xB2, 0x6D, 0x61, 0x77, 0xFF, 0xF9, 0xFF, 0xFC,
|
|
||||||
0xE6, 0xCA, 0xA0, 0x0E, 0xB2, 0x42, 0xBA, 0x9B, 0xFF, 0xFD, 0xE7, 0x18, 0xC2, 0x02, 0x92, 0x68,
|
|
||||||
0x7A, 0xE7, 0x11, 0xE7, 0x11, 0x41, 0xBA, 0xF3, 0x20, 0x21, 0xC5, 0xFC, 0xCF, 0x09, 0x12, 0xC5,
|
|
||||||
0x6F, 0xC4, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x70, 0x73, 0x74, 0x7A, 0xFF, 0xE9,
|
|
||||||
0xED, 0x2D, 0xEF, 0xF2, 0xFF, 0xF0, 0xFF, 0xFD, 0xE7, 0x07, 0xE7, 0x07, 0xE7, 0x07, 0xE7, 0x07,
|
|
||||||
0xE7, 0x07, 0xE7, 0x07, 0xE7, 0x07, 0xE7, 0x07, 0xE7, 0x07, 0xEB, 0x0F, 0xC1, 0x01, 0x92, 0x72,
|
|
||||||
0xF0, 0x47, 0xC2, 0x01, 0x92, 0x7A, 0x68, 0xE5, 0xD6, 0xEA, 0xD9, 0xC1, 0x01, 0x92, 0x74, 0xEE,
|
|
||||||
0x23, 0xC2, 0x01, 0x92, 0x6D, 0x7A, 0xEA, 0xCA, 0xEA, 0xCA, 0xC2, 0x01, 0x92, 0x6D, 0x77, 0xE6,
|
|
||||||
0xB9, 0xEA, 0xC1, 0x51, 0x64, 0xC4, 0xC5, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70,
|
|
||||||
0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xA9, 0xED, 0xE1, 0xED, 0xE5, 0xFF, 0xD9, 0xFF, 0xDF, 0xFF,
|
|
||||||
0xE8, 0xE5, 0xC0, 0xE5, 0xC0, 0xE5, 0xC0, 0xE5, 0xC0, 0xE5, 0xC0, 0xE5, 0xC0, 0xEE, 0x24, 0xFF,
|
|
||||||
0xEE, 0xE5, 0xC0, 0xE5, 0xC0, 0xFF, 0xF7, 0x42, 0x6B, 0x77, 0xE6, 0x7C, 0xE6, 0x7C, 0x21, 0x65,
|
|
||||||
0xF9, 0x22, 0x61, 0x69, 0xC2, 0xFD, 0x53, 0xC5, 0x64, 0xC4, 0x62, 0x63, 0x65, 0x66, 0x67, 0x68,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6E, 0xFB, 0xB1, 0xFE, 0x29, 0xE7, 0xB0,
|
|
||||||
0xE7, 0xCA, 0xFE, 0x89, 0xFE, 0x96, 0xE5, 0x99, 0xE7, 0xEC, 0xE5, 0x99, 0xFE, 0x99, 0xFE, 0xA2,
|
|
||||||
0xFE, 0xAE, 0xFE, 0xB7, 0xFE, 0xE6, 0xFF, 0x2A, 0xE8, 0x30, 0xE5, 0x99, 0xFF, 0x40, 0xFF, 0xFB,
|
|
||||||
0x41, 0x75, 0xEA, 0xA1, 0x42, 0x6E, 0x7A, 0xFF, 0xFC, 0xEA, 0x9D, 0x41, 0x75, 0xE6, 0x42, 0x21,
|
|
||||||
0x6E, 0xFC, 0x21, 0x77, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0x43, 0x73, 0x77, 0x70, 0xEA,
|
|
||||||
0x86, 0xFF, 0xE9, 0xFF, 0xFD, 0x41, 0x6F, 0xFB, 0xC6, 0x21, 0x70, 0xFC, 0x21, 0x73, 0xFD, 0x21,
|
|
||||||
0x79, 0xFD, 0x21, 0x64, 0xFD, 0xC1, 0x01, 0x72, 0x6B, 0xE3, 0xDE, 0x44, 0x82, 0x9B, 0xBA, 0xBC,
|
|
||||||
0xFF, 0xFA, 0xE4, 0xFD, 0xE4, 0xFD, 0xE4, 0xFD, 0x43, 0x85, 0x99, 0x87, 0xEC, 0x11, 0xEC, 0x11,
|
|
||||||
0xE5, 0xE5, 0xA0, 0x0F, 0xB2, 0x21, 0x63, 0xFD, 0xA1, 0x04, 0x52, 0x79, 0xFD, 0x21, 0xBC, 0xFB,
|
|
||||||
0x21, 0xC5, 0xFD, 0xA1, 0x02, 0x72, 0x75, 0xFD, 0xA0, 0x0F, 0xD3, 0x44, 0xBA, 0x82, 0xBC, 0x9B,
|
|
||||||
0xFE, 0xA7, 0xFF, 0xF8, 0xFF, 0xFD, 0xE5, 0xC2, 0x41, 0x68, 0xE7, 0xD3, 0x21, 0x63, 0xFC, 0xA1,
|
|
||||||
0x02, 0x92, 0x75, 0xFD, 0xA0, 0x0F, 0x13, 0x21, 0x72, 0xFD, 0xA0, 0x10, 0x03, 0x21, 0x74, 0xFD,
|
|
||||||
0xA1, 0x0F, 0x13, 0x61, 0xFD, 0x21, 0x74, 0xFB, 0x21, 0x6F, 0xEC, 0xA3, 0x02, 0x92, 0x62, 0x73,
|
|
||||||
0x7A, 0xEC, 0xFA, 0xFD, 0x41, 0x74, 0xE7, 0xA7, 0x21, 0x61, 0xFC, 0x21, 0x6D, 0xFD, 0x41, 0x6B,
|
|
||||||
0xE7, 0x9D, 0x41, 0x6C, 0xE7, 0x99, 0xA0, 0x0F, 0x43, 0xA4, 0x02, 0x92, 0x61, 0x75, 0x79, 0x7A,
|
|
||||||
0xF2, 0xF5, 0xF9, 0xFD, 0xC1, 0x02, 0x92, 0x6D, 0xE7, 0x87, 0xA0, 0x10, 0x32, 0x21, 0x75, 0xFD,
|
|
||||||
0x21, 0x6B, 0xFD, 0xA1, 0x01, 0x71, 0x73, 0xFD, 0x41, 0x6D, 0xFF, 0x7A, 0x41, 0x6A, 0xFF, 0x76,
|
|
||||||
0x42, 0x72, 0x6F, 0xFF, 0x88, 0xFF, 0xFC, 0xC6, 0x01, 0x72, 0x61, 0x67, 0x69, 0x6A, 0x6C, 0x77,
|
|
||||||
0xFF, 0x6B, 0xFF, 0x81, 0xFF, 0xF1, 0xFF, 0x6B, 0xFF, 0x6B, 0xFF, 0xF9, 0x41, 0x6E, 0xE5, 0x37,
|
|
||||||
0x21, 0x6F, 0xFC, 0x41, 0x63, 0xE5, 0x30, 0x41, 0x70, 0xEF, 0xF5, 0x23, 0x67, 0x6B, 0x6C, 0xF5,
|
|
||||||
0xF8, 0xFC, 0x41, 0x7A, 0xE5, 0x21, 0x41, 0x72, 0xE5, 0x1D, 0x21, 0x65, 0xFC, 0x22, 0x67, 0x6D,
|
|
||||||
0xF5, 0xFD, 0xA0, 0x10, 0x73, 0x21, 0x77, 0xFD, 0x21, 0x99, 0xFD, 0x21, 0xC4, 0xFD, 0xC2, 0x02,
|
|
||||||
0x92, 0x69, 0x7A, 0xFF, 0xFD, 0xE4, 0xFF, 0xD9, 0x09, 0x12, 0xC4, 0xC5, 0x6D, 0x6F, 0x72, 0x75,
|
|
||||||
0x79, 0x7A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6E, 0x70,
|
|
||||||
0x73, 0x74, 0x77, 0xFF, 0x11, 0xFF, 0x34, 0xFF, 0x48, 0xFF, 0x64, 0xFF, 0x82, 0xFF, 0x8D, 0xFF,
|
|
||||||
0x9C, 0xFF, 0xB0, 0xFF, 0xD4, 0xE4, 0xFC, 0xED, 0xF8, 0xEE, 0x08, 0xFF, 0xE6, 0xE4, 0xFC, 0xF0,
|
|
||||||
0xB1, 0xE4, 0xFC, 0xE4, 0xFC, 0xE4, 0xFC, 0xE4, 0xFC, 0xE4, 0xFC, 0xE4, 0xFC, 0xE4, 0xFC, 0xFF,
|
|
||||||
0xF7, 0xE4, 0xFC, 0xE4, 0xFC, 0xC1, 0x00, 0x71, 0x7A, 0xE2, 0x8E, 0xA1, 0x01, 0x92, 0x72, 0xFA,
|
|
||||||
0xC1, 0x00, 0x71, 0x72, 0xF0, 0x54, 0xA0, 0x10, 0x52, 0x21, 0x65, 0xFD, 0xC3, 0x10, 0x32, 0x69,
|
|
||||||
0x61, 0x77, 0xFF, 0xFD, 0xE7, 0xD2, 0xE7, 0xD2, 0x21, 0x77, 0xF4, 0xC3, 0x01, 0x92, 0x68, 0x69,
|
|
||||||
0x7A, 0xFF, 0xE5, 0xFF, 0xFD, 0xE8, 0x90, 0xA0, 0x0F, 0x74, 0x21, 0x73, 0xFD, 0x21, 0x6B, 0xFD,
|
|
||||||
0xC2, 0x01, 0x92, 0x6C, 0x72, 0xE8, 0x7B, 0xE8, 0x7B, 0xC1, 0x01, 0x92, 0x6B, 0xE8, 0x72, 0xC3,
|
|
||||||
0x01, 0x92, 0x63, 0x6D, 0x7A, 0xFA, 0x87, 0xE8, 0x6C, 0xE8, 0x6C, 0xC1, 0x00, 0x71, 0x61, 0xE3,
|
|
||||||
0x90, 0xC2, 0x01, 0x92, 0x6B, 0x72, 0xE8, 0x5A, 0xFF, 0xFA, 0x54, 0xC5, 0x64, 0xC4, 0x62, 0x63,
|
|
||||||
0x65, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x75, 0x77, 0x7A, 0xFE,
|
|
||||||
0x51, 0xFF, 0x4D, 0xEB, 0x7A, 0xFF, 0xA1, 0xFF, 0xC1, 0xFF, 0xD3, 0xE3, 0x59, 0xFD, 0x72, 0xE3,
|
|
||||||
0x59, 0xFF, 0xD6, 0xE3, 0x59, 0xFF, 0xDF, 0xE3, 0x59, 0xFA, 0x70, 0xEB, 0xB4, 0xFF, 0xE5, 0xFF,
|
|
||||||
0xF7, 0xE3, 0x59, 0xE3, 0x59, 0xE3, 0x59, 0xC1, 0x01, 0x92, 0x72, 0xE8, 0x14, 0x52, 0xC4, 0xC5,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6F,
|
|
||||||
0xEB, 0x37, 0xEB, 0x3B, 0xFF, 0xFA, 0xEB, 0x48, 0xEB, 0x58, 0xE3, 0x16, 0xFD, 0x2F, 0xE3, 0x16,
|
|
||||||
0xE3, 0x16, 0xE3, 0x16, 0xFF, 0x9C, 0xFA, 0x2D, 0xEB, 0x71, 0xFA, 0x32, 0xFF, 0x9C, 0xE3, 0x16,
|
|
||||||
0xE3, 0x16, 0xEE, 0x5F, 0xC2, 0x00, 0x61, 0x65, 0x79, 0xFF, 0x86, 0xFF, 0xC9, 0xC3, 0x00, 0x51,
|
|
||||||
0x61, 0x65, 0x7A, 0xFD, 0xAE, 0xFD, 0xC5, 0xFF, 0xF7, 0xD9, 0x01, 0xF1, 0xC4, 0xC5, 0xC3, 0x62,
|
|
||||||
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x76, 0x77, 0x78, 0x7A, 0xE3, 0x30, 0xF7, 0xEE, 0xF8, 0x4F, 0xE3, 0x44, 0xF8, 0x52, 0xE3,
|
|
||||||
0x44, 0xF8, 0xB1, 0xE3, 0x44, 0xE3, 0x44, 0xE3, 0x44, 0xF8, 0xED, 0xE3, 0x44, 0xE3, 0x44, 0xE3,
|
|
||||||
0x44, 0xE3, 0x44, 0xE3, 0x44, 0xFD, 0x4D, 0xE3, 0x44, 0xFF, 0xF4, 0xE3, 0x44, 0xE3, 0x44, 0xE3,
|
|
||||||
0x44, 0xE3, 0x44, 0xE3, 0x44, 0xE3, 0x44, 0x41, 0x73, 0xFE, 0x03, 0x21, 0x6E, 0xFC, 0x21, 0x61,
|
|
||||||
0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x69, 0xF8, 0x07, 0x41, 0x65, 0xF8, 0x03, 0x21,
|
|
||||||
0x69, 0xFC, 0xA2, 0x02, 0x22, 0x67, 0x6E, 0xF5, 0xFD, 0xA0, 0x10, 0xA4, 0x21, 0x87, 0xFD, 0x24,
|
|
||||||
0x82, 0x9B, 0xBA, 0xBC, 0xFA, 0xFA, 0xFA, 0xFA, 0xA0, 0x10, 0xE4, 0xC2, 0x10, 0xE4, 0x68, 0x7A,
|
|
||||||
0xE7, 0x40, 0xE7, 0x40, 0xC2, 0x10, 0xE4, 0xC5, 0x7A, 0xEA, 0x7A, 0xE7, 0x37, 0xA0, 0x11, 0x24,
|
|
||||||
0xA1, 0x10, 0xE4, 0x7A, 0xFD, 0xC1, 0x10, 0xE4, 0x6F, 0xF7, 0xC9, 0xC1, 0x10, 0xE4, 0x63, 0xF9,
|
|
||||||
0x3B, 0xC2, 0x10, 0xE4, 0xC5, 0x7A, 0xEA, 0x79, 0xE7, 0x1A, 0xC2, 0x10, 0xE4, 0x63, 0x7A, 0xF9,
|
|
||||||
0x2C, 0xE7, 0x11, 0x21, 0x74, 0xDA, 0xD3, 0x02, 0x22, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67,
|
|
||||||
0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6A, 0x6E, 0xFF, 0xB6, 0xFF, 0xB9,
|
|
||||||
0xFF, 0xC2, 0xFF, 0xC5, 0xFF, 0xCE, 0xFF, 0xC2, 0xFF, 0xDA, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0xC2,
|
|
||||||
0xFF, 0xDF, 0xFF, 0xE5, 0xFF, 0xEB, 0xFF, 0xF4, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0xC2, 0xE2, 0x8A,
|
|
||||||
0xFF, 0xFD, 0x41, 0x9B, 0xE1, 0x9F, 0x41, 0x72, 0xED, 0x16, 0xC1, 0x02, 0x41, 0x72, 0xE1, 0x97,
|
|
||||||
0xCC, 0x02, 0x02, 0x61, 0x65, 0x75, 0xC5, 0x62, 0x64, 0x69, 0x6D, 0x6F, 0x70, 0x74, 0x77, 0xFF,
|
|
||||||
0x62, 0xFF, 0xB6, 0xE2, 0x40, 0xFF, 0xF2, 0xFF, 0xF6, 0xE2, 0x50, 0xE2, 0x50, 0xE2, 0x50, 0xE2,
|
|
||||||
0x50, 0xE4, 0xBC, 0xFF, 0xFA, 0xE2, 0x50, 0x21, 0x7A, 0xD9, 0xD7, 0x01, 0xF1, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74,
|
|
||||||
0x76, 0x77, 0x78, 0x7A, 0xE1, 0xFF, 0xE2, 0x03, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xFF, 0x2A,
|
|
||||||
0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13,
|
|
||||||
0xFF, 0xFD, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13, 0xE2, 0x13,
|
|
||||||
0xE2, 0x13, 0x41, 0x7A, 0xFC, 0xD8, 0x21, 0x6F, 0xFC, 0x45, 0x63, 0x6B, 0x70, 0x77, 0x72, 0xE1,
|
|
||||||
0x69, 0xE1, 0x4A, 0xE1, 0x4A, 0xE1, 0x4A, 0xFF, 0xFD, 0x21, 0x6F, 0xF0, 0x21, 0x6D, 0xFD, 0xC1,
|
|
||||||
0x03, 0x32, 0x73, 0xF7, 0x2C, 0x21, 0x6D, 0xFA, 0x42, 0x65, 0x6D, 0xFF, 0xFD, 0xF7, 0x38, 0x21,
|
|
||||||
0x64, 0xF9, 0x21, 0x65, 0xFD, 0xA0, 0x0E, 0x92, 0x21, 0x65, 0xFD, 0x21, 0xBC, 0xFD, 0x21, 0xC5,
|
|
||||||
0xFD, 0x21, 0x64, 0xFD, 0x21, 0x85, 0xFD, 0xC3, 0x00, 0x51, 0xC4, 0x6C, 0x72, 0xFF, 0xFD, 0xDF,
|
|
||||||
0x53, 0xDF, 0x53, 0x41, 0x62, 0xE7, 0xCB, 0xA0, 0x0D, 0x72, 0x21, 0x82, 0xFD, 0x21, 0xC5, 0xFD,
|
|
||||||
0x21, 0xB3, 0xFD, 0xC3, 0x06, 0x12, 0x7A, 0xC5, 0x64, 0xE8, 0x6A, 0xEA, 0xC4, 0xE8, 0x06, 0xC3,
|
|
||||||
0x05, 0xC2, 0x6E, 0x74, 0x7A, 0xED, 0x69, 0xED, 0x69, 0xE0, 0xB2, 0x51, 0x64, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xE8, 0xED,
|
|
||||||
0x59, 0xEA, 0x91, 0xE6, 0x36, 0xE9, 0xEB, 0xE6, 0x36, 0xE6, 0x36, 0xE6, 0x36, 0xE6, 0x36, 0xE6,
|
|
||||||
0x36, 0xE6, 0x36, 0xE6, 0x36, 0xFF, 0xF4, 0xE9, 0xFD, 0xE6, 0x36, 0xE6, 0x36, 0xE6, 0x36, 0xA2,
|
|
||||||
0x00, 0x51, 0xC3, 0x6F, 0xB1, 0xCC, 0xC5, 0x03, 0x32, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xDF, 0x6D,
|
|
||||||
0xDF, 0x6D, 0xDF, 0x6D, 0xDF, 0x6D, 0xDF, 0x6D, 0x21, 0x6F, 0xEE, 0x21, 0x65, 0xFD, 0x21, 0x72,
|
|
||||||
0xFD, 0xA1, 0x00, 0x51, 0x65, 0xFD, 0x41, 0x74, 0xE0, 0xFD, 0xC2, 0x02, 0x02, 0x69, 0x6F, 0xF7,
|
|
||||||
0x38, 0xFF, 0xFC, 0xA0, 0x11, 0x63, 0xA1, 0x04, 0x52, 0x72, 0xFD, 0x21, 0x74, 0xFB, 0x41, 0x6F,
|
|
||||||
0xE3, 0x6D, 0xA0, 0x11, 0x92, 0x21, 0x62, 0xFD, 0xA0, 0x11, 0xB2, 0x21, 0x74, 0xFD, 0x21, 0x75,
|
|
||||||
0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD, 0xC5, 0x04, 0x32, 0x61, 0x69, 0x7A,
|
|
||||||
0x65, 0x6F, 0xFF, 0xE1, 0xFF, 0xE4, 0xFF, 0xEB, 0xE5, 0x41, 0xFF, 0xFD, 0x21, 0x72, 0xEE, 0x21,
|
|
||||||
0x65, 0xFD, 0x22, 0x62, 0x70, 0xB8, 0xFD, 0xA0, 0x11, 0xD2, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD,
|
|
||||||
0xA1, 0x11, 0xF3, 0x73, 0xFD, 0x21, 0x87, 0xFB, 0x41, 0x9B, 0xEE, 0x63, 0x21, 0xC5, 0xFC, 0x21,
|
|
||||||
0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x22, 0xC4, 0x63, 0xF0, 0xFD, 0x21, 0x9B, 0xFB, 0x42, 0xC5, 0x73,
|
|
||||||
0xFF, 0xFD, 0xF4, 0x53, 0xA1, 0x00, 0x51, 0x65, 0xF9, 0xD9, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74,
|
|
||||||
0x75, 0x76, 0x77, 0x78, 0x7A, 0xE0, 0x60, 0xE0, 0x64, 0xFE, 0xC3, 0xE0, 0x74, 0xF5, 0x82, 0xE0,
|
|
||||||
0x74, 0xE0, 0x74, 0xE0, 0x74, 0xE0, 0x74, 0xFE, 0xD9, 0xE0, 0x74, 0xFE, 0xEE, 0xE0, 0x74, 0xE0,
|
|
||||||
0x74, 0xE0, 0x74, 0xFE, 0xFA, 0xFF, 0x56, 0xE0, 0x74, 0xE0, 0x74, 0xFF, 0x78, 0xFF, 0xC9, 0xE0,
|
|
||||||
0x74, 0xE0, 0x74, 0xE0, 0x74, 0xFF, 0xFB, 0xA0, 0x12, 0x22, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD,
|
|
||||||
0x21, 0x7A, 0xFD, 0x42, 0x6F, 0x72, 0xE4, 0x3A, 0xFF, 0xFD, 0x41, 0x61, 0xE9, 0x1C, 0x41, 0x7A,
|
|
||||||
0xE2, 0x9D, 0x21, 0x63, 0xFC, 0xC2, 0x04, 0x32, 0xC4, 0x65, 0xE2, 0x99, 0xFF, 0xFD, 0x21, 0x6A,
|
|
||||||
0xF7, 0x21, 0xB3, 0xFD, 0xC1, 0x04, 0x52, 0x75, 0xFA, 0x8E, 0x41, 0x65, 0xE2, 0x81, 0xC6, 0x04,
|
|
||||||
0x32, 0x65, 0x69, 0x79, 0x7A, 0x61, 0x6F, 0xFF, 0xF6, 0xFF, 0xFC, 0xE2, 0x7D, 0xE6, 0x8B, 0xE4,
|
|
||||||
0x6D, 0xE4, 0x6D, 0x21, 0x73, 0xEB, 0x21, 0x6E, 0xFD, 0x41, 0x68, 0xF5, 0x55, 0x21, 0x63, 0xFC,
|
|
||||||
0xA1, 0x00, 0x61, 0x65, 0xFD, 0xA3, 0x00, 0x51, 0xC3, 0x61, 0x7A, 0xCC, 0xF1, 0xFB, 0xA0, 0x12,
|
|
||||||
0x42, 0x21, 0x82, 0xFD, 0xA1, 0x02, 0xB1, 0xC5, 0xFD, 0xC3, 0x10, 0x32, 0x61, 0x65, 0x7A, 0xFB,
|
|
||||||
0x8D, 0xFB, 0x8D, 0xFF, 0xFB, 0x21, 0x63, 0xF4, 0x21, 0x85, 0xFD, 0x21, 0xC4, 0xFD, 0x21, 0x69,
|
|
||||||
0xFD, 0x21, 0x73, 0xFD, 0xD9, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
|
|
||||||
0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A,
|
|
||||||
0xDF, 0x85, 0xDF, 0x89, 0xFF, 0x7F, 0xDF, 0x99, 0xF4, 0xA7, 0xDF, 0x99, 0xE3, 0xBC, 0xDF, 0x99,
|
|
||||||
0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xFF, 0x86,
|
|
||||||
0xDF, 0x99, 0xFF, 0xD1, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xDF, 0x99, 0xFF, 0xFD,
|
|
||||||
0xDF, 0x99, 0xA0, 0x04, 0xC2, 0x44, 0x9B, 0x82, 0xBA, 0xBC, 0xF0, 0x76, 0xFF, 0xFD, 0xFF, 0xFD,
|
|
||||||
0xFF, 0xFD, 0xC1, 0x0C, 0xB2, 0x77, 0xDE, 0x8F, 0x21, 0x7A, 0xFA, 0xC2, 0x0B, 0xE2, 0x65, 0x72,
|
|
||||||
0xFF, 0xFD, 0xF0, 0x60, 0xC2, 0x0B, 0xE2, 0x68, 0x7A, 0xF0, 0x57, 0xF0, 0x57, 0xA0, 0x06, 0x72,
|
|
||||||
0x42, 0xBA, 0xBC, 0xDF, 0x30, 0xDF, 0x30, 0xC3, 0x0B, 0xE2, 0x6B, 0xC5, 0x7A, 0xFF, 0xF6, 0xFF,
|
|
||||||
0xF9, 0xF0, 0x44, 0xA1, 0x0B, 0xE2, 0x6E, 0xEA, 0x21, 0x6A, 0xE5, 0x21, 0x65, 0xFD, 0xC2, 0x0B,
|
|
||||||
0xE2, 0x6C, 0x72, 0xFF, 0xFD, 0xF0, 0x2D, 0xA2, 0x0B, 0xE2, 0x73, 0x74, 0xD6, 0xD6, 0x21, 0x72,
|
|
||||||
0xCF, 0xC2, 0x0B, 0xE2, 0x62, 0x6B, 0xFF, 0xFD, 0xF0, 0x1A, 0xA2, 0x0B, 0xE2, 0x63, 0x64, 0xC3,
|
|
||||||
0xC3, 0xA0, 0x12, 0x63, 0x21, 0x73, 0xFD, 0xC3, 0x06, 0x12, 0x61, 0xC5, 0x64, 0xE7, 0xDF, 0xE8,
|
|
||||||
0x30, 0xE5, 0x72, 0x51, 0x64, 0xC4, 0xC5, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x70,
|
|
||||||
0x72, 0x73, 0x74, 0x77, 0x7A, 0xFF, 0xF4, 0xEA, 0xD1, 0xE8, 0x09, 0xE3, 0xAE, 0xE7, 0x63, 0xE3,
|
|
||||||
0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xE7, 0x75, 0xE7,
|
|
||||||
0x75, 0xE3, 0xAE, 0xE3, 0xAE, 0xE3, 0xAE, 0xC3, 0x0B, 0xE2, 0x70, 0x63, 0x6F, 0xFF, 0xBD, 0xF2,
|
|
||||||
0xBB, 0xFF, 0xCC, 0x41, 0xBC, 0xDE, 0x9D, 0x41, 0x7A, 0xF2, 0x59, 0xC4, 0x0B, 0xE2, 0x73, 0xC5,
|
|
||||||
0x6F, 0x7A, 0xFF, 0x62, 0xFF, 0xF8, 0xFF, 0xFC, 0xEF, 0xB0, 0xA0, 0x12, 0x92, 0xA0, 0x05, 0x91,
|
|
||||||
0x23, 0x6E, 0x63, 0x6B, 0xFA, 0xFD, 0xFD, 0xC3, 0x0B, 0xE2, 0x74, 0x63, 0x7A, 0xFF, 0xF9, 0xF2,
|
|
||||||
0x8B, 0xEF, 0x94, 0xC2, 0x0B, 0xE2, 0x6B, 0x72, 0xEF, 0x88, 0xEF, 0x88, 0x41, 0x65, 0xFF, 0x31,
|
|
||||||
0x41, 0x77, 0xE3, 0x31, 0xA2, 0x0B, 0xE2, 0x62, 0x65, 0xF8, 0xFC, 0x52, 0xC4, 0xC5, 0x62, 0x63,
|
|
||||||
0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xEF, 0x73,
|
|
||||||
0xFE, 0xFA, 0xFF, 0x10, 0xFF, 0x19, 0xFF, 0x2C, 0xFF, 0x38, 0xF2, 0x0C, 0xF2, 0x0C, 0xFF, 0x43,
|
|
||||||
0xFF, 0x4C, 0xFF, 0x56, 0xFF, 0x5F, 0xFF, 0xAC, 0xFF, 0xC0, 0xFF, 0xDC, 0xFF, 0xE8, 0xF2, 0x0C,
|
|
||||||
0xFF, 0xF9, 0xC1, 0x03, 0xA2, 0x6E, 0xDF, 0xC7, 0x43, 0x82, 0x9B, 0xBC, 0xFF, 0xFA, 0xDF, 0xBB,
|
|
||||||
0xDF, 0xBB, 0xC1, 0x01, 0xB2, 0x77, 0xDF, 0xF5, 0xC1, 0x01, 0xB2, 0x6B, 0xDE, 0x08, 0xC2, 0x01,
|
|
||||||
0xB2, 0x64, 0x74, 0xDF, 0xE9, 0xDF, 0xE9, 0xC1, 0x01, 0xB2, 0x63, 0xDD, 0xF5, 0xC6, 0x01, 0xB2,
|
|
||||||
0x62, 0x64, 0x6E, 0x73, 0x74, 0x7A, 0xDF, 0xDA, 0xDF, 0xDA, 0xDF, 0xDA, 0xDF, 0xDA, 0xDF, 0xDA,
|
|
||||||
0xDD, 0xF3, 0xA0, 0x12, 0xB3, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0xC3, 0x01, 0xB2, 0x70, 0x74,
|
|
||||||
0x7A, 0xE6, 0x1C, 0xFF, 0xFD, 0xDD, 0xD5, 0xA0, 0x12, 0xE2, 0x21, 0x7A, 0xFD, 0x21, 0x72, 0xFD,
|
|
||||||
0x21, 0x74, 0xFD, 0x21, 0x85, 0xFD, 0x21, 0xC4, 0xFD, 0xA1, 0x01, 0xB2, 0x6E, 0xFD, 0x41, 0x72,
|
|
||||||
0xDF, 0x99, 0x41, 0x82, 0xDF, 0x95, 0x21, 0xC5, 0xFC, 0xA2, 0x01, 0xB2, 0x62, 0x67, 0xF5, 0xFD,
|
|
||||||
0x52, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x77, 0x7A, 0xDF, 0x46, 0xFF, 0x88, 0xDD, 0x2F, 0xDF, 0x65, 0xDF, 0x75, 0xDD, 0x2F, 0xDD,
|
|
||||||
0x2F, 0xDD, 0x2F, 0xDF, 0x8A, 0xFF, 0x92, 0xFF, 0x98, 0xFF, 0x9E, 0xFF, 0xA7, 0xFF, 0xAD, 0xFF,
|
|
||||||
0xCB, 0xFF, 0x98, 0xFF, 0xE9, 0xFF, 0xF9, 0x43, 0xC5, 0x64, 0x6B, 0xDD, 0xC9, 0xDD, 0xCC, 0xDD,
|
|
||||||
0xCC, 0x41, 0x73, 0xF1, 0x96, 0x22, 0x6F, 0x75, 0xF2, 0xFC, 0x21, 0x6C, 0xFB, 0x41, 0x6D, 0xDD,
|
|
||||||
0xD0, 0x21, 0x6F, 0xFC, 0x21, 0x7A, 0xFD, 0x21, 0x63, 0xFD, 0x22, 0x65, 0x6C, 0xF0, 0xFD, 0x41,
|
|
||||||
0x62, 0xDD, 0xC4, 0x21, 0x65, 0xFC, 0xA1, 0x00, 0x51, 0x69, 0xFD, 0x41, 0x77, 0xDC, 0xCA, 0x21,
|
|
||||||
0x62, 0xFC, 0xC4, 0x13, 0x02, 0x69, 0x6F, 0x75, 0x77, 0xE0, 0xCC, 0xFF, 0xFD, 0xE0, 0xCC, 0xE0,
|
|
||||||
0xCC, 0x21, 0x82, 0xF1, 0x21, 0xC5, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0xC3, 0xFD, 0xC2, 0x0D, 0x72,
|
|
||||||
0x6F, 0x77, 0xDB, 0x56, 0xDB, 0x56, 0x21, 0x68, 0xF7, 0x21, 0x63, 0xFD, 0x21, 0x65, 0xFD, 0xA2,
|
|
||||||
0x00, 0x51, 0x70, 0x7A, 0xEB, 0xFD, 0x41, 0x7A, 0xDE, 0xA3, 0xA1, 0x03, 0xA2, 0x73, 0xFC, 0x44,
|
|
||||||
0x82, 0x9B, 0xBA, 0xBC, 0xDE, 0x94, 0xDE, 0x94, 0xDE, 0x94, 0xFF, 0xFB, 0x41, 0x61, 0xDE, 0x8D,
|
|
||||||
0xA1, 0x02, 0x41, 0x68, 0xFC, 0xC2, 0x01, 0xB2, 0x7A, 0x68, 0xFF, 0xFB, 0xDC, 0xDB, 0xC3, 0x01,
|
|
||||||
0xB2, 0xC5, 0x72, 0x7A, 0xDE, 0xA0, 0xDC, 0xD2, 0xDC, 0xD2, 0xC2, 0x01, 0xB2, 0x6C, 0x72, 0xDC,
|
|
||||||
0xC6, 0xDE, 0x80, 0xA0, 0x13, 0x23, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD, 0xC2, 0x01, 0xB2, 0xC5,
|
|
||||||
0x7A, 0xDE, 0xBE, 0xDC, 0xB4, 0xC3, 0x01, 0xB2, 0x63, 0x6B, 0x72, 0xDC, 0xA7, 0xDC, 0xAB, 0xDC,
|
|
||||||
0xAB, 0xA0, 0x06, 0x13, 0x21, 0x73, 0xFD, 0x21, 0x6B, 0xFD, 0x41, 0x6F, 0xED, 0xB1, 0x21, 0x7A,
|
|
||||||
0xFC, 0x54, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, 0x72,
|
|
||||||
0x73, 0x74, 0x77, 0x7A, 0x65, 0x69, 0xDE, 0x35, 0xFF, 0x9E, 0xDE, 0x4F, 0xFF, 0xB4, 0xFF, 0xBD,
|
|
||||||
0xDC, 0x1E, 0xDE, 0x71, 0xDC, 0x1E, 0xFF, 0xC9, 0xDC, 0x1E, 0xFE, 0x87, 0xFF, 0xD8, 0xDE, 0x90,
|
|
||||||
0xFF, 0xDB, 0xDE, 0xA9, 0xFF, 0xE4, 0xDC, 0x1E, 0xDC, 0x1E, 0xFF, 0xF6, 0xFF, 0xFD, 0xD9, 0x01,
|
|
||||||
0xF1, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xDC, 0x2B, 0xDC, 0x2F, 0xDC, 0x3F,
|
|
||||||
0xDC, 0x3F, 0xDC, 0x3F, 0xFE, 0xB2, 0xDC, 0x3F, 0xDC, 0x3F, 0xDC, 0x3F, 0xFF, 0x0C, 0xDC, 0x3F,
|
|
||||||
0xDC, 0x3F, 0xDC, 0x3F, 0xDC, 0x3F, 0xFF, 0x18, 0xE2, 0x93, 0xDC, 0x3F, 0xDC, 0x3F, 0xFF, 0x51,
|
|
||||||
0xDC, 0x3F, 0xDC, 0x3F, 0xDC, 0x3F, 0xDC, 0x3F, 0xFF, 0xC3, 0xDC, 0x3F, 0xC1, 0x00, 0x61, 0x6F,
|
|
||||||
0xE2, 0x3E, 0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFF, 0xFA, 0xD9, 0xB8, 0xD9, 0xB8, 0xD9, 0xB8,
|
|
||||||
0xD9, 0xB8, 0xA0, 0x13, 0x51, 0xA1, 0x13, 0x62, 0x75, 0xFD, 0x21, 0x87, 0xFB, 0x21, 0xC4, 0xFD,
|
|
||||||
0x21, 0x9B, 0xFD, 0x21, 0xC5, 0xFD, 0x41, 0x6F, 0xE7, 0x58, 0x21, 0x70, 0xFC, 0x21, 0x73, 0xFD,
|
|
||||||
0xC5, 0x01, 0xB2, 0xC5, 0x6F, 0x72, 0x79, 0x7A, 0xDD, 0x9E, 0xFF, 0xF3, 0xDB, 0xD0, 0xFF, 0xFD,
|
|
||||||
0xDB, 0xD0, 0xC3, 0x01, 0xB2, 0x74, 0x6C, 0x72, 0xDD, 0xA5, 0xDB, 0xBE, 0xDD, 0x78, 0xC3, 0x01,
|
|
||||||
0xB2, 0x67, 0x6B, 0x74, 0xDD, 0x99, 0xDD, 0x99, 0xDD, 0x99, 0x42, 0x72, 0x73, 0xFE, 0xE9, 0xFE,
|
|
||||||
0xE9, 0x21, 0x62, 0xF9, 0x41, 0x68, 0xDD, 0x83, 0xC3, 0x01, 0xB2, 0x63, 0xC5, 0x7A, 0xFF, 0xFC,
|
|
||||||
0xDD, 0xA2, 0xDB, 0x98, 0x41, 0x6F, 0xDA, 0xAF, 0xA1, 0x01, 0xB2, 0x74, 0xFC, 0xA0, 0x08, 0x41,
|
|
||||||
0x21, 0x63, 0xFD, 0xA1, 0x0C, 0xB2, 0x69, 0xFD, 0x42, 0x6E, 0x7A, 0xFF, 0xFB, 0xFE, 0xE2, 0x41,
|
|
||||||
0x64, 0xDB, 0xD4, 0x21, 0x65, 0xFC, 0x21, 0x69, 0xFD, 0x55, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66,
|
|
||||||
0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x77, 0x7A, 0x69, 0x6E, 0xDD,
|
|
||||||
0x0D, 0xE6, 0x77, 0xDD, 0x27, 0xDD, 0x2C, 0xFF, 0x97, 0xDA, 0xF6, 0xDD, 0x49, 0xDA, 0xF6, 0xFF,
|
|
||||||
0xA9, 0xFF, 0xB5, 0xDD, 0x63, 0xFF, 0xC8, 0xDA, 0xF6, 0xFF, 0xCF, 0xDD, 0x81, 0xDD, 0x8D, 0xFF,
|
|
||||||
0xDF, 0xDA, 0xF6, 0xDA, 0xF6, 0xFF, 0xEF, 0xFF, 0xFD, 0xA0, 0x13, 0x82, 0x21, 0x75, 0xFD, 0x21,
|
|
||||||
0x77, 0xFD, 0x22, 0x6C, 0x72, 0xF7, 0xF7, 0xA3, 0x0C, 0xB2, 0x61, 0x65, 0x79, 0xF8, 0xFB, 0xF2,
|
|
||||||
0x21, 0x7A, 0xF7, 0x41, 0x74, 0xEE, 0xCD, 0x21, 0x6E, 0xFC, 0x41, 0x64, 0xEE, 0xC6, 0x21, 0x65,
|
|
||||||
0xFC, 0x41, 0x6C, 0xEE, 0xBF, 0x21, 0x61, 0xFC, 0x23, 0x6F, 0x72, 0x77, 0xEF, 0xF6, 0xFD, 0x21,
|
|
||||||
0x6B, 0xF9, 0x21, 0x73, 0xFD, 0xA2, 0x00, 0x51, 0x65, 0x79, 0xDB, 0xFD, 0xC1, 0x01, 0xB2, 0x6C,
|
|
||||||
0xDA, 0xE4, 0xC3, 0x01, 0xB2, 0x6B, 0xC5, 0x7A, 0xDC, 0xC5, 0xDC, 0xE8, 0xDA, 0xDE, 0xC3, 0x01,
|
|
||||||
0xB2, 0x63, 0x6D, 0x7A, 0xDA, 0xCE, 0xFF, 0x4F, 0xDA, 0xD2, 0xA0, 0x13, 0xA5, 0x21, 0x73, 0xFD,
|
|
||||||
0x42, 0x65, 0x70, 0xFF, 0xFD, 0xDC, 0xA7, 0x21, 0x6C, 0xF3, 0x21, 0x65, 0xFD, 0x22, 0x6D, 0x73,
|
|
||||||
0xF3, 0xFD, 0x41, 0x70, 0xDC, 0x95, 0x21, 0x61, 0xFC, 0xC3, 0x01, 0xB2, 0x6B, 0x65, 0x68, 0xDC,
|
|
||||||
0x4A, 0xFF, 0xF4, 0xFF, 0xFD, 0x51, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xDC, 0x41, 0xE5, 0xAB, 0xDA, 0x2A, 0xDC, 0x60, 0xDC,
|
|
||||||
0x70, 0xDA, 0x2A, 0xDA, 0x2A, 0xDA, 0x2A, 0xFF, 0xB7, 0xDA, 0x2A, 0xDA, 0x2A, 0xFC, 0xA2, 0xFF,
|
|
||||||
0xBD, 0xFF, 0xC9, 0xFF, 0xF4, 0xDA, 0x2A, 0xDA, 0x2A, 0x41, 0x6B, 0xEE, 0x58, 0x21, 0x6F, 0xFC,
|
|
||||||
0x21, 0x6E, 0xFD, 0x21, 0x6D, 0xFD, 0xC1, 0x13, 0xF2, 0x77, 0xE7, 0xE4, 0x21, 0x68, 0xFA, 0x21,
|
|
||||||
0x63, 0xFD, 0x21, 0x79, 0xFD, 0x21, 0x77, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x72, 0xFD, 0xA1, 0x00,
|
|
||||||
0x51, 0x61, 0xFD, 0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9,
|
|
||||||
0xA5, 0xD9, 0xA5, 0xA0, 0x14, 0x12, 0x42, 0xBA, 0xBC, 0xFF, 0xFD, 0xD8, 0x6D, 0xC2, 0x01, 0x92,
|
|
||||||
0xC5, 0x7A, 0xFF, 0xF9, 0xDE, 0x8E, 0xC1, 0x01, 0x92, 0x63, 0xE1, 0xD8, 0x52, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0xE1,
|
|
||||||
0xA8, 0xFF, 0xD7, 0xD9, 0x87, 0xE1, 0xB9, 0xFF, 0xF1, 0xD9, 0x87, 0xD9, 0x87, 0xD9, 0x87, 0xD9,
|
|
||||||
0x87, 0xD9, 0x87, 0xFF, 0xFA, 0xD9, 0x87, 0xD9, 0x87, 0xE1, 0xEB, 0xE1, 0xEB, 0xD9, 0x87, 0xD9,
|
|
||||||
0x87, 0xD9, 0x87, 0x21, 0x65, 0xC9, 0xA1, 0x00, 0x51, 0x69, 0xFD, 0xC1, 0x0C, 0xB2, 0x75, 0xFE,
|
|
||||||
0xAE, 0x21, 0x7A, 0xFA, 0xA1, 0x00, 0x51, 0x6F, 0xFD, 0xD9, 0x01, 0xF1, 0xC4, 0xC5, 0x61, 0x62,
|
|
||||||
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x76, 0x77, 0x78, 0x7A, 0xD9, 0xA0, 0xFD, 0xC9, 0xFE, 0x60, 0xD9, 0xB4, 0xD9, 0xB4, 0xFE,
|
|
||||||
0xDC, 0xFF, 0x2C, 0xD9, 0xB4, 0xD9, 0xB4, 0xD9, 0xB4, 0xFF, 0x6A, 0xD9, 0xB4, 0xD9, 0xB4, 0xD9,
|
|
||||||
0xB4, 0xFF, 0x85, 0xFF, 0xED, 0xDD, 0xD7, 0xD9, 0xB4, 0xFF, 0xFB, 0xD9, 0xB4, 0xD9, 0xB4, 0xD9,
|
|
||||||
0xB4, 0xD9, 0xB4, 0xD9, 0xB4, 0xD9, 0xB4, 0x5A, 0xC4, 0xC5, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
|
||||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
|
|
||||||
0x78, 0x7A, 0xD7, 0x77, 0xD8, 0x78, 0xD9, 0x3F, 0xD9, 0xA7, 0xDA, 0x7B, 0xDC, 0x80, 0xDD, 0x34,
|
|
||||||
0xDD, 0x44, 0xDD, 0x9C, 0xDD, 0xFC, 0xDE, 0x6F, 0xDE, 0x83, 0xDF, 0x6B, 0xDF, 0xBD, 0xE0, 0x1E,
|
|
||||||
0xEA, 0x49, 0xED, 0xC7, 0xF6, 0x22, 0xF7, 0x53, 0xF8, 0xF2, 0xF9, 0xCD, 0xFB, 0x24, 0xDD, 0x44,
|
|
||||||
0xFD, 0x27, 0xDD, 0x44, 0xFF, 0xB2, 0xA0, 0x14, 0x31, 0xA0, 0x15, 0x81, 0xA1, 0x14, 0x62, 0x2E,
|
|
||||||
0xFD, 0x21, 0x2E, 0xF8, 0x25, 0x84, 0x9B, 0xBA, 0xBC, 0x82, 0xF8, 0xF8, 0xF8, 0xF8, 0xFD, 0xA1,
|
|
||||||
0x15, 0x42, 0x2E, 0xEA, 0x21, 0x87, 0xFB, 0x36, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6D,
|
|
||||||
0x6E, 0x70, 0x73, 0x74, 0x7A, 0xC4, 0x2E, 0x68, 0x6A, 0x6C, 0x72, 0x76, 0x77, 0x78, 0xED, 0xE5,
|
|
||||||
0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xFD, 0xE2, 0xEA, 0xEA, 0xEA,
|
|
||||||
0xEA, 0xEA, 0xEA, 0xEA, 0x43, 0x85, 0x99, 0x87, 0xFF, 0xB2, 0xFF, 0xB2, 0xFF, 0xD3, 0x41, 0x7A,
|
|
||||||
0xF9, 0x9F, 0x21, 0x63, 0xFC, 0xA1, 0x14, 0x31, 0x77, 0xFD, 0x21, 0xB3, 0xFB, 0xA0, 0x14, 0x42,
|
|
||||||
0xC2, 0x14, 0x31, 0x69, 0x2E, 0xFF, 0xFD, 0xFF, 0x99, 0xA0, 0x14, 0x61, 0x42, 0x63, 0x2E, 0xFF,
|
|
||||||
0xFD, 0xFF, 0x8D, 0x21, 0x87, 0xF9, 0x45, 0x9B, 0xBA, 0xBC, 0x82, 0x84, 0xFF, 0x86, 0xFF, 0x86,
|
|
||||||
0xFF, 0x86, 0xFF, 0x8B, 0xFF, 0x8B, 0x43, 0x6D, 0x6E, 0x2E, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0x73,
|
|
||||||
0x56, 0x63, 0xC4, 0xC5, 0x62, 0x64, 0x66, 0x67, 0x6B, 0x70, 0x73, 0x74, 0x7A, 0x6C, 0x2E, 0x68,
|
|
||||||
0x6A, 0x6D, 0x6E, 0x72, 0x76, 0x77, 0x78, 0xFF, 0xD0, 0xFF, 0xE3, 0xFF, 0xE6, 0xFF, 0x6C, 0xFF,
|
|
||||||
0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF, 0x6C, 0xFF,
|
|
||||||
0xF6, 0xFF, 0x69, 0xFF, 0x71, 0xFF, 0x71, 0xFF, 0x71, 0xFF, 0x71, 0xFF, 0x71, 0xFF, 0x71, 0xFF,
|
|
||||||
0x71, 0xFF, 0x71, 0xA0, 0x00, 0xF1, 0x21, 0xBA, 0xFD, 0xC2, 0x14, 0x62, 0xC5, 0x2E, 0xFF, 0xFD,
|
|
||||||
0xFF, 0x20, 0x41, 0x87, 0xFF, 0x1A, 0x45, 0x9B, 0xBC, 0xBA, 0x82, 0x84, 0xFF, 0x16, 0xFF, 0x16,
|
|
||||||
0xFF, 0x29, 0xFF, 0x1B, 0xFF, 0x1B, 0x56, 0x64, 0xC4, 0xC5, 0x62, 0x63, 0x66, 0x67, 0x6B, 0x6C,
|
|
||||||
0x6D, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x7A, 0x2E, 0x68, 0x6A, 0x72, 0x76, 0x78, 0xFF, 0xE3, 0xFF,
|
|
||||||
0xEC, 0xFF, 0xF0, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF,
|
|
||||||
0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x06, 0xFF, 0x03, 0xFF,
|
|
||||||
0x0B, 0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0x0B, 0xFF, 0x0B, 0x45, 0x84, 0x9B, 0xBA, 0xBC, 0x82, 0xFE,
|
|
||||||
0xC3, 0xFE, 0xC3, 0xFE, 0xC3, 0xFE, 0xC3, 0xFE, 0xD6, 0x56, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66,
|
|
||||||
0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x2E, 0x76, 0x78,
|
|
||||||
0xFF, 0x99, 0xFF, 0xF0, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3,
|
|
||||||
0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3,
|
|
||||||
0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB3, 0xFE, 0xB0, 0xFE, 0xB8, 0xFE, 0xB8, 0x45, 0x82, 0x84, 0x9B,
|
|
||||||
0xBA, 0xBC, 0xFE, 0x70, 0xFE, 0x70, 0xFE, 0x70, 0xFE, 0x70, 0xFE, 0x70, 0x56, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A,
|
|
||||||
0x2E, 0x76, 0x78, 0xFF, 0x46, 0xFF, 0xF0, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE,
|
|
||||||
0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE,
|
|
||||||
0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x60, 0xFE, 0x5D, 0xFE, 0x65, 0xFE, 0x65, 0x45,
|
|
||||||
0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xFE, 0x1D, 0xFE, 0x1D, 0xFE, 0x1D, 0xFE, 0x1D, 0xFE, 0x30, 0x56,
|
|
||||||
0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74,
|
|
||||||
0x77, 0x7A, 0x2E, 0x68, 0x76, 0x78, 0xFE, 0xF3, 0xFF, 0xF0, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D,
|
|
||||||
0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D,
|
|
||||||
0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0D, 0xFE, 0x0A, 0xFE, 0x12, 0xFE, 0x12,
|
|
||||||
0xFE, 0x12, 0x45, 0x9B, 0xBA, 0x82, 0x84, 0xBC, 0xFE, 0x5E, 0xFE, 0xC4, 0xFF, 0x17, 0xFF, 0x6A,
|
|
||||||
0xFF, 0xBD, 0x42, 0x6B, 0x2E, 0xFE, 0x27, 0xFD, 0xB7, 0xC1, 0x14, 0x82, 0x2E, 0xFD, 0xB0, 0x25,
|
|
||||||
0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xF3, 0xFA, 0xFA, 0xFA, 0xFA, 0x21, 0x87, 0xEF, 0xA0, 0x16, 0x43,
|
|
||||||
0x21, 0x79, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x77, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x61, 0xFD, 0x45,
|
|
||||||
0x6E, 0x2E, 0x7A, 0x64, 0x6F, 0xFD, 0xFA, 0xFD, 0x8A, 0xFD, 0x92, 0xFE, 0x64, 0xFF, 0xFD, 0xC1,
|
|
||||||
0x15, 0x62, 0x2E, 0xFD, 0x7A, 0xA0, 0x16, 0x22, 0x21, 0x7A, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x61,
|
|
||||||
0xFD, 0x21, 0x68, 0xFD, 0x21, 0x6B, 0xFD, 0x42, 0x2E, 0x6F, 0xFD, 0x62, 0xFF, 0xFD, 0x41, 0x68,
|
|
||||||
0xFE, 0x35, 0x21, 0x74, 0xFC, 0xA0, 0x15, 0x93, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x64,
|
|
||||||
0xD9, 0xF0, 0x21, 0x6E, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x68, 0xFD, 0x23, 0x65, 0x66, 0x6B, 0xE7,
|
|
||||||
0xF0, 0xFD, 0xA0, 0x04, 0xD3, 0x21, 0x75, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x6F, 0xFD, 0xA0, 0x0B,
|
|
||||||
0xB2, 0x21, 0x70, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x7A, 0xFD, 0xA0, 0x15, 0xC4,
|
|
||||||
0xA1, 0x01, 0x22, 0x6D, 0xFD, 0x21, 0x73, 0xFB, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x16,
|
|
||||||
0x02, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6E, 0xFD,
|
|
||||||
0x21, 0x69, 0xFD, 0x21, 0x6D, 0xFD, 0x24, 0x6E, 0x73, 0x7A, 0x72, 0xC5, 0xD4, 0xE5, 0xFD, 0x41,
|
|
||||||
0x61, 0xDB, 0x01, 0x21, 0x6D, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x65, 0xB5, 0x21, 0x6D, 0xFD, 0xA0,
|
|
||||||
0x13, 0x02, 0xA1, 0x00, 0x71, 0x6D, 0xFD, 0x21, 0x73, 0xFB, 0x21, 0x73, 0xFD, 0x21, 0x65, 0xFD,
|
|
||||||
0x21, 0x6E, 0xFD, 0x22, 0x7A, 0x69, 0xE9, 0xFD, 0xA0, 0x16, 0x72, 0x21, 0x6E, 0xFD, 0x21, 0x61,
|
|
||||||
0xFD, 0x21, 0x70, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD, 0x22, 0x73, 0x6B, 0xE9, 0xFD, 0x5A,
|
|
||||||
0xC5, 0xC4, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x7A, 0x72, 0x62, 0x2E,
|
|
||||||
0x68, 0x6A, 0x6C, 0x76, 0x77, 0x78, 0x65, 0x69, 0x6F, 0x75, 0xFF, 0x00, 0xFF, 0x0B, 0xFE, 0xFA,
|
|
||||||
0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA, 0xFE, 0xFA,
|
|
||||||
0xFE, 0xFA, 0xFE, 0xFA, 0xFF, 0x20, 0xFF, 0x30, 0xFC, 0xAA, 0xFC, 0xB2, 0xFC, 0xB2, 0xFF, 0x48,
|
|
||||||
0xFC, 0xB2, 0xFC, 0xB2, 0xFC, 0xB2, 0xFF, 0x6C, 0xFF, 0xB7, 0xFF, 0xC7, 0xFF, 0xFB, 0x45, 0x84,
|
|
||||||
0x9B, 0xBA, 0xBC, 0x82, 0xFE, 0xAB, 0xFE, 0xAB, 0xFE, 0xAB, 0xFE, 0xAB, 0xFC, 0x63, 0xA0, 0x14,
|
|
||||||
0x62, 0x21, 0x87, 0xFD, 0xC1, 0x00, 0xF1, 0x2E, 0xFC, 0x45, 0x25, 0x84, 0x9B, 0xBA, 0xBC, 0x82,
|
|
||||||
0xF4, 0xF4, 0xF4, 0xF4, 0xFA, 0xC1, 0x00, 0xF1, 0x7A, 0xFC, 0x3C, 0xA0, 0x16, 0x91, 0x21, 0x74,
|
|
||||||
0xFD, 0x21, 0x73, 0xFD, 0xD4, 0x02, 0x31, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6D,
|
|
||||||
0x6E, 0x70, 0x73, 0x74, 0x7A, 0x2E, 0x72, 0x77, 0x6A, 0x6C, 0x75, 0xFF, 0xDD, 0xFF, 0xE6, 0xFF,
|
|
||||||
0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF,
|
|
||||||
0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFF, 0xDA, 0xFC, 0x25, 0xFF, 0xF1, 0xFF, 0xE0, 0xFC, 0xFF, 0xFC,
|
|
||||||
0xFF, 0xFF, 0xFD, 0x44, 0x84, 0x9B, 0xBA, 0xBC, 0xFF, 0x9B, 0xFF, 0x9B, 0xFF, 0x9B, 0xFF, 0x9B,
|
|
||||||
0xD0, 0x02, 0x31, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73,
|
|
||||||
0x74, 0x7A, 0x2E, 0xFF, 0x91, 0xFF, 0xF3, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF,
|
|
||||||
0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFF, 0x8E, 0xFB, 0xDC, 0xFF,
|
|
||||||
0x8E, 0xFB, 0xD9, 0x41, 0x6C, 0xFE, 0x32, 0x21, 0x6C, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x77, 0xFD,
|
|
||||||
0x21, 0x64, 0xFD, 0x21, 0x6C, 0xFD, 0xA0, 0x16, 0xA2, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x21,
|
|
||||||
0x7A, 0xFD, 0x21, 0x72, 0xFD, 0x58, 0xC4, 0xC5, 0x62, 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x70, 0x73, 0x74, 0x68, 0x7A, 0x63, 0x2E, 0x6A, 0x72, 0x76, 0x77, 0x78, 0x61, 0x75, 0xFD, 0xE5,
|
|
||||||
0xFF, 0x29, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4,
|
|
||||||
0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFD, 0xD4, 0xFF, 0x5F, 0xFF, 0xAB, 0xFE, 0x0A, 0xFB, 0x84,
|
|
||||||
0xFB, 0x8C, 0xFB, 0x8C, 0xFB, 0x8C, 0xFB, 0x8C, 0xFB, 0x8C, 0xFF, 0xEE, 0xFF, 0xFD, 0x41, 0x7A,
|
|
||||||
0xFB, 0xAB, 0x43, 0x62, 0x73, 0x2E, 0xFB, 0xA7, 0xFF, 0xFC, 0xFB, 0x37, 0xA0, 0x14, 0xA2, 0x21,
|
|
||||||
0x87, 0xFD, 0x24, 0x84, 0x9B, 0xBA, 0xBC, 0xFA, 0xFA, 0xFA, 0xFA, 0xCF, 0x02, 0x31, 0xC4, 0xC5,
|
|
||||||
0x62, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x7A, 0x2E, 0xFF, 0xF4, 0xFF,
|
|
||||||
0xF7, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF,
|
|
||||||
0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, 0xFB, 0x1E, 0x45, 0x84, 0x9B, 0xBA, 0xBC,
|
|
||||||
0x82, 0xFF, 0xC1, 0xFF, 0xC1, 0xFF, 0xC1, 0xFF, 0xC1, 0xE7, 0xF3, 0x42, 0x62, 0x6D, 0xDF, 0xFD,
|
|
||||||
0xDF, 0xFD, 0x21, 0x7A, 0xF9, 0xD4, 0x02, 0x31, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6B,
|
|
||||||
0x6D, 0x6E, 0x70, 0x73, 0x74, 0x7A, 0x2E, 0x6A, 0x6C, 0x72, 0x77, 0x65, 0xFF, 0xAA, 0xFF, 0xE6,
|
|
||||||
0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7,
|
|
||||||
0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFF, 0xA7, 0xFA, 0xD4, 0xE7, 0xD9, 0xE7, 0xD9, 0xE7, 0xD9,
|
|
||||||
0xE7, 0xD9, 0xFF, 0xFD, 0x45, 0x82, 0xBA, 0xBC, 0x84, 0x9B, 0xFF, 0x5E, 0xFF, 0x77, 0xFF, 0xC1,
|
|
||||||
0xFC, 0xE5, 0xFC, 0xE5, 0xA0, 0x14, 0xC2, 0x21, 0x77, 0xFD, 0x21, 0x6F, 0xFD, 0xC2, 0x14, 0x82,
|
|
||||||
0x69, 0x2E, 0xFF, 0xFD, 0xFA, 0x7C, 0x42, 0x2E, 0x77, 0xFA, 0x73, 0xE7, 0x78, 0x43, 0x6E, 0x2E,
|
|
||||||
0x7A, 0xFA, 0xDC, 0xFA, 0x6C, 0xFF, 0xF9, 0xD0, 0x02, 0x31, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66,
|
|
||||||
0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x7A, 0x2E, 0xFE, 0x1A, 0xFE, 0x7C, 0xFE, 0x17,
|
|
||||||
0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17,
|
|
||||||
0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFE, 0x17, 0xFA, 0x62, 0x41, 0x64, 0xDF, 0x60, 0x21, 0x6E,
|
|
||||||
0xFC, 0x21, 0x61, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD,
|
|
||||||
0x21, 0x74, 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x74, 0xFC, 0xE8, 0x21, 0x73, 0xFC, 0x21, 0x61, 0xFD,
|
|
||||||
0x21, 0x70, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD, 0x58, 0xC5, 0xC4, 0x62, 0x63, 0x66, 0x67,
|
|
||||||
0x6B, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x72, 0x7A, 0x64, 0x2E, 0x68, 0x6A, 0x6C, 0x76, 0x77, 0x78,
|
|
||||||
0x65, 0x75, 0xFF, 0x6B, 0xFC, 0x61, 0xFC, 0x50, 0xFC, 0x50, 0xFC, 0x50, 0xFC, 0x50, 0xFC, 0x50,
|
|
||||||
0xFC, 0x50, 0xFF, 0x84, 0xFC, 0x50, 0xFC, 0x50, 0xFC, 0x50, 0xFF, 0x94, 0xFF, 0x9E, 0xFC, 0x86,
|
|
||||||
0xFA, 0x00, 0xFA, 0x08, 0xFA, 0x08, 0xFA, 0x08, 0xFA, 0x08, 0xFA, 0x08, 0xFA, 0x08, 0xFF, 0xEA,
|
|
||||||
0xFF, 0xFD, 0x41, 0x87, 0xF9, 0xBF, 0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC, 0xF9, 0xBB, 0xF9, 0xBB,
|
|
||||||
0xF9, 0xBB, 0xF9, 0xBB, 0xF9, 0xBB, 0x41, 0x6D, 0xD7, 0xBA, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD,
|
|
||||||
0x41, 0x6E, 0xFC, 0x6E, 0x21, 0x69, 0xFC, 0x21, 0x62, 0xFD, 0x22, 0x68, 0x7A, 0xF3, 0xFD, 0x21,
|
|
||||||
0x73, 0xFB, 0xA0, 0x16, 0xC2, 0x21, 0x63, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x7A, 0xFD, 0x41, 0x72,
|
|
||||||
0xFC, 0x07, 0x21, 0x6F, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x6B, 0xFD, 0xA0, 0x16, 0xE3, 0x21, 0x74,
|
|
||||||
0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x66, 0xFD, 0x44, 0x6B, 0x72,
|
|
||||||
0x6C, 0x73, 0xD1, 0x98, 0xFF, 0xDE, 0xFF, 0xEB, 0xFF, 0xFD, 0x58, 0x63, 0x6B, 0x6D, 0x6E, 0x66,
|
|
||||||
0x2E, 0xC4, 0xC5, 0x62, 0x64, 0x67, 0x68, 0x6A, 0x6C, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78,
|
|
||||||
0x7A, 0x69, 0x6F, 0xFB, 0x9F, 0xFB, 0x9F, 0xFB, 0x9F, 0xFB, 0x9F, 0xFB, 0xD5, 0xF9, 0x4F, 0xFF,
|
|
||||||
0x98, 0xFF, 0x9C, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9,
|
|
||||||
0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xF9, 0x57, 0xFF,
|
|
||||||
0xC5, 0xFF, 0xF3, 0x42, 0x62, 0x2E, 0xF9, 0x76, 0xF9, 0x06, 0x45, 0x82, 0x84, 0x9B, 0xBA, 0xBC,
|
|
||||||
0xFF, 0xF9, 0xFB, 0x4F, 0xFB, 0x4F, 0xFB, 0x4F, 0xFB, 0x4F, 0xA0, 0x17, 0x12, 0x21, 0x6F, 0xFD,
|
|
||||||
0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x74, 0xF4, 0x52,
|
|
||||||
0x43, 0x2E, 0x61, 0x6F, 0xF8, 0xD9, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xE5, 0xAD, 0x21, 0x65,
|
|
||||||
0xFC, 0x41, 0x61, 0xD6, 0xC0, 0x21, 0x74, 0xFC, 0x21, 0x70, 0xFD, 0x22, 0x67, 0x6F, 0xF3, 0xFD,
|
|
||||||
0x21, 0x64, 0xFB, 0xC1, 0x02, 0x02, 0x7A, 0xD1, 0x1E, 0x21, 0x73, 0xFA, 0x21, 0x66, 0xFD, 0x21,
|
|
||||||
0x6C, 0xFD, 0x58, 0xC5, 0xC4, 0x62, 0x63, 0x64, 0x66, 0x6B, 0x6D, 0x70, 0x73, 0x74, 0x7A, 0x67,
|
|
||||||
0x2E, 0x68, 0x6A, 0x6C, 0x6E, 0x72, 0x76, 0x77, 0x78, 0x61, 0x6F, 0xFF, 0xA8, 0xFB, 0x08, 0xFA,
|
|
||||||
0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA, 0xF7, 0xFA,
|
|
||||||
0xF7, 0xFA, 0xF7, 0xFB, 0x2D, 0xF8, 0xA7, 0xF8, 0xAF, 0xF8, 0xAF, 0xF8, 0xAF, 0xF8, 0xAF, 0xFF,
|
|
||||||
0xCE, 0xF8, 0xAF, 0xF8, 0xAF, 0xF8, 0xAF, 0xFF, 0xEE, 0xFF, 0xFD, 0x45, 0x82, 0x84, 0x9B, 0xBA,
|
|
||||||
0xBC, 0xFA, 0xAE, 0xFA, 0xAE, 0xFA, 0xAE, 0xFA, 0xAE, 0xFA, 0xAE, 0x41, 0x7A, 0xFB, 0x26, 0x21,
|
|
||||||
0x73, 0xFC, 0xA0, 0x17, 0x32, 0x21, 0x77, 0xFD, 0x21, 0x7A, 0xFD, 0x41, 0x79, 0xD6, 0x55, 0x21,
|
|
||||||
0x65, 0xFC, 0x21, 0x6C, 0xFD, 0x22, 0x63, 0x78, 0xF3, 0xFD, 0x58, 0xC4, 0xC5, 0x62, 0x63, 0x64,
|
|
||||||
0x66, 0x67, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x68, 0x2E, 0x76,
|
|
||||||
0x78, 0x69, 0x75, 0xFA, 0x90, 0xFF, 0xD1, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA,
|
|
||||||
0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA,
|
|
||||||
0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0x7F, 0xFA, 0xB5, 0xF8, 0x2F, 0xF8, 0x37, 0xF8, 0x37, 0xFF,
|
|
||||||
0xE5, 0xFF, 0xFB, 0xA0, 0x05, 0x42, 0x21, 0x82, 0xFD, 0xC3, 0x14, 0x82, 0x2E, 0xC5, 0x72, 0xF7,
|
|
||||||
0xE0, 0xFF, 0xFD, 0xFF, 0xFA, 0xA0, 0x17, 0x53, 0x22, 0x62, 0x6D, 0xFD, 0xFD, 0x21, 0x7A, 0xFB,
|
|
||||||
0x21, 0x7A, 0xFD, 0x57, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6B, 0x6C, 0x6D, 0x6E,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6A, 0x2E, 0x76, 0x78, 0x61, 0xFA, 0x27, 0xFF, 0x68, 0xFA,
|
|
||||||
0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA,
|
|
||||||
0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFA, 0x16, 0xFF, 0xE6, 0xFA, 0x16, 0xFA, 0x16, 0xFA,
|
|
||||||
0x4C, 0xF7, 0xC6, 0xF7, 0xCE, 0xF7, 0xCE, 0xFF, 0xFD, 0xA0, 0x14, 0xE3, 0xA1, 0x14, 0x82, 0x74,
|
|
||||||
0xFD, 0xC3, 0x14, 0x82, 0x7A, 0x2E, 0x74, 0xFF, 0xFB, 0xF7, 0x78, 0xF7, 0x80, 0x41, 0x6E, 0xFE,
|
|
||||||
0x7D, 0x21, 0x6F, 0xFC, 0x21, 0x72, 0xFD, 0x41, 0x65, 0xFE, 0x73, 0x21, 0x68, 0xFC, 0x21, 0x75,
|
|
||||||
0xFD, 0x22, 0x6B, 0x72, 0xF3, 0xFD, 0x21, 0x73, 0xFB, 0x21, 0x6C, 0xFD, 0x21, 0x72, 0xFD, 0x41,
|
|
||||||
0x66, 0xE4, 0x4F, 0x21, 0x66, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x68, 0xFD, 0x21,
|
|
||||||
0x63, 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x6D, 0xE6, 0x76, 0x21, 0x73, 0xFC, 0x21, 0x65, 0xFD, 0x21,
|
|
||||||
0x72, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6E, 0xFD, 0x59, 0xC5, 0xC4, 0x62, 0x63, 0x64, 0x66, 0x67,
|
|
||||||
0x6D, 0x6E, 0x70, 0x73, 0x74, 0x7A, 0x6B, 0x2E, 0x68, 0x6A, 0x6C, 0x72, 0x76, 0x77, 0x78, 0x61,
|
|
||||||
0x69, 0x6F, 0xFE, 0x22, 0xF9, 0x82, 0xF9, 0x71, 0xF9, 0x71, 0xF9, 0x71, 0xF9, 0x71, 0xF9, 0x71,
|
|
||||||
0xF9, 0x71, 0xF9, 0x71, 0xF9, 0x71, 0xFF, 0xA9, 0xF9, 0x71, 0xF9, 0x71, 0xF9, 0xA7, 0xF7, 0x21,
|
|
||||||
0xF7, 0x29, 0xF7, 0x29, 0xF7, 0x29, 0xF7, 0x29, 0xF7, 0x29, 0xF7, 0x29, 0xF7, 0x29, 0xFF, 0xD4,
|
|
||||||
0xFF, 0xEA, 0xFF, 0xFD, 0xA0, 0x14, 0xB1, 0x21, 0x77, 0xFD, 0x21, 0x64, 0xFD, 0x41, 0x66, 0xF9,
|
|
||||||
0xEC, 0x21, 0x66, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x77, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x72, 0xFE,
|
|
||||||
0x75, 0x21, 0x65, 0xFC, 0x21, 0x66, 0xFD, 0x21, 0x73, 0xFD, 0x22, 0x66, 0x6B, 0xF0, 0xFD, 0x41,
|
|
||||||
0x6F, 0xF7, 0x84, 0x59, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E,
|
|
||||||
0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6C, 0x2E, 0x76, 0x78, 0x65, 0x75, 0x79, 0xF9, 0x07, 0xFE,
|
|
||||||
0x48, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8,
|
|
||||||
0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8, 0xF6, 0xF8,
|
|
||||||
0xF6, 0xF9, 0x2C, 0xF6, 0xA6, 0xF6, 0xAE, 0xF6, 0xAE, 0xFF, 0xD7, 0xFF, 0xF7, 0xFF, 0xFC, 0xA0,
|
|
||||||
0x15, 0x13, 0xC2, 0x14, 0x82, 0x6E, 0x2E, 0xFF, 0xFD, 0xF6, 0x57, 0x41, 0x74, 0xFB, 0xC9, 0x41,
|
|
||||||
0x6A, 0xFB, 0xC5, 0x22, 0x73, 0x7A, 0xF8, 0xFC, 0xC2, 0x14, 0x82, 0x65, 0x2E, 0xFF, 0xFB, 0xF6,
|
|
||||||
0x41, 0x41, 0x6E, 0xFE, 0xB8, 0xC3, 0x14, 0x82, 0x6B, 0x2E, 0x74, 0xFF, 0xFC, 0xF6, 0x34, 0xF6,
|
|
||||||
0x3C, 0x41, 0x82, 0xFC, 0xA1, 0x43, 0xC5, 0x6C, 0x6E, 0xFF, 0xFC, 0xFC, 0x9D, 0xFC, 0x9D, 0x21,
|
|
||||||
0x7A, 0xF6, 0x21, 0x72, 0xFD, 0x41, 0x7A, 0xFD, 0xCD, 0x21, 0x73, 0xFC, 0x21, 0x61, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFD, 0x21, 0x7A, 0xFD, 0xA0, 0x17, 0x82, 0x21, 0x82, 0xFD, 0x22, 0xC5, 0x69, 0xFD, 0xFA,
|
|
||||||
0x21, 0x7A, 0xFB, 0x21, 0x72, 0xFD, 0x22, 0x73, 0x65, 0xEC, 0xFD, 0x41, 0x6C, 0xD3, 0x22, 0x21,
|
|
||||||
0x61, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x6B, 0xE2, 0xC5, 0x21,
|
|
||||||
0x69, 0xFC, 0x21, 0x61, 0xFD, 0x22, 0x6E, 0x7A, 0xF3, 0xFD, 0xA0, 0x17, 0xA3, 0x21, 0x6C, 0xFD,
|
|
||||||
0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x61, 0xFD, 0x21,
|
|
||||||
0x7A, 0xFD, 0x21, 0x72, 0xFD, 0x5A, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6D, 0x2E, 0x76, 0x78, 0x61, 0x69, 0x6F, 0x75,
|
|
||||||
0xF8, 0x15, 0xFD, 0x56, 0xF8, 0x04, 0xF8, 0x04, 0xF8, 0x04, 0xF8, 0x04, 0xF8, 0x04, 0xF8, 0x04,
|
|
||||||
0xF8, 0x04, 0xFF, 0x5D, 0xF8, 0x04, 0xFF, 0x73, 0xF8, 0x04, 0xF8, 0x04, 0xFF, 0x80, 0xF8, 0x04,
|
|
||||||
0xF8, 0x04, 0xF8, 0x04, 0xF8, 0x3A, 0xF5, 0xB4, 0xF5, 0xBC, 0xF5, 0xBC, 0xFF, 0x9D, 0xFF, 0xC1,
|
|
||||||
0xFF, 0xE0, 0xFF, 0xFD, 0xC2, 0x14, 0x82, 0x6E, 0x2E, 0xF5, 0xD5, 0xF5, 0x65, 0x41, 0x73, 0xEF,
|
|
||||||
0xFD, 0x21, 0x77, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x82, 0xFD, 0x41, 0x76, 0xEF,
|
|
||||||
0x40, 0x22, 0xC5, 0x72, 0xF9, 0xFC, 0x57, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A,
|
|
||||||
0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x74, 0x77, 0x7A, 0x6E, 0x2E, 0x76, 0x78, 0x61, 0xF7, 0xA4,
|
|
||||||
0xFC, 0xE5, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93,
|
|
||||||
0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xF7, 0x93, 0xFF, 0xDE, 0xF7, 0x93,
|
|
||||||
0xF7, 0x93, 0xF7, 0xC9, 0xF5, 0x43, 0xF5, 0x4B, 0xF5, 0x4B, 0xFF, 0xFB, 0x41, 0x75, 0xFA, 0x78,
|
|
||||||
0xC2, 0x14, 0x82, 0x65, 0x2E, 0xFF, 0xFC, 0xF4, 0xF9, 0xA0, 0x14, 0x82, 0xC2, 0x14, 0x82, 0x7A,
|
|
||||||
0x2E, 0xFF, 0xFD, 0xF4, 0xED, 0x42, 0x6E, 0x2E, 0xF5, 0x54, 0xF4, 0xE4, 0x41, 0x6C, 0xF5, 0xB7,
|
|
||||||
0x21, 0x61, 0xFC, 0x21, 0x68, 0xFD, 0x41, 0x61, 0xF8, 0x12, 0x21, 0x70, 0xFC, 0x21, 0x7A, 0xFD,
|
|
||||||
0x21, 0x73, 0xFD, 0x44, 0x2E, 0x7A, 0x63, 0x65, 0xF4, 0xC6, 0xF4, 0xCE, 0xFF, 0xF0, 0xFF, 0xFD,
|
|
||||||
0x41, 0x72, 0xF7, 0x82, 0x21, 0x65, 0xFC, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x42, 0x61, 0x68,
|
|
||||||
0xD2, 0xC6, 0xD2, 0xC3, 0x21, 0x63, 0xF9, 0x22, 0x6E, 0x73, 0xF3, 0xFD, 0x41, 0x69, 0xCD, 0x37,
|
|
||||||
0x21, 0x6E, 0xFC, 0x21, 0x64, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x82, 0xFD, 0x41, 0x7A, 0xD2, 0x7C,
|
|
||||||
0x21, 0x72, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6E, 0xFD, 0x42,
|
|
||||||
0x85, 0x99, 0xE1, 0x58, 0xE1, 0x58, 0x42, 0xC4, 0x69, 0xFF, 0xF9, 0xE1, 0x51, 0x21, 0x6E, 0xF9,
|
|
||||||
0xA0, 0x17, 0xD4, 0x21, 0x68, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x6F, 0xFD, 0x21,
|
|
||||||
0x6D, 0xFD, 0x41, 0x64, 0xEE, 0xF8, 0x21, 0x6E, 0xFC, 0x21, 0x61, 0xFD, 0x22, 0x73, 0x6C, 0xF3,
|
|
||||||
0xFD, 0x21, 0x74, 0xFB, 0x41, 0x65, 0xD2, 0x3D, 0x21, 0x72, 0xFC, 0x42, 0x65, 0x75, 0xD4, 0xF0,
|
|
||||||
0xFF, 0xFD, 0x21, 0x69, 0xF9, 0x41, 0x69, 0xCD, 0xBE, 0x21, 0x6E, 0xFC, 0x21, 0x64, 0xFD, 0x21,
|
|
||||||
0x65, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD, 0x46, 0xC5, 0x64, 0x6D, 0x72, 0x6C, 0x77, 0xFF,
|
|
||||||
0x91, 0xFF, 0xA4, 0xFF, 0xB5, 0xFF, 0xD9, 0xFF, 0xEA, 0xFF, 0xFD, 0x58, 0xC4, 0xC5, 0x62, 0x63,
|
|
||||||
0x64, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x73, 0x74, 0x7A, 0x6C, 0x70, 0x2E, 0x68, 0x6A, 0x72, 0x76,
|
|
||||||
0x77, 0x78, 0x61, 0x6F, 0xF6, 0x6F, 0xF7, 0xB3, 0xF6, 0x5E, 0xF6, 0x5E, 0xF6, 0x5E, 0xF6, 0x5E,
|
|
||||||
0xF6, 0x5E, 0xF6, 0x5E, 0xF6, 0x5E, 0xFF, 0x15, 0xFF, 0x21, 0xF6, 0x5E, 0xF6, 0x5E, 0xFF, 0x2A,
|
|
||||||
0xF6, 0x94, 0xF4, 0x0E, 0xF4, 0x16, 0xF4, 0x16, 0xFF, 0x48, 0xF4, 0x16, 0xF4, 0x16, 0xF4, 0x16,
|
|
||||||
0xFF, 0x6C, 0xFF, 0xED, 0xC2, 0x14, 0x82, 0x2E, 0x7A, 0xF3, 0xC5, 0xF3, 0xCD, 0x45, 0x82, 0x84,
|
|
||||||
0x9B, 0xBA, 0xBC, 0xF3, 0xBF, 0xF7, 0x71, 0xF7, 0x71, 0xF7, 0x71, 0xF7, 0x71, 0xD3, 0x02, 0x31,
|
|
||||||
0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73,
|
|
||||||
0x74, 0x77, 0x2E, 0xF7, 0x64, 0xFF, 0xF0, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7,
|
|
||||||
0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7,
|
|
||||||
0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF7, 0x61, 0xF3, 0xAC, 0x41, 0x6E, 0xF6, 0x1C, 0x21, 0x65, 0xFC,
|
|
||||||
0x21, 0x67, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x7A, 0xD1, 0x77, 0x21, 0x63, 0xFC,
|
|
||||||
0x21, 0x6F, 0xFD, 0x21, 0x72, 0xFD, 0xA0, 0x18, 0x14, 0x21, 0x6B, 0xFD, 0x21, 0x63, 0xFD, 0x21,
|
|
||||||
0x6F, 0xFD, 0x21, 0x74, 0xFD, 0x23, 0x65, 0x6B, 0x73, 0xE1, 0xEE, 0xFD, 0x57, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x7A, 0x72,
|
|
||||||
0x2E, 0x76, 0x78, 0x6F, 0xF5, 0x9E, 0xFA, 0xDF, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D,
|
|
||||||
0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D, 0xF5, 0x8D,
|
|
||||||
0xFF, 0x78, 0xF5, 0x8D, 0xF5, 0x8D, 0xFF, 0x91, 0xF5, 0xC3, 0xF3, 0x3D, 0xF3, 0x45, 0xF3, 0x45,
|
|
||||||
0xFF, 0xF9, 0x45, 0x82, 0xBA, 0xBC, 0x84, 0x9B, 0xF9, 0xF1, 0xF5, 0x47, 0xF5, 0x47, 0xF2, 0xFF,
|
|
||||||
0xF2, 0xFF, 0x42, 0x2E, 0x7A, 0xF2, 0xE7, 0xF2, 0xEF, 0x46, 0x6B, 0x6E, 0x73, 0x2E, 0x72, 0x77,
|
|
||||||
0xF3, 0x50, 0xF3, 0x50, 0xF7, 0xA5, 0xF2, 0xE0, 0xFF, 0xF9, 0xF2, 0xE8, 0x41, 0x9B, 0xF6, 0x82,
|
|
||||||
0x42, 0x2E, 0x62, 0xF2, 0xC9, 0xF2, 0xD1, 0xA1, 0x14, 0x62, 0x7A, 0xF9, 0xC1, 0x14, 0x62, 0x6E,
|
|
||||||
0xF3, 0x2D, 0x41, 0x6D, 0xF9, 0xC8, 0x21, 0x6C, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x68, 0xFD, 0x21,
|
|
||||||
0x6B, 0xFD, 0xC3, 0x14, 0x62, 0x2E, 0x72, 0x6F, 0xF2, 0xA7, 0xF2, 0xAF, 0xFF, 0xFD, 0xA0, 0x18,
|
|
||||||
0x52, 0x21, 0x79, 0xFD, 0x21, 0x82, 0xFD, 0x21, 0xC5, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x73, 0xFD,
|
|
||||||
0x41, 0x67, 0xF5, 0x15, 0x21, 0x6E, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x87, 0xCA,
|
|
||||||
0xC6, 0x21, 0xC4, 0xFC, 0x21, 0x9B, 0xFD, 0x22, 0x7A, 0xC5, 0xF3, 0xFD, 0x41, 0x73, 0xF9, 0x7E,
|
|
||||||
0x21, 0x61, 0xFC, 0x21, 0x77, 0xFD, 0x21, 0x6B, 0xFD, 0x21, 0x6E, 0xFD, 0xD1, 0x02, 0x31, 0xC4,
|
|
||||||
0xC5, 0x63, 0x66, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x77, 0x7A, 0x2E, 0x61, 0x65, 0x79,
|
|
||||||
0xF6, 0x15, 0xFF, 0x90, 0xFF, 0x9B, 0xF6, 0x12, 0xF2, 0x60, 0xFF, 0xA0, 0xF6, 0x12, 0xF2, 0x60,
|
|
||||||
0xF6, 0x12, 0xF6, 0x12, 0xFF, 0xB6, 0xF6, 0x12, 0xF6, 0x12, 0xF2, 0x5D, 0xFF, 0xD1, 0xFF, 0xEB,
|
|
||||||
0xFF, 0xFD, 0x42, 0x2E, 0x68, 0xF2, 0x27, 0xF2, 0x2F, 0x41, 0x7A, 0xF2, 0x28, 0x41, 0x72, 0xFC,
|
|
||||||
0x18, 0x21, 0x65, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x7A, 0xFD, 0x21, 0x72, 0xFD,
|
|
||||||
0x43, 0x2E, 0x72, 0x6F, 0xF2, 0x09, 0xFF, 0xE9, 0xFF, 0xFD, 0x42, 0x2E, 0x72, 0xF1, 0xFF, 0xF2,
|
|
||||||
0xD9, 0x41, 0x65, 0xD0, 0x0F, 0x21, 0x6C, 0xFC, 0x21, 0x74, 0xFD, 0x41, 0x7A, 0xCF, 0xE6, 0x21,
|
|
||||||
0x69, 0xFC, 0x21, 0x77, 0xFD, 0x41, 0x74, 0xCF, 0xFB, 0x21, 0x70, 0xFC, 0x21, 0x6F, 0xFD, 0x41,
|
|
||||||
0x6D, 0xCE, 0x7F, 0x21, 0x65, 0xFC, 0x21, 0x74, 0xFD, 0x22, 0x6E, 0x73, 0xF3, 0xFD, 0x59, 0xC5,
|
|
||||||
0x62, 0x64, 0x66, 0x67, 0x73, 0x6E, 0x74, 0x7A, 0x2E, 0xC4, 0x63, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x70, 0x72, 0x76, 0x77, 0x78, 0x65, 0x6F, 0x79, 0xFE, 0xD4, 0xF4, 0x1B, 0xF4, 0x1B, 0xF4, 0x1B,
|
|
||||||
0xF4, 0x1B, 0xF4, 0x1B, 0xF4, 0x14, 0xFE, 0xEB, 0xFF, 0x6E, 0xF1, 0xCB, 0xF8, 0x14, 0xFF, 0xA4,
|
|
||||||
0xF1, 0xD3, 0xF1, 0xD3, 0xFF, 0xC2, 0xF1, 0xD3, 0xFF, 0xCC, 0xF1, 0xD3, 0xF1, 0xD3, 0xF1, 0xD3,
|
|
||||||
0xF1, 0xD3, 0xF1, 0xD3, 0xFF, 0xDA, 0xFF, 0xE4, 0xFF, 0xFB, 0x43, 0x6B, 0x7A, 0x2E, 0xF1, 0xEF,
|
|
||||||
0xFC, 0x9B, 0xF1, 0x7F, 0xA0, 0x18, 0x73, 0x21, 0x74, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x79, 0xFD,
|
|
||||||
0x41, 0x74, 0xE0, 0xAB, 0x21, 0x75, 0xFC, 0x21, 0x7A, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD,
|
|
||||||
0x21, 0x6F, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x62, 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x69, 0xCF, 0x45,
|
|
||||||
0x21, 0x6E, 0xFC, 0x21, 0x64, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x67, 0xFD, 0x59, 0xC4, 0xC5, 0x62,
|
|
||||||
0x63, 0x64, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x70, 0x73, 0x7A, 0x6C, 0x72, 0x74, 0x2E, 0x68, 0x6A,
|
|
||||||
0x76, 0x77, 0x78, 0x6F, 0x75, 0x79, 0xF3, 0x9E, 0xF4, 0xE2, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D,
|
|
||||||
0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D, 0xF3, 0x8D,
|
|
||||||
0xFC, 0x59, 0xFF, 0xBE, 0xF3, 0xC3, 0xF1, 0x3D, 0xF1, 0x45, 0xF1, 0x45, 0xF1, 0x45, 0xF1, 0x45,
|
|
||||||
0xF1, 0x45, 0xFF, 0xD1, 0xFF, 0xED, 0xFF, 0xFD, 0x41, 0x64, 0xF6, 0x6C, 0x41, 0x73, 0xF6, 0x68,
|
|
||||||
0xC3, 0x14, 0x82, 0x61, 0x6F, 0x2E, 0xFF, 0xF8, 0xFF, 0xFC, 0xF0, 0xE9, 0x45, 0x82, 0x84, 0x9B,
|
|
||||||
0xBA, 0xBC, 0xFF, 0xF4, 0xF3, 0x2D, 0xF3, 0x2D, 0xF3, 0x2D, 0xF3, 0x2D, 0x21, 0x61, 0xE0, 0xC2,
|
|
||||||
0x14, 0x82, 0x7A, 0x2E, 0xFF, 0xFD, 0xF0, 0xCA, 0xA0, 0x18, 0xA3, 0x21, 0x64, 0xFD, 0x21, 0x6E,
|
|
||||||
0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6B, 0xFD, 0x42, 0x66, 0x6D, 0xEB, 0x53, 0xEB, 0x53, 0x21, 0x74,
|
|
||||||
0xF9, 0x22, 0x65, 0x73, 0xF3, 0xFD, 0x57, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x7A, 0x77, 0x2E, 0x68, 0x76, 0x78, 0x65, 0xF3, 0x04,
|
|
||||||
0xFF, 0xC6, 0xF2, 0xF3, 0xFF, 0xD9, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3,
|
|
||||||
0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3, 0xF2, 0xF3,
|
|
||||||
0xF3, 0x29, 0xF0, 0xA3, 0xF0, 0xAB, 0xF0, 0xAB, 0xF0, 0xAB, 0xFF, 0xFB, 0x45, 0x9B, 0x82, 0x84,
|
|
||||||
0xBA, 0xBC, 0xF2, 0xAD, 0xF0, 0x65, 0xF0, 0x65, 0xF0, 0x65, 0xF0, 0x65, 0xC4, 0x14, 0x82, 0x6B,
|
|
||||||
0x6E, 0x2E, 0x72, 0xF0, 0xBD, 0xF0, 0xBD, 0xF0, 0x4D, 0xFD, 0x66, 0x41, 0x70, 0xEA, 0x32, 0x21,
|
|
||||||
0x70, 0xFC, 0x57, 0xC4, 0xC5, 0x63, 0x64, 0x66, 0x6B, 0x70, 0x73, 0x74, 0x7A, 0x2E, 0x62, 0x67,
|
|
||||||
0x68, 0x6A, 0x6C, 0x6D, 0x6E, 0x72, 0x76, 0x77, 0x78, 0x65, 0xF2, 0x98, 0xFF, 0xDA, 0xF2, 0x87,
|
|
||||||
0xFF, 0xEA, 0xF2, 0x87, 0xF2, 0x87, 0xF2, 0x87, 0xF2, 0x87, 0xF2, 0x87, 0xF2, 0xBD, 0xF0, 0x37,
|
|
||||||
0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F,
|
|
||||||
0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xFF, 0xFD, 0xA0, 0x15, 0xB3, 0x21, 0x73, 0xFD, 0x21, 0x6B,
|
|
||||||
0xFD, 0x21, 0x6C, 0xFD, 0x57, 0x2E, 0xC4, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B,
|
|
||||||
0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x7A, 0x6F, 0xEF, 0xE5, 0xF6, 0x2E,
|
|
||||||
0xF6, 0x32, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED,
|
|
||||||
0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED,
|
|
||||||
0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xEF, 0xED, 0xFF, 0xFD, 0x56, 0x2E, 0xC4, 0xC5, 0x62, 0x63,
|
|
||||||
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78,
|
|
||||||
0x7A, 0xEF, 0x9F, 0xF5, 0xE8, 0xF5, 0xEC, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF,
|
|
||||||
0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF,
|
|
||||||
0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xEF, 0xA7, 0xA0, 0x00, 0xD1,
|
|
||||||
0xC6, 0x02, 0x61, 0x75, 0x79, 0x61, 0x65, 0x69, 0x6F, 0xFF, 0xFD, 0xFF, 0xFD, 0xF0, 0x33, 0xF0,
|
|
||||||
0x33, 0xF0, 0x33, 0xF0, 0x33, 0xA0, 0x04, 0xF2, 0x21, 0x63, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x7A,
|
|
||||||
0xFD, 0xA0, 0x04, 0xE2, 0x21, 0x7A, 0xFD, 0xA1, 0x00, 0xD1, 0x73, 0xFD, 0xC7, 0x02, 0x61, 0x72,
|
|
||||||
0x75, 0x79, 0x61, 0x65, 0x69, 0x6F, 0xFF, 0xF2, 0xFF, 0xFB, 0xFF, 0xD1, 0xF0, 0x07, 0xF0, 0x07,
|
|
||||||
0xF0, 0x07, 0xF0, 0x07, 0x22, 0x85, 0x99, 0xB9, 0xB9, 0x21, 0xB3, 0xB4, 0x41, 0x6B, 0xE9, 0xB1,
|
|
||||||
0x21, 0x63, 0xFC, 0x21, 0x75, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x62, 0xFD, 0x21, 0x73, 0xFD, 0x41,
|
|
||||||
0x63, 0xE9, 0x9E, 0x21, 0x75, 0xFC, 0x21, 0x72, 0xFD, 0x21, 0x62, 0xFD, 0x22, 0x6E, 0x73, 0xF0,
|
|
||||||
0xFD, 0xC9, 0x02, 0x61, 0xC4, 0xC3, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x6E, 0xFF, 0xD3, 0xFF,
|
|
||||||
0xD8, 0xFF, 0x8C, 0xFF, 0x8C, 0xFF, 0x8C, 0xFF, 0x8C, 0xFF, 0x8C, 0xFF, 0x8C, 0xFF, 0xFB, 0x41,
|
|
||||||
0x72, 0xDB, 0xBC, 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6D, 0xFD, 0x21,
|
|
||||||
0x68, 0xFD, 0x41, 0x65, 0xFC, 0xD3, 0x21, 0x73, 0xFC, 0x21, 0x66, 0xFD, 0xC8, 0x02, 0x61, 0x79,
|
|
||||||
0x61, 0x63, 0x65, 0x66, 0x69, 0x6F, 0x75, 0xFF, 0x51, 0xEF, 0x87, 0xFF, 0xF3, 0xEF, 0x87, 0xFF,
|
|
||||||
0xFD, 0xEF, 0x87, 0xEF, 0x87, 0xEF, 0x87, 0xC6, 0x02, 0x61, 0x79, 0x61, 0x65, 0x69, 0x6F, 0x75,
|
|
||||||
0xFF, 0x36, 0xEF, 0x6C, 0xEF, 0x6C, 0xEF, 0x6C, 0xEF, 0x6C, 0xEF, 0x6C, 0xC5, 0x02, 0x61, 0x61,
|
|
||||||
0x65, 0x69, 0x6F, 0x75, 0xEF, 0x57, 0xEF, 0x57, 0xEF, 0x57, 0xEF, 0x57, 0xEF, 0x57, 0x5D, 0x2E,
|
|
||||||
0xC4, 0xC3, 0xC5, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72,
|
|
||||||
0x73, 0x74, 0x77, 0x7A, 0x76, 0x78, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xEE, 0x19, 0xEE, 0xB6,
|
|
||||||
0xEE, 0xCC, 0xF0, 0xA4, 0xF1, 0xC1, 0xF2, 0xE7, 0xF4, 0x6B, 0xF5, 0x1C, 0xF5, 0xC4, 0xF6, 0x3C,
|
|
||||||
0xF6, 0xA5, 0xF7, 0x4A, 0xF7, 0xC5, 0xF8, 0xB7, 0xF9, 0x28, 0xFA, 0x5D, 0xFB, 0x2E, 0xFC, 0xA0,
|
|
||||||
0xFD, 0x2E, 0xFD, 0xC8, 0xFE, 0x34, 0xFE, 0x86, 0xFE, 0xCC, 0xFF, 0x12, 0xFF, 0x3E, 0xFF, 0x83,
|
|
||||||
0xFF, 0xBE, 0xFF, 0xD9, 0xFF, 0xEE,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SerializedHyphenationPatterns pl_patterns = {
|
|
||||||
0x3C4Eu,
|
|
||||||
pl_trie_data,
|
|
||||||
sizeof(pl_trie_data),
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,183 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <HalStorage.h>
|
|
||||||
#include <expat.h>
|
|
||||||
|
|
||||||
#include <climits>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Epub/FootnoteEntry.h"
|
|
||||||
#include "Epub/ParsedText.h"
|
|
||||||
#include "Epub/blocks/ImageBlock.h"
|
|
||||||
#include "Epub/blocks/TextBlock.h"
|
|
||||||
#include "Epub/css/CssParser.h"
|
|
||||||
#include "Epub/css/CssStyle.h"
|
|
||||||
|
|
||||||
class Page;
|
|
||||||
class GfxRenderer;
|
|
||||||
class Epub;
|
|
||||||
|
|
||||||
#define MAX_WORD_SIZE 200
|
|
||||||
|
|
||||||
class ChapterHtmlSlimParser {
|
|
||||||
std::shared_ptr<Epub> epub;
|
|
||||||
const std::string& filepath;
|
|
||||||
GfxRenderer& renderer;
|
|
||||||
std::function<void(std::unique_ptr<Page>, uint16_t, uint16_t)> completePageFn;
|
|
||||||
std::function<void()> popupFn; // Popup callback
|
|
||||||
int depth = 0;
|
|
||||||
int skipUntilDepth = INT_MAX;
|
|
||||||
int boldUntilDepth = INT_MAX;
|
|
||||||
int italicUntilDepth = INT_MAX;
|
|
||||||
// buffer for building up words from characters, will auto break if longer than this
|
|
||||||
// leave one char at end for null pointer
|
|
||||||
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
|
||||||
int partWordBufferIndex = 0;
|
|
||||||
bool nextWordContinues = false; // true when next flushed word attaches to previous (inline element boundary)
|
|
||||||
std::unique_ptr<ParsedText> currentTextBlock = nullptr;
|
|
||||||
std::unique_ptr<Page> currentPage = nullptr;
|
|
||||||
int16_t currentPageNextY = 0;
|
|
||||||
int fontId;
|
|
||||||
float lineCompression;
|
|
||||||
bool extraParagraphSpacing;
|
|
||||||
uint8_t paragraphAlignment;
|
|
||||||
uint16_t viewportWidth;
|
|
||||||
uint16_t viewportHeight;
|
|
||||||
bool hyphenationEnabled;
|
|
||||||
bool focusReadingEnabled;
|
|
||||||
const CssParser* cssParser;
|
|
||||||
bool embeddedStyle;
|
|
||||||
uint8_t imageRendering;
|
|
||||||
std::string contentBase;
|
|
||||||
std::string imageBasePath;
|
|
||||||
int imageCounter = 0;
|
|
||||||
|
|
||||||
// Style tracking (replaces depth-based approach)
|
|
||||||
struct StyleStackEntry {
|
|
||||||
int depth = 0;
|
|
||||||
bool hasBold = false, bold = false;
|
|
||||||
bool hasItalic = false, italic = false;
|
|
||||||
bool hasTextDecoration = false;
|
|
||||||
CssTextDecoration textDecoration = CssTextDecoration::None;
|
|
||||||
bool hasDirection = false;
|
|
||||||
CssTextDirection direction = CssTextDirection::Ltr;
|
|
||||||
bool hasSup = false, sup = false;
|
|
||||||
bool hasSub = false, sub = false;
|
|
||||||
};
|
|
||||||
std::vector<StyleStackEntry> inlineStyleStack;
|
|
||||||
std::vector<BlockStyle> blockStyleStack; // accumulated block styles from open ancestor elements
|
|
||||||
CssStyle currentCssStyle;
|
|
||||||
bool effectiveBold = false;
|
|
||||||
bool effectiveItalic = false;
|
|
||||||
CssTextDecoration effectiveTextDecoration = CssTextDecoration::None;
|
|
||||||
bool effectiveDirectionDefined = false;
|
|
||||||
CssTextDirection effectiveDirection = CssTextDirection::Ltr;
|
|
||||||
bool effectiveSup = false;
|
|
||||||
bool effectiveSub = false;
|
|
||||||
int tableDepth = 0;
|
|
||||||
int tableRowIndex = 0;
|
|
||||||
int tableColIndex = 0;
|
|
||||||
bool listItemBulletOnly = false; // true when currentTextBlock has only the <li> bullet
|
|
||||||
|
|
||||||
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
|
||||||
int completedPageCount = 0;
|
|
||||||
std::vector<std::pair<std::string, uint16_t>> anchorData;
|
|
||||||
std::string pendingAnchorId; // deferred until after previous text block is flushed
|
|
||||||
std::vector<std::string> tocAnchors; // the list of anchors that are TOC chapter boundaries
|
|
||||||
uint16_t xpathParagraphIndex = 0;
|
|
||||||
uint16_t xpathListItemIndex = 0;
|
|
||||||
|
|
||||||
// Footnote link tracking
|
|
||||||
bool insideFootnoteLink = false;
|
|
||||||
int footnoteLinkDepth = -1;
|
|
||||||
FootnoteEntry currentFootnote = {};
|
|
||||||
int currentFootnoteLinkTextLen = 0;
|
|
||||||
std::vector<std::pair<int, FootnoteEntry>> pendingFootnotes; // <wordIndex, entry>
|
|
||||||
int wordsExtractedInBlock = 0;
|
|
||||||
|
|
||||||
// Resumable parse state. The one-shot parseAndBuildPages() drives these
|
|
||||||
// internally; the incremental section builder drives them across render ticks
|
|
||||||
// so a large single chapter can yield between pages instead of blocking the UI
|
|
||||||
// until the whole thing is laid out. parseFile_ and the expat parser stay alive
|
|
||||||
// for the lifetime of the parse so it can be paused and resumed at buffer
|
|
||||||
// boundaries.
|
|
||||||
XML_Parser xmlParser_ = nullptr;
|
|
||||||
HalFile parseFile_;
|
|
||||||
uint32_t parseStartTime_ = 0;
|
|
||||||
|
|
||||||
void updateEffectiveInlineStyle();
|
|
||||||
void startNewTextBlock(const BlockStyle& blockStyle);
|
|
||||||
void flushPendingAnchor();
|
|
||||||
void flushPartWordBuffer();
|
|
||||||
void makePages();
|
|
||||||
static EpdFontFamily::Style fontStyleForTextDecoration(CssTextDecoration decoration);
|
|
||||||
static void applyDirectionToEntry(StyleStackEntry& entry, const CssStyle& css);
|
|
||||||
static void applyTextDecorationToEntry(StyleStackEntry& entry, const CssStyle& css);
|
|
||||||
void pushDecorationStyleEntry(CssTextDecoration defaultDecoration, const CssStyle& cssStyle);
|
|
||||||
void emitHorizontalRule(const BlockStyle& blockStyle);
|
|
||||||
// XML callbacks
|
|
||||||
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
|
||||||
static void XMLCALL characterData(void* userData, const XML_Char* s, int len);
|
|
||||||
static void XMLCALL defaultHandlerExpand(void* userData, const XML_Char* s, int len);
|
|
||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ChapterHtmlSlimParser(std::shared_ptr<Epub> epub, const std::string& filepath, GfxRenderer& renderer,
|
|
||||||
const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
|
||||||
const bool focusReadingEnabled,
|
|
||||||
const std::function<void(std::unique_ptr<Page>, uint16_t, uint16_t)>& completePageFn,
|
|
||||||
const bool embeddedStyle, const std::string& contentBase,
|
|
||||||
const std::string& imageBasePath, const uint8_t imageRendering = 0,
|
|
||||||
std::vector<std::string> tocAnchors = {},
|
|
||||||
const std::function<void()>& popupFn = nullptr, const CssParser* cssParser = nullptr)
|
|
||||||
|
|
||||||
: epub(epub),
|
|
||||||
filepath(filepath),
|
|
||||||
renderer(renderer),
|
|
||||||
fontId(fontId),
|
|
||||||
lineCompression(lineCompression),
|
|
||||||
extraParagraphSpacing(extraParagraphSpacing),
|
|
||||||
paragraphAlignment(paragraphAlignment),
|
|
||||||
viewportWidth(viewportWidth),
|
|
||||||
viewportHeight(viewportHeight),
|
|
||||||
hyphenationEnabled(hyphenationEnabled),
|
|
||||||
focusReadingEnabled(focusReadingEnabled),
|
|
||||||
completePageFn(completePageFn),
|
|
||||||
popupFn(popupFn),
|
|
||||||
cssParser(cssParser),
|
|
||||||
embeddedStyle(embeddedStyle),
|
|
||||||
imageRendering(imageRendering),
|
|
||||||
contentBase(contentBase),
|
|
||||||
imageBasePath(imageBasePath),
|
|
||||||
tocAnchors(std::move(tocAnchors)) {}
|
|
||||||
|
|
||||||
~ChapterHtmlSlimParser();
|
|
||||||
|
|
||||||
// One-shot parse: builds every page before returning (begin + step* + finish).
|
|
||||||
bool parseAndBuildPages();
|
|
||||||
|
|
||||||
// Resumable parse, for the incremental section builder. Drive as:
|
|
||||||
// if (!beginParse()) fail;
|
|
||||||
// loop: switch (parseStep()) { More: keep going / yield; Done: finishParse(); Error: abortParse(); }
|
|
||||||
// Pages are emitted via completePageFn as they complete during parseStep(), so
|
|
||||||
// the caller can stop once enough pages are built and resume on a later tick.
|
|
||||||
enum class ParseStatus { More, Done, Error };
|
|
||||||
bool beginParse();
|
|
||||||
ParseStatus parseStep();
|
|
||||||
bool finishParse(); // flush the trailing page and tear down; returns true
|
|
||||||
void abortParse(); // tear down without flushing (error / abandon)
|
|
||||||
|
|
||||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
|
||||||
const std::vector<std::pair<std::string, uint16_t>>& getAnchors() const { return anchorData; }
|
|
||||||
|
|
||||||
// Byte progress of the in-flight parse, used to estimate a still-building section's total page
|
|
||||||
// count (a giant single-spine book never fully lays out, so its real count is unknown). Valid
|
|
||||||
// between beginParse() and finishParse()/abortParse().
|
|
||||||
size_t parseBytesConsumed() { return parseFile_ ? parseFile_.position() : 0; }
|
|
||||||
size_t parseTotalBytes() { return parseFile_ ? parseFile_.size() : 0; }
|
|
||||||
};
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#include "ContainerParser.h"
|
|
||||||
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <XmlParserUtils.h>
|
|
||||||
|
|
||||||
bool ContainerParser::setup() {
|
|
||||||
parser = XML_ParserCreate(nullptr);
|
|
||||||
if (!parser) {
|
|
||||||
LOG_ERR("CTR", "Couldn't allocate memory for parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
XML_SetUserData(parser, this);
|
|
||||||
XML_SetElementHandler(parser, startElement, endElement);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerParser::~ContainerParser() { destroyXmlParser(parser); }
|
|
||||||
|
|
||||||
size_t ContainerParser::write(const uint8_t data) { return write(&data, 1); }
|
|
||||||
|
|
||||||
size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
|
|
||||||
if (!parser) return 0;
|
|
||||||
|
|
||||||
const uint8_t* currentBufferPos = buffer;
|
|
||||||
auto remainingInBuffer = size;
|
|
||||||
|
|
||||||
while (remainingInBuffer > 0) {
|
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
|
||||||
if (!buf) {
|
|
||||||
LOG_DBG("CTR", "Couldn't allocate buffer");
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
|
||||||
memcpy(buf, currentBufferPos, toRead);
|
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
|
||||||
LOG_ERR("CTR", "Parse error: %s", XML_ErrorString(XML_GetErrorCode(parser)));
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBufferPos += toRead;
|
|
||||||
remainingInBuffer -= toRead;
|
|
||||||
remainingSize -= toRead;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL ContainerParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
|
||||||
auto* self = static_cast<ContainerParser*>(userData);
|
|
||||||
|
|
||||||
// Simple state tracking to ensure we are looking at the valid schema structure
|
|
||||||
if (self->state == START && strcmp(name, "container") == 0) {
|
|
||||||
self->state = IN_CONTAINER;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_CONTAINER && strcmp(name, "rootfiles") == 0) {
|
|
||||||
self->state = IN_ROOTFILES;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_ROOTFILES && strcmp(name, "rootfile") == 0) {
|
|
||||||
const char* mediaType = nullptr;
|
|
||||||
const char* path = nullptr;
|
|
||||||
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "media-type") == 0) {
|
|
||||||
mediaType = atts[i + 1];
|
|
||||||
} else if (strcmp(atts[i], "full-path") == 0) {
|
|
||||||
path = atts[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is the standard OEBPS package
|
|
||||||
if (mediaType && path && strcmp(mediaType, "application/oebps-package+xml") == 0) {
|
|
||||||
self->fullPath = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL ContainerParser::endElement(void* userData, const XML_Char* name) {
|
|
||||||
auto* self = static_cast<ContainerParser*>(userData);
|
|
||||||
|
|
||||||
if (self->state == IN_ROOTFILES && strcmp(name, "rootfiles") == 0) {
|
|
||||||
self->state = IN_CONTAINER;
|
|
||||||
} else if (self->state == IN_CONTAINER && strcmp(name, "container") == 0) {
|
|
||||||
self->state = START;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "expat.h"
|
|
||||||
|
|
||||||
class ContainerParser final : public Print {
|
|
||||||
enum ParserState {
|
|
||||||
START,
|
|
||||||
IN_CONTAINER,
|
|
||||||
IN_ROOTFILES,
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t remainingSize;
|
|
||||||
XML_Parser parser = nullptr;
|
|
||||||
ParserState state = START;
|
|
||||||
|
|
||||||
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
|
||||||
static void endElement(void* userData, const XML_Char* name);
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::string fullPath;
|
|
||||||
|
|
||||||
explicit ContainerParser(const size_t xmlSize) : remainingSize(xmlSize) {}
|
|
||||||
~ContainerParser() override;
|
|
||||||
|
|
||||||
bool setup();
|
|
||||||
|
|
||||||
size_t write(uint8_t) override;
|
|
||||||
size_t write(const uint8_t* buffer, size_t size) override;
|
|
||||||
};
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
#include "ContentOpfParser.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <Serialization.h>
|
|
||||||
#include <XmlParserUtils.h>
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
#include "Epub/BookMetadataCache.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml";
|
|
||||||
constexpr char MEDIA_TYPE_CSS[] = "text/css";
|
|
||||||
constexpr char MEDIA_TYPE_IMAGE_PREFIX[] = "image/";
|
|
||||||
constexpr char itemCacheFile[] = "/.items.bin";
|
|
||||||
|
|
||||||
bool startsWithImageMediaType(const std::string& mediaType) {
|
|
||||||
constexpr size_t prefixLen = sizeof(MEDIA_TYPE_IMAGE_PREFIX) - 1;
|
|
||||||
if (mediaType.size() < prefixLen) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < prefixLen; ++i) {
|
|
||||||
const char c = static_cast<char>(std::tolower(static_cast<unsigned char>(mediaType[i])));
|
|
||||||
if (c != MEDIA_TYPE_IMAGE_PREFIX[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool ContentOpfParser::setup() {
|
|
||||||
parser = XML_ParserCreate(nullptr);
|
|
||||||
if (!parser) {
|
|
||||||
LOG_DBG("COF", "Couldn't allocate memory for parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
XML_SetUserData(parser, this);
|
|
||||||
XML_SetElementHandler(parser, startElement, endElement);
|
|
||||||
XML_SetCharacterDataHandler(parser, characterData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentOpfParser::~ContentOpfParser() {
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
if (tempItemStore) {
|
|
||||||
tempItemStore.close();
|
|
||||||
}
|
|
||||||
const auto itemCachePath = cachePath + itemCacheFile;
|
|
||||||
if (Storage.exists(itemCachePath.c_str())) {
|
|
||||||
Storage.remove(itemCachePath.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }
|
|
||||||
|
|
||||||
size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
|
|
||||||
if (!parser) return 0;
|
|
||||||
|
|
||||||
const uint8_t* currentBufferPos = buffer;
|
|
||||||
auto remainingInBuffer = size;
|
|
||||||
|
|
||||||
while (remainingInBuffer > 0) {
|
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
|
||||||
|
|
||||||
if (!buf) {
|
|
||||||
LOG_ERR("COF", "Couldn't allocate memory for buffer");
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
|
||||||
memcpy(buf, currentBufferPos, toRead);
|
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
|
||||||
LOG_DBG("COF", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBufferPos += toRead;
|
|
||||||
remainingInBuffer -= toRead;
|
|
||||||
remainingSize -= toRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
|
||||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
|
||||||
(void)atts;
|
|
||||||
|
|
||||||
if (self->state == START && (strcmp(name, "package") == 0 || strcmp(name, "opf:package") == 0)) {
|
|
||||||
self->state = IN_PACKAGE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "metadata") == 0 || strcmp(name, "opf:metadata") == 0)) {
|
|
||||||
self->state = IN_METADATA;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_METADATA && strcmp(name, "dc:title") == 0) {
|
|
||||||
// Only capture the first dc:title element; subsequent ones are subtitles
|
|
||||||
if (self->title.empty()) {
|
|
||||||
self->state = IN_BOOK_TITLE;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_METADATA && strcmp(name, "dc:creator") == 0) {
|
|
||||||
self->state = IN_BOOK_AUTHOR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_METADATA && strcmp(name, "dc:language") == 0) {
|
|
||||||
self->state = IN_BOOK_LANGUAGE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
|
||||||
self->state = IN_MANIFEST;
|
|
||||||
if (!Storage.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
|
||||||
LOG_ERR("COF", "Couldn't open temp items file for writing. This is probably going to be a fatal error.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
|
||||||
self->state = IN_SPINE;
|
|
||||||
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
|
||||||
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the (unconditionally-built) item index so every idref lookup uses binary
|
|
||||||
// search. Without this, small/medium manifests fell back to an O(spine × manifest)
|
|
||||||
// linear rescan of .items.bin per itemref (up to ~200ms/item at large scale).
|
|
||||||
if (!self->itemIndex.empty()) {
|
|
||||||
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;
|
|
||||||
LOG_DBG("COF", "Using fast index for %zu manifest items", self->itemIndex.size());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
|
||||||
self->state = IN_GUIDE;
|
|
||||||
// TODO Remove print
|
|
||||||
LOG_DBG("COF", "Entering guide state.");
|
|
||||||
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
|
||||||
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_METADATA && (strcmp(name, "meta") == 0 || strcmp(name, "opf:meta") == 0)) {
|
|
||||||
bool isCover = false;
|
|
||||||
std::string coverItemId;
|
|
||||||
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "name") == 0 && strcmp(atts[i + 1], "cover") == 0) {
|
|
||||||
isCover = true;
|
|
||||||
} else if (strcmp(atts[i], "content") == 0) {
|
|
||||||
coverItemId = atts[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCover) {
|
|
||||||
self->coverItemId = coverItemId;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) {
|
|
||||||
std::string itemId;
|
|
||||||
std::string href;
|
|
||||||
std::string mediaType;
|
|
||||||
std::string properties;
|
|
||||||
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "id") == 0) {
|
|
||||||
itemId = atts[i + 1];
|
|
||||||
} else if (strcmp(atts[i], "href") == 0) {
|
|
||||||
href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(self->baseContentPath + atts[i + 1]));
|
|
||||||
} else if (strcmp(atts[i], "media-type") == 0) {
|
|
||||||
mediaType = atts[i + 1];
|
|
||||||
} else if (strcmp(atts[i], "properties") == 0) {
|
|
||||||
properties = atts[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record index entry for fast lookup later
|
|
||||||
if (self->tempItemStore) {
|
|
||||||
ItemIndexEntry entry;
|
|
||||||
entry.idHash = fnvHash(itemId);
|
|
||||||
entry.idLen = static_cast<uint16_t>(itemId.size());
|
|
||||||
entry.fileOffset = static_cast<uint32_t>(self->tempItemStore.position());
|
|
||||||
self->itemIndex.push_back(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write items down to SD card
|
|
||||||
serialization::writeString(self->tempItemStore, itemId);
|
|
||||||
serialization::writeString(self->tempItemStore, href);
|
|
||||||
|
|
||||||
if (itemId == self->coverItemId) {
|
|
||||||
// Some EPUBs set meta name="cover" to an XHTML wrapper item.
|
|
||||||
// Only treat it as a cover image when the manifest media-type is image/*.
|
|
||||||
if (startsWithImageMediaType(mediaType)) {
|
|
||||||
self->coverItemHref = href;
|
|
||||||
} else {
|
|
||||||
LOG_DBG("COF", "Ignoring meta cover item '%s' with non-image media type: %s", itemId.c_str(),
|
|
||||||
mediaType.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType == MEDIA_TYPE_NCX) {
|
|
||||||
if (self->tocNcxPath.empty()) {
|
|
||||||
self->tocNcxPath = href;
|
|
||||||
} else {
|
|
||||||
LOG_DBG("COF", "Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s", href.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect CSS files
|
|
||||||
if (mediaType == MEDIA_TYPE_CSS) {
|
|
||||||
self->cssFiles.push_back(href);
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPUB 3: Check for nav document (properties contains "nav")
|
|
||||||
if (!properties.empty() && self->tocNavPath.empty()) {
|
|
||||||
// Properties is space-separated, check if "nav" is present as a word
|
|
||||||
if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) {
|
|
||||||
self->tocNavPath = href;
|
|
||||||
LOG_DBG("COF", "Found EPUB 3 nav document: %s", href.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPUB 3: Check for cover image (properties contains "cover-image")
|
|
||||||
if (!properties.empty() && self->coverItemHref.empty()) {
|
|
||||||
if (properties == "cover-image" || properties.find("cover-image ") == 0 ||
|
|
||||||
properties.find(" cover-image") != std::string::npos) {
|
|
||||||
self->coverItemHref = href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This relies on spine appearing after item manifest (which is pretty safe as it's part of the EPUB spec)
|
|
||||||
// Only run the spine parsing if there's a cache to add it to
|
|
||||||
if (self->cache) {
|
|
||||||
if (self->state == IN_SPINE && (strcmp(name, "itemref") == 0 || strcmp(name, "opf:itemref") == 0)) {
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "idref") == 0) {
|
|
||||||
const std::string idref = atts[i + 1];
|
|
||||||
std::string href;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
if (self->useItemIndex) {
|
|
||||||
// Fast path: binary search
|
|
||||||
uint32_t targetHash = fnvHash(idref);
|
|
||||||
uint16_t targetLen = static_cast<uint16_t>(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 {
|
|
||||||
// Fallback linear scan, only reached when the index is empty (no manifest
|
|
||||||
// items). The fast binary-search path above is used for all real manifests.
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// parse the guide
|
|
||||||
if (self->state == IN_GUIDE && (strcmp(name, "reference") == 0 || strcmp(name, "opf:reference") == 0)) {
|
|
||||||
std::string type;
|
|
||||||
std::string guideHref;
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "type") == 0) {
|
|
||||||
type = atts[i + 1];
|
|
||||||
} else if (strcmp(atts[i], "href") == 0) {
|
|
||||||
guideHref = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(self->baseContentPath + atts[i + 1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!guideHref.empty()) {
|
|
||||||
if (type == "text" || (type == "start" && !self->textReferenceHref.empty())) {
|
|
||||||
LOG_DBG("COF", "Found %s reference in guide: %s", type.c_str(), guideHref.c_str());
|
|
||||||
self->textReferenceHref = guideHref;
|
|
||||||
} else if ((type == "cover" || type == "cover-page") && self->guideCoverPageHref.empty()) {
|
|
||||||
LOG_DBG("COF", "Found cover reference in guide: %s", guideHref.c_str());
|
|
||||||
self->guideCoverPageHref = guideHref;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s, const int len) {
|
|
||||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_TITLE) {
|
|
||||||
self->title.append(s, len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_AUTHOR) {
|
|
||||||
if (!self->author.empty()) {
|
|
||||||
self->author.append(", "); // Add separator for multiple authors
|
|
||||||
}
|
|
||||||
self->author.append(s, len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_LANGUAGE) {
|
|
||||||
self->language.append(s, len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL ContentOpfParser::endElement(void* userData, const XML_Char* name) {
|
|
||||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
|
||||||
(void)name;
|
|
||||||
|
|
||||||
if (self->state == IN_SPINE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
|
||||||
self->state = IN_PACKAGE;
|
|
||||||
self->tempItemStore.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_GUIDE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
|
||||||
self->state = IN_PACKAGE;
|
|
||||||
self->tempItemStore.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_MANIFEST && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
|
||||||
self->state = IN_PACKAGE;
|
|
||||||
self->tempItemStore.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_TITLE && strcmp(name, "dc:title") == 0) {
|
|
||||||
self->state = IN_METADATA;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_AUTHOR && strcmp(name, "dc:creator") == 0) {
|
|
||||||
self->state = IN_METADATA;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_BOOK_LANGUAGE && strcmp(name, "dc:language") == 0) {
|
|
||||||
self->state = IN_METADATA;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_METADATA && (strcmp(name, "metadata") == 0 || strcmp(name, "opf:metadata") == 0)) {
|
|
||||||
self->state = IN_PACKAGE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "package") == 0 || strcmp(name, "opf:package") == 0)) {
|
|
||||||
self->state = START;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <deque>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Epub.h"
|
|
||||||
#include "expat.h"
|
|
||||||
|
|
||||||
class BookMetadataCache;
|
|
||||||
|
|
||||||
class ContentOpfParser final : public Print {
|
|
||||||
enum ParserState {
|
|
||||||
START,
|
|
||||||
IN_PACKAGE,
|
|
||||||
IN_METADATA,
|
|
||||||
IN_BOOK_TITLE,
|
|
||||||
IN_BOOK_AUTHOR,
|
|
||||||
IN_BOOK_LANGUAGE,
|
|
||||||
IN_MANIFEST,
|
|
||||||
IN_SPINE,
|
|
||||||
IN_GUIDE,
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::string& cachePath;
|
|
||||||
const std::string& baseContentPath;
|
|
||||||
size_t remainingSize;
|
|
||||||
XML_Parser parser = nullptr;
|
|
||||||
ParserState state = START;
|
|
||||||
BookMetadataCache* cache;
|
|
||||||
HalFile tempItemStore;
|
|
||||||
std::string coverItemId;
|
|
||||||
|
|
||||||
// Index for fast idref→href lookup (binary search over .items.bin)
|
|
||||||
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::deque<ItemIndexEntry> itemIndex;
|
|
||||||
bool useItemIndex = false;
|
|
||||||
|
|
||||||
// FNV-1a hash function
|
|
||||||
static uint32_t fnvHash(const std::string& s) {
|
|
||||||
uint32_t hash = 2166136261u;
|
|
||||||
for (char c : s) {
|
|
||||||
hash ^= static_cast<uint8_t>(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);
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::string title;
|
|
||||||
std::string author;
|
|
||||||
std::string language;
|
|
||||||
std::string tocNcxPath;
|
|
||||||
std::string tocNavPath; // EPUB 3 nav document path
|
|
||||||
std::string coverItemHref;
|
|
||||||
std::string guideCoverPageHref; // Guide reference with type="cover" or "cover-page" (points to XHTML wrapper)
|
|
||||||
std::string textReferenceHref;
|
|
||||||
std::vector<std::string> cssFiles; // CSS stylesheet paths
|
|
||||||
|
|
||||||
explicit ContentOpfParser(const std::string& cachePath, const std::string& baseContentPath, const size_t xmlSize,
|
|
||||||
BookMetadataCache* cache)
|
|
||||||
: cachePath(cachePath), baseContentPath(baseContentPath), remainingSize(xmlSize), cache(cache) {}
|
|
||||||
~ContentOpfParser() override;
|
|
||||||
|
|
||||||
bool setup();
|
|
||||||
|
|
||||||
size_t write(uint8_t) override;
|
|
||||||
size_t write(const uint8_t* buffer, size_t size) override;
|
|
||||||
};
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
#include "TocNavParser.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <XmlParserUtils.h>
|
|
||||||
|
|
||||||
#include "Epub/BookMetadataCache.h"
|
|
||||||
|
|
||||||
bool TocNavParser::setup() {
|
|
||||||
parser = XML_ParserCreate(nullptr);
|
|
||||||
if (!parser) {
|
|
||||||
LOG_DBG("NAV", "Couldn't allocate memory for parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
XML_SetUserData(parser, this);
|
|
||||||
XML_SetElementHandler(parser, startElement, endElement);
|
|
||||||
XML_SetCharacterDataHandler(parser, characterData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TocNavParser::~TocNavParser() { destroyXmlParser(parser); }
|
|
||||||
|
|
||||||
size_t TocNavParser::write(const uint8_t data) { return write(&data, 1); }
|
|
||||||
|
|
||||||
size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
|
|
||||||
if (!parser) return 0;
|
|
||||||
|
|
||||||
const uint8_t* currentBufferPos = buffer;
|
|
||||||
auto remainingInBuffer = size;
|
|
||||||
|
|
||||||
while (remainingInBuffer > 0) {
|
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
|
||||||
if (!buf) {
|
|
||||||
LOG_DBG("NAV", "Couldn't allocate memory for buffer");
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
|
||||||
memcpy(buf, currentBufferPos, toRead);
|
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
|
||||||
LOG_DBG("NAV", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBufferPos += toRead;
|
|
||||||
remainingInBuffer -= toRead;
|
|
||||||
remainingSize -= toRead;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNavParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
|
||||||
auto* self = static_cast<TocNavParser*>(userData);
|
|
||||||
|
|
||||||
// Track HTML structure loosely - we mainly care about finding <nav epub:type="toc">
|
|
||||||
if (strcmp(name, "html") == 0) {
|
|
||||||
self->state = IN_HTML;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_HTML && strcmp(name, "body") == 0) {
|
|
||||||
self->state = IN_BODY;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for <nav epub:type="toc"> anywhere in body (or nested elements)
|
|
||||||
if (self->state >= IN_BODY && strcmp(name, "nav") == 0) {
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) {
|
|
||||||
self->state = IN_NAV_TOC;
|
|
||||||
LOG_DBG("NAV", "Found nav toc element");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only process ol/li/a if we're inside the toc nav
|
|
||||||
if (self->state < IN_NAV_TOC) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(name, "ol") == 0) {
|
|
||||||
self->olDepth++;
|
|
||||||
self->state = IN_OL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_OL && strcmp(name, "li") == 0) {
|
|
||||||
self->state = IN_LI;
|
|
||||||
self->currentLabel.clear();
|
|
||||||
self->currentHref.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_LI && strcmp(name, "a") == 0) {
|
|
||||||
self->state = IN_ANCHOR;
|
|
||||||
// Get href attribute
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "href") == 0) {
|
|
||||||
self->currentHref = atts[i + 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNavParser::characterData(void* userData, const XML_Char* s, const int len) {
|
|
||||||
auto* self = static_cast<TocNavParser*>(userData);
|
|
||||||
|
|
||||||
// Only collect text when inside an anchor within the TOC nav
|
|
||||||
if (self->state == IN_ANCHOR) {
|
|
||||||
self->currentLabel.append(s, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) {
|
|
||||||
auto* self = static_cast<TocNavParser*>(userData);
|
|
||||||
|
|
||||||
if (strcmp(name, "a") == 0 && self->state == IN_ANCHOR) {
|
|
||||||
// Create TOC entry when closing anchor tag (we have all data now)
|
|
||||||
if (!self->currentLabel.empty() && !self->currentHref.empty()) {
|
|
||||||
const std::string rawTarget = self->baseContentPath + self->currentHref;
|
|
||||||
const size_t pos = rawTarget.find('#');
|
|
||||||
const std::string rawPath = pos == std::string::npos ? rawTarget : rawTarget.substr(0, pos);
|
|
||||||
std::string href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(rawPath));
|
|
||||||
std::string anchor;
|
|
||||||
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
anchor = FsHelpers::decodeUriEscapes(rawTarget.substr(pos + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->cache) {
|
|
||||||
// olDepth gives us the nesting level (1-based from the outer ol)
|
|
||||||
self->cache->createTocEntry(self->currentLabel, href, anchor, self->olDepth);
|
|
||||||
}
|
|
||||||
|
|
||||||
self->currentLabel.clear();
|
|
||||||
self->currentHref.clear();
|
|
||||||
}
|
|
||||||
self->state = IN_LI;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(name, "li") == 0 && (self->state == IN_LI || self->state == IN_OL)) {
|
|
||||||
self->state = IN_OL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(name, "ol") == 0 && self->state >= IN_NAV_TOC) {
|
|
||||||
self->olDepth--;
|
|
||||||
if (self->olDepth == 0) {
|
|
||||||
self->state = IN_NAV_TOC;
|
|
||||||
} else {
|
|
||||||
self->state = IN_LI; // Back to parent li
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) {
|
|
||||||
self->state = IN_BODY;
|
|
||||||
LOG_DBG("NAV", "Finished parsing nav toc");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Print.h>
|
|
||||||
#include <expat.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class BookMetadataCache;
|
|
||||||
|
|
||||||
// Parser for EPUB 3 nav.xhtml navigation documents
|
|
||||||
// Parses HTML5 nav elements with epub:type="toc" to extract table of contents
|
|
||||||
class TocNavParser final : public Print {
|
|
||||||
enum ParserState {
|
|
||||||
START,
|
|
||||||
IN_HTML,
|
|
||||||
IN_BODY,
|
|
||||||
IN_NAV_TOC, // Inside <nav epub:type="toc">
|
|
||||||
IN_OL, // Inside <ol>
|
|
||||||
IN_LI, // Inside <li>
|
|
||||||
IN_ANCHOR, // Inside <a>
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::string& baseContentPath;
|
|
||||||
size_t remainingSize;
|
|
||||||
XML_Parser parser = nullptr;
|
|
||||||
ParserState state = START;
|
|
||||||
BookMetadataCache* cache;
|
|
||||||
|
|
||||||
// Track nesting depth for <ol> elements to determine TOC depth
|
|
||||||
uint8_t olDepth = 0;
|
|
||||||
// Current entry data being collected
|
|
||||||
std::string currentLabel;
|
|
||||||
std::string currentHref;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit TocNavParser(const std::string& baseContentPath, const size_t xmlSize, BookMetadataCache* cache)
|
|
||||||
: baseContentPath(baseContentPath), remainingSize(xmlSize), cache(cache) {}
|
|
||||||
~TocNavParser() override;
|
|
||||||
|
|
||||||
bool setup();
|
|
||||||
|
|
||||||
size_t write(uint8_t) override;
|
|
||||||
size_t write(const uint8_t* buffer, size_t size) override;
|
|
||||||
};
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
#include "TocNcxParser.h"
|
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <Logging.h>
|
|
||||||
#include <XmlParserUtils.h>
|
|
||||||
|
|
||||||
#include "Epub/BookMetadataCache.h"
|
|
||||||
|
|
||||||
bool TocNcxParser::setup() {
|
|
||||||
parser = XML_ParserCreate(nullptr);
|
|
||||||
if (!parser) {
|
|
||||||
LOG_DBG("TOC", "Couldn't allocate memory for parser");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
XML_SetUserData(parser, this);
|
|
||||||
XML_SetElementHandler(parser, startElement, endElement);
|
|
||||||
XML_SetCharacterDataHandler(parser, characterData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TocNcxParser::~TocNcxParser() { destroyXmlParser(parser); }
|
|
||||||
|
|
||||||
size_t TocNcxParser::write(const uint8_t data) { return write(&data, 1); }
|
|
||||||
|
|
||||||
size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
|
|
||||||
if (!parser) return 0;
|
|
||||||
|
|
||||||
const uint8_t* currentBufferPos = buffer;
|
|
||||||
auto remainingInBuffer = size;
|
|
||||||
|
|
||||||
while (remainingInBuffer > 0) {
|
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
|
||||||
if (!buf) {
|
|
||||||
LOG_DBG("TOC", "Couldn't allocate memory for buffer");
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
|
||||||
memcpy(buf, currentBufferPos, toRead);
|
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
|
||||||
LOG_DBG("TOC", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
|
||||||
destroyXmlParser(parser);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBufferPos += toRead;
|
|
||||||
remainingInBuffer -= toRead;
|
|
||||||
remainingSize -= toRead;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNcxParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
|
||||||
// NOTE: We rely on navPoint label and content coming before any nested navPoints, this will be fine:
|
|
||||||
// <navPoint>
|
|
||||||
// <navLabel><text>Chapter 1</text></navLabel>
|
|
||||||
// <content src="ch1.html"/>
|
|
||||||
// <navPoint> ...nested... </navPoint>
|
|
||||||
// </navPoint>
|
|
||||||
//
|
|
||||||
// This will NOT:
|
|
||||||
// <navPoint>
|
|
||||||
// <navPoint> ...nested... </navPoint>
|
|
||||||
// <navLabel><text>Chapter 1</text></navLabel>
|
|
||||||
// <content src="ch1.html"/>
|
|
||||||
// </navPoint>
|
|
||||||
|
|
||||||
auto* self = static_cast<TocNcxParser*>(userData);
|
|
||||||
|
|
||||||
if (self->state == START && strcmp(name, "ncx") == 0) {
|
|
||||||
self->state = IN_NCX;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NCX && strcmp(name, "navMap") == 0) {
|
|
||||||
self->state = IN_NAV_MAP;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles both top-level and nested navPoints
|
|
||||||
if ((self->state == IN_NAV_MAP || self->state == IN_NAV_POINT) && strcmp(name, "navPoint") == 0) {
|
|
||||||
self->state = IN_NAV_POINT;
|
|
||||||
self->currentDepth++;
|
|
||||||
|
|
||||||
self->currentLabel.clear();
|
|
||||||
self->currentSrc.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_POINT && strcmp(name, "navLabel") == 0) {
|
|
||||||
self->state = IN_NAV_LABEL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_LABEL && strcmp(name, "text") == 0) {
|
|
||||||
self->state = IN_NAV_LABEL_TEXT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_POINT && strcmp(name, "content") == 0) {
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "src") == 0) {
|
|
||||||
self->currentSrc = atts[i + 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNcxParser::characterData(void* userData, const XML_Char* s, const int len) {
|
|
||||||
auto* self = static_cast<TocNcxParser*>(userData);
|
|
||||||
if (self->state == IN_NAV_LABEL_TEXT) {
|
|
||||||
self->currentLabel.append(s, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLCALL TocNcxParser::endElement(void* userData, const XML_Char* name) {
|
|
||||||
auto* self = static_cast<TocNcxParser*>(userData);
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_LABEL_TEXT && strcmp(name, "text") == 0) {
|
|
||||||
self->state = IN_NAV_LABEL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_LABEL && strcmp(name, "navLabel") == 0) {
|
|
||||||
self->state = IN_NAV_POINT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_POINT && strcmp(name, "navPoint") == 0) {
|
|
||||||
self->currentDepth--;
|
|
||||||
if (self->currentDepth == 0) {
|
|
||||||
self->state = IN_NAV_MAP;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->state == IN_NAV_POINT && strcmp(name, "content") == 0) {
|
|
||||||
// At this point (end of content tag), we likely have both Label (from previous tags) and Src.
|
|
||||||
// This is the safest place to push the data, assuming <navLabel> always comes before <content>.
|
|
||||||
// NCX spec says navLabel comes before content.
|
|
||||||
if (!self->currentLabel.empty() && !self->currentSrc.empty()) {
|
|
||||||
const std::string rawTarget = self->baseContentPath + self->currentSrc;
|
|
||||||
const size_t pos = rawTarget.find('#');
|
|
||||||
const std::string rawPath = pos == std::string::npos ? rawTarget : rawTarget.substr(0, pos);
|
|
||||||
std::string href = FsHelpers::normalisePath(FsHelpers::decodeUriEscapes(rawPath));
|
|
||||||
std::string anchor;
|
|
||||||
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
anchor = FsHelpers::decodeUriEscapes(rawTarget.substr(pos + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self->cache) {
|
|
||||||
self->cache->createTocEntry(self->currentLabel, href, anchor, self->currentDepth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear them so we don't re-add them if there are weird XML structures
|
|
||||||
self->currentLabel.clear();
|
|
||||||
self->currentSrc.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Print.h>
|
|
||||||
#include <expat.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class BookMetadataCache;
|
|
||||||
|
|
||||||
class TocNcxParser final : public Print {
|
|
||||||
enum ParserState { START, IN_NCX, IN_NAV_MAP, IN_NAV_POINT, IN_NAV_LABEL, IN_NAV_LABEL_TEXT, IN_CONTENT };
|
|
||||||
|
|
||||||
const std::string& baseContentPath;
|
|
||||||
size_t remainingSize;
|
|
||||||
XML_Parser parser = nullptr;
|
|
||||||
ParserState state = START;
|
|
||||||
BookMetadataCache* cache;
|
|
||||||
|
|
||||||
std::string currentLabel;
|
|
||||||
std::string currentSrc;
|
|
||||||
uint8_t currentDepth = 0;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit TocNcxParser(const std::string& baseContentPath, const size_t xmlSize, BookMetadataCache* cache)
|
|
||||||
: baseContentPath(baseContentPath), remainingSize(xmlSize), cache(cache) {}
|
|
||||||
~TocNcxParser() override;
|
|
||||||
|
|
||||||
bool setup();
|
|
||||||
|
|
||||||
size_t write(uint8_t) override;
|
|
||||||
size_t write(const uint8_t* buffer, size_t size) override;
|
|
||||||
};
|
|
||||||
@@ -62,14 +62,7 @@ void FontCacheManager::resetStats() {
|
|||||||
bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
|
bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
|
||||||
|
|
||||||
void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) {
|
void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) {
|
||||||
if (!text) return;
|
scanText_ += text;
|
||||||
const size_t remaining = (scanTextLen_ < SCAN_TEXT_CAPACITY - 1) ? (SCAN_TEXT_CAPACITY - 1 - scanTextLen_) : 0;
|
|
||||||
if (remaining > 0) {
|
|
||||||
const size_t textLen = strnlen(text, remaining);
|
|
||||||
memcpy(scanText_ + scanTextLen_, text, textLen);
|
|
||||||
scanTextLen_ += textLen;
|
|
||||||
scanText_[scanTextLen_] = '\0';
|
|
||||||
}
|
|
||||||
if (scanFontId_ < 0) scanFontId_ = fontId;
|
if (scanFontId_ < 0) scanFontId_ = fontId;
|
||||||
const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
|
const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
|
||||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
|
||||||
@@ -87,15 +80,15 @@ FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manage
|
|||||||
manager_->scanMode_ = ScanMode::Scanning;
|
manager_->scanMode_ = ScanMode::Scanning;
|
||||||
manager_->clearCache();
|
manager_->clearCache();
|
||||||
manager_->resetStats();
|
manager_->resetStats();
|
||||||
manager_->scanTextLen_ = 0;
|
manager_->scanText_.clear();
|
||||||
manager_->scanText_[0] = '\0';
|
manager_->scanText_.reserve(2048); // Pre-allocate to avoid heap fragmentation from repeated concat
|
||||||
memset(manager_->scanStyleCounts_, 0, sizeof(manager_->scanStyleCounts_));
|
memset(manager_->scanStyleCounts_, 0, sizeof(manager_->scanStyleCounts_));
|
||||||
manager_->scanFontId_ = -1;
|
manager_->scanFontId_ = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
|
void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
|
||||||
manager_->scanMode_ = ScanMode::None;
|
manager_->scanMode_ = ScanMode::None;
|
||||||
if (manager_->scanTextLen_ == 0) return;
|
if (manager_->scanText_.empty()) return;
|
||||||
|
|
||||||
// Build style bitmask from all styles that appeared during the scan
|
// Build style bitmask from all styles that appeared during the scan
|
||||||
uint8_t styleMask = 0;
|
uint8_t styleMask = 0;
|
||||||
@@ -104,10 +97,11 @@ void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
|
|||||||
}
|
}
|
||||||
if (styleMask == 0) styleMask = 1; // default to regular
|
if (styleMask == 0) styleMask = 1; // default to regular
|
||||||
|
|
||||||
manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_, styleMask);
|
manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_.c_str(), styleMask);
|
||||||
|
|
||||||
manager_->scanTextLen_ = 0;
|
// Free scan string memory
|
||||||
manager_->scanText_[0] = '\0';
|
manager_->scanText_.clear();
|
||||||
|
manager_->scanText_.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
FontCacheManager::PrewarmScope::~PrewarmScope() {
|
FontCacheManager::PrewarmScope::~PrewarmScope() {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
#include <EpdFontFamily.h>
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class FontDecompressor;
|
class FontDecompressor;
|
||||||
class SdCardFont;
|
class SdCardFont;
|
||||||
@@ -51,9 +51,7 @@ class FontCacheManager {
|
|||||||
|
|
||||||
enum class ScanMode : uint8_t { None, Scanning };
|
enum class ScanMode : uint8_t { None, Scanning };
|
||||||
ScanMode scanMode_ = ScanMode::None;
|
ScanMode scanMode_ = ScanMode::None;
|
||||||
static constexpr size_t SCAN_TEXT_CAPACITY = 2048;
|
std::string scanText_;
|
||||||
char scanText_[SCAN_TEXT_CAPACITY] = {};
|
|
||||||
size_t scanTextLen_ = 0;
|
|
||||||
uint32_t scanStyleCounts_[4] = {};
|
uint32_t scanStyleCounts_[4] = {};
|
||||||
int scanFontId_ = -1;
|
int scanFontId_ = -1;
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-128
@@ -1,7 +1,6 @@
|
|||||||
#include "GfxRenderer.h"
|
#include "GfxRenderer.h"
|
||||||
|
|
||||||
#include <BidiUtils.h>
|
#include <BidiUtils.h>
|
||||||
#include <BuildScratch.h>
|
|
||||||
#include <FontDecompressor.h>
|
#include <FontDecompressor.h>
|
||||||
#include <HalGPIO.h>
|
#include <HalGPIO.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
@@ -25,36 +24,6 @@ uint8_t resolveSdCardStyle(const SdCardFont& font, const EpdFontFamily::Style st
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const char* resolveVisualText(const char* text, std::string& visualBuffer, BidiUtils::BidiBaseDir baseDir);
|
const char* resolveVisualText(const char* text, std::string& visualBuffer, BidiUtils::BidiBaseDir baseDir);
|
||||||
|
|
||||||
// Appends the shaped visual form of every RTL token in `text` to `shapedOut`.
|
|
||||||
// getTextAdvanceX() measures the bidi-reordered, Arabic-shaped codepoint stream,
|
|
||||||
// so the SD advance table must be warmed with the presentation forms as well as
|
|
||||||
// the logical codepoints — otherwise every RTL word measurement misses the fast
|
|
||||||
// path and falls through to onGlyphMiss(), which opens the .cpfont and reads
|
|
||||||
// glyph metadata + bitmap into the 8-slot overflow ring, once per glyph.
|
|
||||||
// Tokens without RTL lead bytes (0xD6-0xDB) are skipped with a byte scan, so
|
|
||||||
// pure-LTR text pays almost nothing.
|
|
||||||
void appendShapedRtlTokens(const char* text, std::string& shapedOut) {
|
|
||||||
const auto isBreak = [](const char c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t'; };
|
|
||||||
std::string token;
|
|
||||||
std::string visual;
|
|
||||||
const char* p = text;
|
|
||||||
while (*p) {
|
|
||||||
while (*p && isBreak(*p)) ++p;
|
|
||||||
const char* start = p;
|
|
||||||
bool hasRtlBytes = false;
|
|
||||||
while (*p && !isBreak(*p)) {
|
|
||||||
const auto b = static_cast<unsigned char>(*p);
|
|
||||||
hasRtlBytes = hasRtlBytes || (b >= 0xD6 && b <= 0xDB);
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
if (!hasRtlBytes) continue;
|
|
||||||
token.assign(start, p - start);
|
|
||||||
if (BidiUtils::applyBidiVisual(token.c_str(), visual, static_cast<int>(BidiUtils::BidiBaseDir::AUTO))) {
|
|
||||||
shapedOut += visual;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
|
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
|
||||||
@@ -88,9 +57,7 @@ const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const Ep
|
|||||||
void GfxRenderer::ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask) const {
|
void GfxRenderer::ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask) const {
|
||||||
auto it = sdCardFonts_.find(fontId);
|
auto it = sdCardFonts_.find(fontId);
|
||||||
if (it != sdCardFonts_.end()) {
|
if (it != sdCardFonts_.end()) {
|
||||||
std::string shaped;
|
int missed = it->second->buildAdvanceTable(utf8Text, styleMask);
|
||||||
appendShapedRtlTokens(utf8Text, shaped);
|
|
||||||
int missed = it->second->buildAdvanceTable(utf8Text, styleMask, shaped.empty() ? nullptr : shaped.c_str());
|
|
||||||
if (missed > 0) {
|
if (missed > 0) {
|
||||||
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
||||||
}
|
}
|
||||||
@@ -104,12 +71,7 @@ void GfxRenderer::ensureSdCardFontReady(int fontId, const std::vector<std::strin
|
|||||||
// Augment the persistent advance-only table for layout measurement.
|
// Augment the persistent advance-only table for layout measurement.
|
||||||
// The table survives across paragraphs/sections (capped per font), so
|
// The table survives across paragraphs/sections (capped per font), so
|
||||||
// repeated indexing of the same SD font amortizes glyph-metric SD reads.
|
// repeated indexing of the same SD font amortizes glyph-metric SD reads.
|
||||||
std::string shaped;
|
int missed = it->second->buildAdvanceTable(words, includeHyphen, styleMask);
|
||||||
for (const auto& w : words) {
|
|
||||||
appendShapedRtlTokens(w.c_str(), shaped);
|
|
||||||
}
|
|
||||||
int missed =
|
|
||||||
it->second->buildAdvanceTable(words, includeHyphen, styleMask, shaped.empty() ? nullptr : shaped.c_str());
|
|
||||||
if (missed > 0) {
|
if (missed > 0) {
|
||||||
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
||||||
}
|
}
|
||||||
@@ -130,46 +92,19 @@ void GfxRenderer::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::releaseFrameBufferForBuild() {
|
void GfxRenderer::releaseFrameBufferForBuild() {
|
||||||
// Lend the framebuffer's bytes IN PLACE: the allocation is never freed, so
|
display.releaseFrameBuffers();
|
||||||
// it cannot move and repeated loans cannot fragment the heap (the previous
|
|
||||||
// free+realloc model measurably decayed the max contiguous block over a
|
|
||||||
// session). The bytes are deposited in the build-scratch registry so
|
|
||||||
// memory-hungry build phases (e.g. InflateStream's tinfl state + window)
|
|
||||||
// can claim them instead of allocating.
|
|
||||||
uint32_t size = 0;
|
|
||||||
uint8_t* scratch = display.lendFrameBufferStorage(&size);
|
|
||||||
frameBuffer = nullptr;
|
frameBuffer = nullptr;
|
||||||
if (scratch) {
|
|
||||||
buildscratch::lend(scratch, size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GfxRenderer::restoreFrameBufferAfterBuild() {
|
bool GfxRenderer::restoreFrameBufferAfterBuild() {
|
||||||
buildscratch::reclaim();
|
if (!display.reallocFrameBuffers()) {
|
||||||
display.returnFrameBufferStorage(); // cannot fail: the allocation was never freed
|
LOG_ERR("GFX", "Framebuffer realloc failed after build");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
frameBuffer = display.getFrameBuffer();
|
frameBuffer = display.getFrameBuffer();
|
||||||
return frameBuffer != nullptr;
|
return frameBuffer != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
GfxRenderer::FrameBufferLoan::FrameBufferLoan(GfxRenderer& renderer) : renderer_(renderer) {
|
|
||||||
// Nesting guard: if the framebuffer is already lent out (an outer loan),
|
|
||||||
// stay inert so this end() cannot return storage the outer loan still owns.
|
|
||||||
if (!renderer_.hasFrameBuffer()) return;
|
|
||||||
renderer_.releaseFrameBufferForBuild();
|
|
||||||
active_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxRenderer::FrameBufferLoan::end() {
|
|
||||||
if (!active_) return;
|
|
||||||
active_ = false;
|
|
||||||
if (!renderer_.restoreFrameBufferAfterBuild()) {
|
|
||||||
// Only reachable if the framebuffer never existed, which begin() already
|
|
||||||
// asserts against; kept as a backstop since running blind helps nobody.
|
|
||||||
LOG_ERR("GFX", "Framebuffer restore failed - restarting");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); }
|
bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); }
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
|
||||||
@@ -497,21 +432,18 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
uint32_t prevCp = 0;
|
uint32_t prevCp = 0;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&textCursor)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&textCursor)))) {
|
||||||
// RTL vowel marks (Hebrew niqqud, Arabic harakat) ride the combining-mark
|
// Skip Hebrew Niqqud (vowel marks)
|
||||||
// path: zero-advance overlays on the preceding base glyph (applyBidiVisual
|
// Temporary: avoid adding Niqqud to built-in fonts. Remove when custom fonts are supported.
|
||||||
// emits base-then-marks per UAX#9 L3). anchorFor pins position-sensitive
|
if (cp >= 0x0591 && cp <= 0x05C7) {
|
||||||
// niqqud (dagesh, shin/sin dots, holam) to their spot on the base; other
|
continue;
|
||||||
// marks stay centered, raised above the base or (kasra) at their
|
}
|
||||||
// font-native position. Fonts without their glyphs — the built-ins — miss
|
|
||||||
// the getGlyph lookup and skip them, as before.
|
if (utf8IsCombiningMark(cp)) {
|
||||||
if (utf8IsCombiningMark(cp) || BidiUtils::isTransparentMark(cp)) {
|
|
||||||
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
||||||
if (!combiningGlyph) continue;
|
if (!combiningGlyph) continue;
|
||||||
const auto anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
||||||
const int raiseBy =
|
const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left,
|
||||||
combiningMark::raiseAboveBase(anchor, combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
combiningGlyph->width);
|
||||||
const int combiningX = combiningMark::anchorOver(anchor, lastBaseX, lastBaseLeft, lastBaseWidth,
|
|
||||||
combiningGlyph->left, combiningGlyph->width);
|
|
||||||
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style);
|
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -553,6 +485,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
namespace {
|
namespace {
|
||||||
const char* resolveVisualText(const char* text, std::string& visualBuffer, const BidiUtils::BidiBaseDir baseDir) {
|
const char* resolveVisualText(const char* text, std::string& visualBuffer, const BidiUtils::BidiBaseDir baseDir) {
|
||||||
if (!text || *text == '\0') return text;
|
if (!text || *text == '\0') return text;
|
||||||
|
if (baseDir == BidiUtils::BidiBaseDir::NONE) return text; // caller supplies visual order
|
||||||
|
|
||||||
if (baseDir != BidiUtils::BidiBaseDir::RTL) {
|
if (baseDir != BidiUtils::BidiBaseDir::RTL) {
|
||||||
// Byte-level scan: skip BiDi when no RTL script lead bytes are present.
|
// Byte-level scan: skip BiDi when no RTL script lead bytes are present.
|
||||||
@@ -1451,20 +1384,6 @@ void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const
|
|||||||
display.displayBuffer(refreshMode, fadingFix);
|
display.displayBuffer(refreshMode, fadingFix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::displayBufferAsync(const HalDisplay::RefreshMode refreshMode) const {
|
|
||||||
// The async path has no turn-off-screen hook, which the sunlight fading fix
|
|
||||||
// relies on; keep those users on the blocking path.
|
|
||||||
if (fadingFix) {
|
|
||||||
display.displayBuffer(refreshMode, fadingFix);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
display.displayBufferAsync(refreshMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxRenderer::waitRefreshComplete() const { display.waitRefreshComplete(); }
|
|
||||||
|
|
||||||
bool GfxRenderer::supportsAsyncRefresh() const { return !fadingFix && display.supportsAsyncRefresh(); }
|
|
||||||
|
|
||||||
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
if (!text || maxWidth <= 0) return "";
|
if (!text || maxWidth <= 0) return "";
|
||||||
@@ -1711,15 +1630,6 @@ int GfxRenderer::getKerning(const int fontId, const uint32_t leftCp, const uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFamily::Style style) const {
|
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFamily::Style style) const {
|
||||||
// Measure the exact codepoint stream drawText renders: bidi-reordered and
|
|
||||||
// Arabic-shaped (contextual presentation forms, Lam-Alef collapse).
|
|
||||||
// Measuring the raw logical text counts the Alef a ligature absorbs and
|
|
||||||
// uses base-letter advances instead of presentation-form advances, so RTL
|
|
||||||
// lines come out wider than they draw — uneven word gaps and a ragged
|
|
||||||
// right margin.
|
|
||||||
std::string visual;
|
|
||||||
text = resolveVisualText(text, visual, BidiUtils::BidiBaseDir::AUTO);
|
|
||||||
|
|
||||||
// Advance table fast-path for SD card fonts during layout.
|
// Advance table fast-path for SD card fonts during layout.
|
||||||
// No kerning/ligature lookup — consistent with previous metadataOnly behavior
|
// No kerning/ligature lookup — consistent with previous metadataOnly behavior
|
||||||
// where kern/lig data was not loaded.
|
// where kern/lig data was not loaded.
|
||||||
@@ -1735,10 +1645,6 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
}
|
}
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
while (uint32_t cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text))) {
|
while (uint32_t cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text))) {
|
||||||
// RTL vowel marks (niqqud/harakat) are zero-advance overlays in drawText — no width.
|
|
||||||
if (BidiUtils::isTransparentMark(cp)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int32_t advFP = sdIt->second->getAdvance(cp, styleIdx);
|
int32_t advFP = sdIt->second->getAdvance(cp, styleIdx);
|
||||||
if (advFP == 0 && !utf8IsCombiningMark(cp)) {
|
if (advFP == 0 && !utf8IsCombiningMark(cp)) {
|
||||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
||||||
@@ -1761,10 +1667,6 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
|
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
// RTL vowel marks (niqqud/harakat) are zero-advance overlays in drawText — no width.
|
|
||||||
if (BidiUtils::isTransparentMark(cp)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (utf8IsCombiningMark(cp)) {
|
if (utf8IsCombiningMark(cp)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1841,21 +1743,18 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
uint32_t prevCp = 0;
|
uint32_t prevCp = 0;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
// RTL vowel marks (Hebrew niqqud, Arabic harakat) ride the combining-mark
|
// Skip Hebrew Niqqud (vowel marks)
|
||||||
// path: zero-advance overlays on the preceding base glyph (applyBidiVisual
|
// Temporary: avoid adding Niqqud to built-in fonts. Remove when custom fonts are supported.
|
||||||
// emits base-then-marks per UAX#9 L3). anchorFor pins position-sensitive
|
if (cp >= 0x0591 && cp <= 0x05C7) {
|
||||||
// niqqud (dagesh, shin/sin dots, holam) to their spot on the base; other
|
continue;
|
||||||
// marks stay centered, raised above the base or (kasra) at their
|
}
|
||||||
// font-native position. Fonts without their glyphs — the built-ins — miss
|
|
||||||
// the getGlyph lookup and skip them, as before.
|
if (utf8IsCombiningMark(cp)) {
|
||||||
if (utf8IsCombiningMark(cp) || BidiUtils::isTransparentMark(cp)) {
|
|
||||||
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
||||||
if (!combiningGlyph) continue;
|
if (!combiningGlyph) continue;
|
||||||
const auto anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
||||||
const int raiseBy =
|
|
||||||
combiningMark::raiseAboveBase(anchor, combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
|
||||||
const int combiningX = x - raiseBy;
|
const int combiningX = x - raiseBy;
|
||||||
const int combiningY = combiningMark::anchorOverRotated90CW(anchor, lastBaseY, lastBaseLeft, lastBaseWidth,
|
const int combiningY = combiningMark::centerOverRotated90CW(lastBaseY, lastBaseLeft, lastBaseWidth,
|
||||||
combiningGlyph->left, combiningGlyph->width);
|
combiningGlyph->left, combiningGlyph->width);
|
||||||
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, combiningX, combiningY, black, style);
|
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, combiningX, combiningY, black, style);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ namespace BidiUtils {
|
|||||||
// AUTO: scan text for first strong directional character (P2/P3 rules)
|
// AUTO: scan text for first strong directional character (P2/P3 rules)
|
||||||
// LTR: force left-to-right paragraph embedding level
|
// LTR: force left-to-right paragraph embedding level
|
||||||
// RTL: force right-to-left paragraph embedding level
|
// RTL: force right-to-left paragraph embedding level
|
||||||
enum class BidiBaseDir : signed char { AUTO = -1, LTR = 0, RTL = 1 };
|
// NONE: text is already in visual order (e.g. FreeInkBook page runs, which
|
||||||
|
// bake UAX#9 reordering and Arabic shaping in at layout time) — draw
|
||||||
|
// byte-for-byte, never reorder
|
||||||
|
enum class BidiBaseDir : signed char { NONE = -2, AUTO = -1, LTR = 0, RTL = 1 };
|
||||||
} // namespace BidiUtils
|
} // namespace BidiUtils
|
||||||
|
|
||||||
class FontCacheManager;
|
class FontCacheManager;
|
||||||
@@ -135,17 +138,6 @@ class GfxRenderer {
|
|||||||
int getScreenWidth() const;
|
int getScreenWidth() const;
|
||||||
int getScreenHeight() const;
|
int getScreenHeight() const;
|
||||||
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
||||||
// Non-blocking refresh: starts the waveform and returns so CPU work (e.g.
|
|
||||||
// grayscale strip rendering) can overlap the panel's refresh time. The
|
|
||||||
// framebuffer must stay untouched until waitRefreshComplete(). Falls back to
|
|
||||||
// a blocking refresh when fadingFix is enabled or the panel lacks deferral
|
|
||||||
// support. See HalDisplay::displayBufferAsync for the baseline contract.
|
|
||||||
void displayBufferAsync(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
|
||||||
void waitRefreshComplete() const;
|
|
||||||
// True when displayBufferAsync() genuinely overlaps: panel defers and
|
|
||||||
// fadingFix isn't forcing the blocking path. Callers can skip overlap
|
|
||||||
// scaffolding (e.g. whole-plane grayscale buffers) when false.
|
|
||||||
bool supportsAsyncRefresh() const;
|
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
// void displayWindow(int x, int y, int width, int height) const;
|
// void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
@@ -261,34 +253,15 @@ class GfxRenderer {
|
|||||||
// Font helpers
|
// Font helpers
|
||||||
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
||||||
|
|
||||||
// Lend the 48 KB framebuffer's bytes to a memory-hungry phase (chapter
|
// Lend the 48 KB framebuffer to a memory-hungry phase (chapter builds).
|
||||||
// builds) WITHOUT freeing the allocation, so it never moves and repeated
|
// Between release and a successful restore NOTHING may draw or display —
|
||||||
// loans cannot fragment the heap. Between release and restore NOTHING may
|
// the panel keeps showing its last refreshed image. restore returns the
|
||||||
// draw or display — the panel keeps showing its last refreshed image. The
|
// buffer white, so the caller must redraw the full screen; false means the
|
||||||
// lent bytes are published via buildscratch::claim() for consumers like
|
// heap could not re-supply the buffer (callers treat that as fatal).
|
||||||
// InflateStream. restore returns the buffer white, so the caller must
|
|
||||||
// redraw the full screen; it cannot fail (no allocation involved).
|
|
||||||
void releaseFrameBufferForBuild();
|
void releaseFrameBufferForBuild();
|
||||||
bool restoreFrameBufferAfterBuild();
|
bool restoreFrameBufferAfterBuild();
|
||||||
bool hasFrameBuffer() const { return frameBuffer != nullptr; }
|
bool hasFrameBuffer() const { return frameBuffer != nullptr; }
|
||||||
|
|
||||||
// RAII form of the loan above, for blocking build regions with early-return
|
|
||||||
// error paths: restores on scope exit (or explicitly via end()). Display the
|
|
||||||
// popup/screen the panel should hold BEFORE constructing one. Constructing
|
|
||||||
// while the framebuffer is already lent yields an inert loan (nesting-safe).
|
|
||||||
class FrameBufferLoan {
|
|
||||||
public:
|
|
||||||
explicit FrameBufferLoan(GfxRenderer& renderer);
|
|
||||||
~FrameBufferLoan() { end(); }
|
|
||||||
void end();
|
|
||||||
FrameBufferLoan(const FrameBufferLoan&) = delete;
|
|
||||||
FrameBufferLoan& operator=(const FrameBufferLoan&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GfxRenderer& renderer_;
|
|
||||||
bool active_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
size_t getBufferSize() const;
|
size_t getBufferSize() const;
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ STR_USERNAME: "Імя карыстальніка"
|
|||||||
STR_PASSWORD: "Пароль"
|
STR_PASSWORD: "Пароль"
|
||||||
STR_SYNC_SERVER_URL: "URL сервера сінхранізацыі"
|
STR_SYNC_SERVER_URL: "URL сервера сінхранізацыі"
|
||||||
STR_DOCUMENT_MATCHING: "Супастаўленне дакументаў"
|
STR_DOCUMENT_MATCHING: "Супастаўленне дакументаў"
|
||||||
STR_SEND_METADATA: "Адпраўляць метаданыя дакумента"
|
|
||||||
STR_AUTHENTICATE: "Аўтарызацыя"
|
STR_AUTHENTICATE: "Аўтарызацыя"
|
||||||
STR_KOREADER_USERNAME: "Імя карыстальніка KOReader"
|
STR_KOREADER_USERNAME: "Імя карыстальніка KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
||||||
@@ -301,5 +300,3 @@ STR_STEP_HINT_SIDE: "Бакавыя кнопкі:"
|
|||||||
STR_AUTO_TURN_ENABLED: "Аўтаперагортванне: "
|
STR_AUTO_TURN_ENABLED: "Аўтаперагортванне: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Аўтаперагортванне (старонак за хвіліну)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Аўтаперагортванне (старонак за хвіліну)"
|
||||||
STR_TILT_PAGE_TURN: "Перагортванне нахілам"
|
STR_TILT_PAGE_TURN: "Перагортванне нахілам"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Дадаць схаваную сетку..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Увядзіце назву сеткі (SSID)"
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ STR_BOOTING: "ARRENCANT"
|
|||||||
STR_SLEEPING: "ENTRANT EN REPÒS"
|
STR_SLEEPING: "ENTRANT EN REPÒS"
|
||||||
STR_ENTERING_SLEEP: "Entrant en repòs"
|
STR_ENTERING_SLEEP: "Entrant en repòs"
|
||||||
STR_BROWSE_FILES: "Explora fitxers"
|
STR_BROWSE_FILES: "Explora fitxers"
|
||||||
STR_FILE_TRANSFER: "Transferència de fitxers"
|
STR_FILE_TRANSFER: "Transferència"
|
||||||
STR_SETTINGS_TITLE: "Configuració"
|
STR_SETTINGS_TITLE: "Configuració"
|
||||||
STR_CONTINUE_READING: "Continua llegint"
|
STR_CONTINUE_READING: "Continua llegint"
|
||||||
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
||||||
@@ -18,7 +18,6 @@ STR_NO_CHAPTERS: "Sense capítols"
|
|||||||
STR_END_OF_BOOK: "Final del llibre"
|
STR_END_OF_BOOK: "Final del llibre"
|
||||||
STR_EMPTY_CHAPTER: "Capítol buit"
|
STR_EMPTY_CHAPTER: "Capítol buit"
|
||||||
STR_INDEXING: "S'està indexant"
|
STR_INDEXING: "S'està indexant"
|
||||||
STR_INDEX_FAILED: "No s'ha pogut indexar: llibre no vàlid"
|
|
||||||
STR_MEMORY_ERROR: "Error de memòria"
|
STR_MEMORY_ERROR: "Error de memòria"
|
||||||
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
||||||
STR_EMPTY_FILE: "Fitxer buit"
|
STR_EMPTY_FILE: "Fitxer buit"
|
||||||
@@ -29,63 +28,58 @@ STR_WIFI_NETWORKS: "Xarxes Wi-Fi"
|
|||||||
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
||||||
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
||||||
STR_SCANNING: "S'està escanejant..."
|
STR_SCANNING: "S'està escanejant..."
|
||||||
STR_FINDING_SAVED_WIFI: "S'estan cercant xarxes Wi-Fi desades..."
|
|
||||||
STR_CONNECTING: "S'està connectant..."
|
STR_CONNECTING: "S'està connectant..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "S'està connectant a una xarxa Wi-Fi desada..."
|
|
||||||
STR_SHOW_NETWORKS: "Mostra"
|
|
||||||
STR_CONNECTED: "S'ha connectat!"
|
STR_CONNECTED: "S'ha connectat!"
|
||||||
STR_CONNECTION_FAILED: "Error de connexió"
|
STR_CONNECTION_FAILED: "Error de connexió"
|
||||||
STR_FORGET_NETWORK: "Vols oblidar aquesta xarxa?"
|
STR_FORGET_NETWORK: "Voleu oblidar aquesta xarxa?"
|
||||||
STR_SAVE_PASSWORD: "Vols desar la contrasenya per a la propera vegada?"
|
STR_SAVE_PASSWORD: "Voleu desar la contrasenya per a la propera vegada?"
|
||||||
STR_PRESS_OK_SCAN: "Prem OK per tornar a escanejar"
|
STR_PRESS_OK_SCAN: "Premeu OK per tornar a escanejar"
|
||||||
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
|
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
|
||||||
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
||||||
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
||||||
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
||||||
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
||||||
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
||||||
STR_CONNECT_WIFI_HINT: "Connecta el dispositiu a aquesta xarxa Wi-Fi"
|
STR_CONNECT_WIFI_HINT: "Connecteu el dispositiu a aquesta xarxa Wi-Fi"
|
||||||
STR_OPEN_URL_HINT: "Obre aquest URL al navegador"
|
STR_OPEN_URL_HINT: "Obriu aquest URL al navegador"
|
||||||
STR_OR_HTTP_PREFIX: "o http://"
|
STR_OR_HTTP_PREFIX: "o http://"
|
||||||
STR_SCAN_QR_HINT: "o escaneja el codi QR amb el telèfon:"
|
STR_SCAN_QR_HINT: "o escanegeu el codi QR amb el telèfon:"
|
||||||
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
||||||
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
|
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
|
||||||
STR_MAC_ADDRESS: "Adreça MAC:"
|
STR_MAC_ADDRESS: "Adreça MAC:"
|
||||||
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Introdueix la contrasenya Wi-Fi"
|
STR_ENTER_WIFI_PASSWORD: "Introduïu la contrasenya Wi-Fi"
|
||||||
STR_TO_PREFIX: "a "
|
STR_TO_PREFIX: "a "
|
||||||
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
||||||
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Instal·la el connector CrossPoint Reader"
|
STR_CALIBRE_INSTRUCTION_1: "1) Instal·leu el connector CrossPoint Reader"
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Estigues a la mateixa xarxa Wi-Fi"
|
STR_CALIBRE_INSTRUCTION_2: "2) Estigueu a la mateixa xarxa Wi-Fi"
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Mantén aquesta pantalla oberta mentre s'envia\""
|
STR_CALIBRE_INSTRUCTION_4: "\"Mantingueu aquesta pantalla oberta mentre s'envia\""
|
||||||
STR_CAT_DISPLAY: "Visualització"
|
STR_CAT_DISPLAY: "Visualització"
|
||||||
STR_CAT_READER: "Lector"
|
STR_CAT_READER: "Lector"
|
||||||
STR_CAT_CONTROLS: "Controls"
|
STR_CAT_CONTROLS: "Controls"
|
||||||
STR_CAT_SYSTEM: "Sistema"
|
STR_CAT_SYSTEM: "Sistema"
|
||||||
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
||||||
STR_SLEEP_COVER_MODE: "Ajust de la portada en repòs"
|
STR_SLEEP_COVER_MODE: "Mode de pantalla de repòs"
|
||||||
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
||||||
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
||||||
STR_TEXT_AA: "Antialiàsing del text"
|
STR_TEXT_AA: "Antialiàsing del text"
|
||||||
STR_IMAGES: "Imatges"
|
STR_IMAGES: "Imatges"
|
||||||
STR_IMAGES_DISPLAY: "Mostrar"
|
STR_IMAGES_DISPLAY: "Mostrar"
|
||||||
STR_IMAGES_PLACEHOLDER: "Text alternatiu"
|
STR_IMAGES_PLACEHOLDER: "Text de mostra"
|
||||||
STR_IMAGES_SUPPRESS: "Suprimir"
|
STR_IMAGES_SUPPRESS: "Suprimir"
|
||||||
STR_EOB_HOME: "Inici"
|
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
|
||||||
STR_EOB_CONTINUE_WITH: "Continua amb"
|
|
||||||
STR_SHORT_PWR_BTN: "Pulsació curta del botó d'engegada"
|
|
||||||
STR_ORIENTATION: "Orientació de lectura"
|
STR_ORIENTATION: "Orientació de lectura"
|
||||||
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Acció en mantenir premut un botó"
|
STR_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
||||||
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
||||||
STR_FONT_FAMILY: "Font del lector"
|
STR_FONT_FAMILY: "Tipus de lletra"
|
||||||
STR_FONT_SIZE: "Cos de lletra del lector"
|
STR_FONT_SIZE: "Mida de la lletra (UI)"
|
||||||
STR_LINE_SPACING: "Interlineat del lector"
|
STR_LINE_SPACING: "Interlineat del lector"
|
||||||
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
||||||
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
||||||
@@ -94,16 +88,15 @@ STR_TIME_TO_SLEEP: "Temps per entrar en repòs"
|
|||||||
STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults"
|
STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults"
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
||||||
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
||||||
STR_REFRESH_FREQ: "Freqüència d'actualització"
|
STR_REFRESH_FREQ: "Freqüència de refresc"
|
||||||
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
||||||
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
||||||
STR_LANGUAGE: "Llengua"
|
STR_LANGUAGE: "Idioma"
|
||||||
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
||||||
STR_USERNAME: "Nom d'usuari"
|
STR_USERNAME: "Nom d'usuari"
|
||||||
STR_PASSWORD: "Contrasenya"
|
STR_PASSWORD: "Contrasenya"
|
||||||
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
||||||
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
||||||
STR_SEND_METADATA: "Envia metadades del document"
|
|
||||||
STR_AUTHENTICATE: "Autentica"
|
STR_AUTHENTICATE: "Autentica"
|
||||||
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
||||||
@@ -149,7 +142,7 @@ STR_PREV_NEXT: "Anterior/Següent"
|
|||||||
STR_NEXT_PREV: "Següent/Anterior"
|
STR_NEXT_PREV: "Següent/Anterior"
|
||||||
STR_KOSYNC: "KOSync"
|
STR_KOSYNC: "KOSync"
|
||||||
STR_BOOKMARK_OPTION: "Punt de llibre"
|
STR_BOOKMARK_OPTION: "Punt de llibre"
|
||||||
STR_DISABLED: "Sense funció"
|
STR_DISABLED: "Desactivats"
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
STR_NOTO_SERIF: "Noto Serif"
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
STR_NOTO_SANS: "Noto Sans"
|
||||||
STR_SMALL: "Petita"
|
STR_SMALL: "Petita"
|
||||||
@@ -177,16 +170,16 @@ STR_UPDATING: "S'està actualitzant..."
|
|||||||
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
||||||
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
||||||
STR_UPDATE_COMPLETE: "Actualització completada"
|
STR_UPDATE_COMPLETE: "Actualització completada"
|
||||||
STR_POWER_ON_HINT: "Prem i mantén premut el botó d'encesa per tornar a engegar"
|
STR_POWER_ON_HINT: "Premeu i manteniu premut el botó d'encesa per tornar a engegar"
|
||||||
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
||||||
STR_DOWNLOADING: "S'està baixant..."
|
STR_DOWNLOADING: "S'està baixant..."
|
||||||
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
||||||
STR_ERROR_MSG: "Error:"
|
STR_ERROR_MSG: "Error:"
|
||||||
STR_UNNAMED: "Sense nom"
|
STR_UNNAMED: "Sense nom"
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Mantén premut Obre per esborrar"
|
STR_HOLD_OPEN_TO_DELETE: "Manteniu premut Obre per esborrar"
|
||||||
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
||||||
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del canal de continguts"
|
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
|
||||||
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del canal de continguts"
|
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
|
||||||
STR_NETWORK_PREFIX: "Xarxa: "
|
STR_NETWORK_PREFIX: "Xarxa: "
|
||||||
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
||||||
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
||||||
@@ -199,7 +192,7 @@ STR_HOME: "« Inici"
|
|||||||
STR_SELECT: "Selecciona"
|
STR_SELECT: "Selecciona"
|
||||||
STR_SELECTED: "Seleccionat"
|
STR_SELECTED: "Seleccionat"
|
||||||
STR_TOGGLE: "Canvia"
|
STR_TOGGLE: "Canvia"
|
||||||
STR_TOGGLE_BOOKMARK: "Afegeix o elimina el punt de llibre"
|
STR_TOGGLE_BOOKMARK: "Commuta punt de llibre"
|
||||||
STR_CONFIRM: "Confirma"
|
STR_CONFIRM: "Confirma"
|
||||||
STR_CANCEL: "Cancel·la"
|
STR_CANCEL: "Cancel·la"
|
||||||
STR_CONNECT: "Connecta"
|
STR_CONNECT: "Connecta"
|
||||||
@@ -218,7 +211,7 @@ STR_DIR_RIGHT: "Dreta"
|
|||||||
STR_DIR_UP: "Amunt"
|
STR_DIR_UP: "Amunt"
|
||||||
STR_DIR_DOWN: "Avall"
|
STR_DIR_DOWN: "Avall"
|
||||||
STR_OK_BUTTON: "OK"
|
STR_OK_BUTTON: "OK"
|
||||||
STR_SLEEP_COVER_FILTER: "Filtre de la portada en repòs"
|
STR_SLEEP_COVER_FILTER: "Filtre de pantalla de repòs"
|
||||||
STR_FILTER_CONTRAST: "Contrast"
|
STR_FILTER_CONTRAST: "Contrast"
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
||||||
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
||||||
@@ -240,7 +233,7 @@ STR_THEME_CLASSIC: "Clàssic"
|
|||||||
STR_THEME_LYRA: "Lyra"
|
STR_THEME_LYRA: "Lyra"
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
|
STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
|
||||||
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
|
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida per inactivitat"
|
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida després del temps"
|
||||||
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
|
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
|
||||||
STR_BOOKMARKS: "Punts de llibre"
|
STR_BOOKMARKS: "Punts de llibre"
|
||||||
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
|
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
|
||||||
@@ -249,17 +242,17 @@ STR_OPDS_BROWSER: "Navegador OPDS"
|
|||||||
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
||||||
STR_QUICK_RESUME: "Represa ràpida"
|
STR_QUICK_RESUME: "Represa ràpida"
|
||||||
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
||||||
STR_REMOVE_FROM_RECENTS: "Vols suprimir-lo de Llibres recents?"
|
STR_REMOVE_FROM_RECENTS: "Voleu suprimir-lo de Llibres recents?"
|
||||||
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
||||||
STR_CALIBRE_DESC: "Usa les transferències sense fils de Calibre"
|
STR_CALIBRE_DESC: "Usa les transferències sense fils de Calibre"
|
||||||
STR_FORGET_AND_REMOVE: "Vols oblidar la xarxa i suprimir la contrasenya desada?"
|
STR_FORGET_AND_REMOVE: "Voleu suprimir la contrasenya desada?"
|
||||||
STR_FORGET_BUTTON: "Oblida"
|
STR_FORGET_BUTTON: "Oblida"
|
||||||
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
||||||
STR_CALIBRE_SETUP: "Configura"
|
STR_CALIBRE_SETUP: "Configura"
|
||||||
STR_CALIBRE_STATUS: "Estat"
|
STR_CALIBRE_STATUS: "Estat"
|
||||||
STR_CLEAR_BUTTON: "Esborra"
|
STR_CLEAR_BUTTON: "Esborra"
|
||||||
STR_DEFAULT_VALUE: "Per defecte"
|
STR_DEFAULT_VALUE: "Per defecte"
|
||||||
STR_REMAP_PROMPT: "Prem un botó frontal per a cada rol"
|
STR_REMAP_PROMPT: "Premeu un botó frontal per a cada rol"
|
||||||
STR_UNASSIGNED: "No assignat"
|
STR_UNASSIGNED: "No assignat"
|
||||||
STR_ALREADY_ASSIGNED: "Ja assignat"
|
STR_ALREADY_ASSIGNED: "Ja assignat"
|
||||||
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
|
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
|
||||||
@@ -273,19 +266,19 @@ STR_GO_HOME_BUTTON: "Ves a l'inici"
|
|||||||
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
||||||
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
||||||
STR_DELETE: "Esborra"
|
STR_DELETE: "Esborra"
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Vols esborrar aquest punt de llibre?"
|
STR_CONFIRM_DELETE_BOOKMARK: "Voleu esborrar aquest punt de llibre?"
|
||||||
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
||||||
STR_CHAPTER_PREFIX: "Capítol: "
|
STR_CHAPTER_PREFIX: "Capítol: "
|
||||||
STR_PAGES_SEPARATOR: " pàgines | "
|
STR_PAGES_SEPARATOR: " pàgines | "
|
||||||
STR_BOOK_PREFIX: "Llibre: "
|
STR_BOOK_PREFIX: "Llibre: "
|
||||||
STR_CALIBRE_URL_HINT: "Per al Calibre, afegeix /opds a la URL"
|
STR_CALIBRE_URL_HINT: "Per al Calibre, afegiu /opds a la URL"
|
||||||
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
||||||
STR_CALC_HASH: "S'està calculant l'empremta electrònica del document..."
|
STR_CALC_HASH: "S'està calculant el hash del document..."
|
||||||
STR_HASH_FAILED: "No s'ha pogut calcular l'empremta electrònica del document"
|
STR_HASH_FAILED: "No s'ha pogut calcular el hash del document"
|
||||||
STR_FETCH_PROGRESS: "S'està obtenint el progrés remot..."
|
STR_FETCH_PROGRESS: "S'està obtenint el progrés remot..."
|
||||||
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
||||||
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
||||||
STR_KOREADER_SETUP_HINT: "Configura el compte de KOReader a la configuració"
|
STR_KOREADER_SETUP_HINT: "Configureu el compte de KOReader a la configuració"
|
||||||
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
||||||
STR_REMOTE_LABEL: "Remot:"
|
STR_REMOTE_LABEL: "Remot:"
|
||||||
STR_LOCAL_LABEL: "Local:"
|
STR_LOCAL_LABEL: "Local:"
|
||||||
@@ -295,7 +288,7 @@ STR_DEVICE_FROM_FORMAT: " De: %s"
|
|||||||
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
||||||
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
||||||
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
||||||
STR_UPLOAD_PROMPT: "Vols pujar la posició actual?"
|
STR_UPLOAD_PROMPT: "Voleu pujar la posició actual?"
|
||||||
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
||||||
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
||||||
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut desar el progrés"
|
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut desar el progrés"
|
||||||
@@ -314,11 +307,11 @@ STR_SLEEP_NEVER: "Mai"
|
|||||||
STR_STEP_HINT_FRONT: "Botons frontals:"
|
STR_STEP_HINT_FRONT: "Botons frontals:"
|
||||||
STR_STEP_HINT_SIDE: "Botons laterals:"
|
STR_STEP_HINT_SIDE: "Botons laterals:"
|
||||||
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
||||||
STR_AUTO_TURN_ENABLED: "Pas automàtic de pàgina activat"
|
STR_AUTO_TURN_ENABLED: "Passar automàtic activat: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Pas automàtic de pàgina (pàg./min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Passar automàtic (pàgines per minut)"
|
||||||
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
||||||
STR_FORCE_REFRESH: "Refresca la pantalla"
|
STR_FORCE_REFRESH: "Refresca la pantalla"
|
||||||
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, mantén premut el botó d'encesa durant uns segons."
|
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, manteniu premut el botó d'encesa durant uns segons."
|
||||||
STR_NEXT_PAGE: "Pàgina següent »"
|
STR_NEXT_PAGE: "Pàgina següent »"
|
||||||
STR_PREV_PAGE: "« Pàgina anterior"
|
STR_PREV_PAGE: "« Pàgina anterior"
|
||||||
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
||||||
@@ -347,20 +340,20 @@ STR_SERVER_NAME: "Nom del servidor"
|
|||||||
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
||||||
STR_DELETE_SERVER: "Suprimeix el servidor"
|
STR_DELETE_SERVER: "Suprimeix el servidor"
|
||||||
STR_OPDS_SERVERS: "Servidors OPDS"
|
STR_OPDS_SERVERS: "Servidors OPDS"
|
||||||
STR_MANAGE_FONTS: "Gestiona les fonts"
|
STR_MANAGE_FONTS: "Gestiona els tipus de lletra"
|
||||||
STR_FONT_BROWSER: "Navegador de fonts"
|
STR_FONT_BROWSER: "Navegador de tipus de lletra"
|
||||||
STR_LOADING_FONT_LIST: "S'està carregant la llista de fonts..."
|
STR_LOADING_FONT_LIST: "S'està carregant la llista de tipus de lletra..."
|
||||||
STR_NO_FONTS_AVAILABLE: "No hi ha fonts disponibles"
|
STR_NO_FONTS_AVAILABLE: "No hi ha tipus de lletra disponibles"
|
||||||
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
||||||
STR_FONT_INSTALLED: "Font instal·lada!"
|
STR_FONT_INSTALLED: "Tipus de lletra instal·lat!"
|
||||||
STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació de la font"
|
STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació del tipus de lletra"
|
||||||
STR_INSTALLED: "Instal·lat"
|
STR_INSTALLED: "Instal·lat"
|
||||||
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
||||||
STR_UPDATE_ALL: "Actualitza-ho tot"
|
STR_UPDATE_ALL: "Actualitza-ho tot"
|
||||||
STR_UPDATE_AVAILABLE: "Actualitza"
|
STR_UPDATE_AVAILABLE: "Actualitza"
|
||||||
STR_CRASH_TITLE: "Fallada del sistema"
|
STR_CRASH_TITLE: "Bloqueig del sistema"
|
||||||
STR_CRASH_DESCRIPTION: "S'ha desat un informe detallat a crash_report.txt. Inclou aquest fitxer a l'informe d'errors."
|
STR_CRASH_DESCRIPTION: "S'ha desat un informe detallat a crash_report.txt. Incloeu aquest fitxer a l'informe d'errors."
|
||||||
STR_CRASH_REASON: "Motiu de la fallada"
|
STR_CRASH_REASON: "Motiu del bloqueig:"
|
||||||
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
||||||
@@ -373,22 +366,20 @@ STR_KB_TIPS: "Consells:"
|
|||||||
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per sortir del mode URL"
|
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per sortir del mode URL"
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Mantén premut DEL per esborrar tot el text"
|
STR_KB_HINT_CLEAR_TEXT: "Mantén premut DEL per esborrar tot el text"
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Mantén premut SELECT: caràcter secundari"
|
STR_KB_HINT_SECONDARY_CHAR: "Mantén premut SELECT per al caràcter secundari"
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Mantén premut SELECT: majúscules o caràcter secundari"
|
STR_KB_HINT_UPPER_SECONDARY: "Mantén premut SELECT per MAJÚSCULES o caràcter secundari"
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Mantén premut SELECT: minúscules o caràcter secundari"
|
STR_KB_HINT_LOWER_SECONDARY: "Mantén premut SELECT per minúscules o caràcter secundari"
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
||||||
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
||||||
STR_SELECT_FIRMWARE_FILE: "Selecciona un fitxer de firmware (.bin)"
|
STR_SELECT_FIRMWARE_FILE: "Seleccioneu un fitxer de firmware (.bin)"
|
||||||
STR_NO_BIN_FILES: "No s'han trobat fitxers .bin"
|
STR_NO_BIN_FILES: "No s'han trobat fitxers .bin"
|
||||||
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
||||||
STR_INVALID_FIRMWARE: "Fitxer de firmware no vàlid"
|
STR_INVALID_FIRMWARE: "Fitxer de firmware no vàlid"
|
||||||
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
||||||
STR_FIRMWARE_TOO_SMALL: "El fitxer de firmware és massa petit"
|
STR_FIRMWARE_TOO_SMALL: "El fitxer de firmware és massa petit"
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Vols actualitzar el firmware?"
|
STR_FIRMWARE_UPDATE_PROMPT: "Voleu actualitzar el firmware?"
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir el fitxer"
|
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir el fitxer"
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apaguis el dispositiu!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apagueu el dispositiu!"
|
||||||
STR_RECOVERY_MODE: "Mode de recuperació"
|
STR_RECOVERY_MODE: "Mode de recuperació"
|
||||||
STR_RECOVERY_MODE_HINT: "Posa firmware.bin a l'arrel de la targeta SD i selecciona'l"
|
STR_RECOVERY_MODE_HINT: "Poseu firmware.bin a l'arrel de la targeta SD i seleccioneu-lo"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Afegeix una xarxa oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introdueix el nom de la xarxa (SSID)"
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ STR_NETWORK_LEGEND: "* = Šifrováno | + = Uloženo"
|
|||||||
STR_MAC_ADDRESS: "MAC adresa:"
|
STR_MAC_ADDRESS: "MAC adresa:"
|
||||||
STR_CHECKING_WIFI: "Kontrola Wi-Fi..."
|
STR_CHECKING_WIFI: "Kontrola Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Zadejte heslo Wi-Fi"
|
STR_ENTER_WIFI_PASSWORD: "Zadejte heslo Wi-Fi"
|
||||||
STR_TO_PREFIX: "k "
|
STR_TO_PREFIX: "pro"
|
||||||
STR_CALIBRE_RECEIVING: "Příjem:"
|
STR_CALIBRE_RECEIVING: "Příjem:"
|
||||||
STR_CALIBRE_RECEIVED: "Přijato:"
|
STR_CALIBRE_RECEIVED: "Přijato:"
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Nainstalujte plugin CrossPoint Reader"
|
STR_CALIBRE_INSTRUCTION_1: "1) Nainstalujte plugin CrossPoint Reader"
|
||||||
@@ -92,7 +92,6 @@ STR_USERNAME: "Uživatelské jméno"
|
|||||||
STR_PASSWORD: "Heslo"
|
STR_PASSWORD: "Heslo"
|
||||||
STR_SYNC_SERVER_URL: "URL synch. serveru"
|
STR_SYNC_SERVER_URL: "URL synch. serveru"
|
||||||
STR_DOCUMENT_MATCHING: "Párování dokumentů"
|
STR_DOCUMENT_MATCHING: "Párování dokumentů"
|
||||||
STR_SEND_METADATA: "Odesílat metadata dokumentu"
|
|
||||||
STR_AUTHENTICATE: "Ověření"
|
STR_AUTHENTICATE: "Ověření"
|
||||||
STR_KOREADER_USERNAME: "Uživ. jméno KOReaderu"
|
STR_KOREADER_USERNAME: "Uživ. jméno KOReaderu"
|
||||||
STR_KOREADER_PASSWORD: "Heslo KOReaderu"
|
STR_KOREADER_PASSWORD: "Heslo KOReaderu"
|
||||||
@@ -276,5 +275,3 @@ STR_SLEEP_NEVER: "Nikdy"
|
|||||||
STR_STEP_HINT_FRONT: "Přední tlačítka:"
|
STR_STEP_HINT_FRONT: "Přední tlačítka:"
|
||||||
STR_STEP_HINT_SIDE: "Boční tlačítka:"
|
STR_STEP_HINT_SIDE: "Boční tlačítka:"
|
||||||
STR_TILT_PAGE_TURN: "Otáčení stránek nakloněním"
|
STR_TILT_PAGE_TURN: "Otáčení stránek nakloněním"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Přidat skrytou síť..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Zadejte název sítě (SSID)"
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ STR_USERNAME: "Brugernavn"
|
|||||||
STR_PASSWORD: "Adgangskode"
|
STR_PASSWORD: "Adgangskode"
|
||||||
STR_SYNC_SERVER_URL: "Synkroniseringsserver-URL"
|
STR_SYNC_SERVER_URL: "Synkroniseringsserver-URL"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumentsammenkobling"
|
STR_DOCUMENT_MATCHING: "Dokumentsammenkobling"
|
||||||
STR_SEND_METADATA: "Send dokumentmetadata"
|
|
||||||
STR_AUTHENTICATE: "Godkend"
|
STR_AUTHENTICATE: "Godkend"
|
||||||
STR_KOREADER_USERNAME: "KOReader brugernavn"
|
STR_KOREADER_USERNAME: "KOReader brugernavn"
|
||||||
STR_KOREADER_PASSWORD: "KOReader adgangskode"
|
STR_KOREADER_PASSWORD: "KOReader adgangskode"
|
||||||
@@ -304,5 +303,3 @@ STR_SCREENSHOT_BUTTON: "Tag skærmbillede"
|
|||||||
STR_AUTO_TURN_ENABLED: "Automatisk sidevendning aktiveret: "
|
STR_AUTO_TURN_ENABLED: "Automatisk sidevendning aktiveret: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vending (sider per minut)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vending (sider per minut)"
|
||||||
STR_TILT_PAGE_TURN: "Vip for at vende side"
|
STR_TILT_PAGE_TURN: "Vip for at vende side"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Tilføj skjult netværk..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Indtast netværksnavn (SSID)"
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ STR_USERNAME: "Gebruikersnaam"
|
|||||||
STR_PASSWORD: "Wachtwoord"
|
STR_PASSWORD: "Wachtwoord"
|
||||||
STR_SYNC_SERVER_URL: "Sync-server URL"
|
STR_SYNC_SERVER_URL: "Sync-server URL"
|
||||||
STR_DOCUMENT_MATCHING: "Documentkoppeling"
|
STR_DOCUMENT_MATCHING: "Documentkoppeling"
|
||||||
STR_SEND_METADATA: "Documentmetadata versturen"
|
|
||||||
STR_AUTHENTICATE: "Authenticatie"
|
STR_AUTHENTICATE: "Authenticatie"
|
||||||
STR_KOREADER_USERNAME: "KOReader gebruikersnaam"
|
STR_KOREADER_USERNAME: "KOReader gebruikersnaam"
|
||||||
STR_KOREADER_PASSWORD: "KOReader wachtwoord"
|
STR_KOREADER_PASSWORD: "KOReader wachtwoord"
|
||||||
@@ -304,5 +303,3 @@ STR_SCREENSHOT_BUTTON: "Screenshot maken"
|
|||||||
STR_AUTO_TURN_ENABLED: "Automatisch omslaan ingeschakeld: "
|
STR_AUTO_TURN_ENABLED: "Automatisch omslaan ingeschakeld: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Autom. omslaan (pagina's per minuut)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Autom. omslaan (pagina's per minuut)"
|
||||||
STR_TILT_PAGE_TURN: "Kantel om te bladeren"
|
STR_TILT_PAGE_TURN: "Kantel om te bladeren"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Verborgen netwerk toevoegen..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Voer netwerknaam in (SSID)"
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ STR_NO_CHAPTERS: "No chapters"
|
|||||||
STR_END_OF_BOOK: "End of book"
|
STR_END_OF_BOOK: "End of book"
|
||||||
STR_EMPTY_CHAPTER: "Empty chapter"
|
STR_EMPTY_CHAPTER: "Empty chapter"
|
||||||
STR_INDEXING: "Indexing"
|
STR_INDEXING: "Indexing"
|
||||||
STR_INDEX_FAILED: "Failed to index - invalid book"
|
|
||||||
STR_MEMORY_ERROR: "Memory error"
|
STR_MEMORY_ERROR: "Memory error"
|
||||||
STR_PAGE_LOAD_ERROR: "Page load error"
|
STR_PAGE_LOAD_ERROR: "Page load error"
|
||||||
STR_EMPTY_FILE: "Empty file"
|
STR_EMPTY_FILE: "Empty file"
|
||||||
@@ -29,10 +28,7 @@ STR_WIFI_NETWORKS: "Wi-Fi Networks"
|
|||||||
STR_NO_NETWORKS: "No networks found"
|
STR_NO_NETWORKS: "No networks found"
|
||||||
STR_NETWORKS_FOUND: "%zu networks found"
|
STR_NETWORKS_FOUND: "%zu networks found"
|
||||||
STR_SCANNING: "Scanning..."
|
STR_SCANNING: "Scanning..."
|
||||||
STR_FINDING_SAVED_WIFI: "Finding saved Wi-Fi..."
|
|
||||||
STR_CONNECTING: "Connecting..."
|
STR_CONNECTING: "Connecting..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "Connecting to saved Wi-Fi..."
|
|
||||||
STR_SHOW_NETWORKS: "Show"
|
|
||||||
STR_CONNECTED: "Connected!"
|
STR_CONNECTED: "Connected!"
|
||||||
STR_CONNECTION_FAILED: "Connection Failed"
|
STR_CONNECTION_FAILED: "Connection Failed"
|
||||||
STR_FORGET_NETWORK: "Forget Network?"
|
STR_FORGET_NETWORK: "Forget Network?"
|
||||||
@@ -53,8 +49,6 @@ STR_NETWORK_LEGEND: "* = Encrypted | + = Saved"
|
|||||||
STR_MAC_ADDRESS: "MAC address:"
|
STR_MAC_ADDRESS: "MAC address:"
|
||||||
STR_CHECKING_WIFI: "Checking Wi-Fi..."
|
STR_CHECKING_WIFI: "Checking Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Enter Wi-Fi password"
|
STR_ENTER_WIFI_PASSWORD: "Enter Wi-Fi password"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Add hidden network..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Enter network name (SSID)"
|
|
||||||
STR_TO_PREFIX: "to "
|
STR_TO_PREFIX: "to "
|
||||||
STR_CALIBRE_RECEIVING: "Receiving: "
|
STR_CALIBRE_RECEIVING: "Receiving: "
|
||||||
STR_CALIBRE_RECEIVED: "Received: "
|
STR_CALIBRE_RECEIVED: "Received: "
|
||||||
@@ -107,7 +101,6 @@ STR_USERNAME: "Username"
|
|||||||
STR_PASSWORD: "Password"
|
STR_PASSWORD: "Password"
|
||||||
STR_SYNC_SERVER_URL: "Sync Server URL"
|
STR_SYNC_SERVER_URL: "Sync Server URL"
|
||||||
STR_DOCUMENT_MATCHING: "Document Matching"
|
STR_DOCUMENT_MATCHING: "Document Matching"
|
||||||
STR_SEND_METADATA: "Send Document Metadata"
|
|
||||||
STR_AUTHENTICATE: "Authenticate"
|
STR_AUTHENTICATE: "Authenticate"
|
||||||
STR_KOREADER_USERNAME: "KOReader Username"
|
STR_KOREADER_USERNAME: "KOReader Username"
|
||||||
STR_KOREADER_PASSWORD: "KOReader Password"
|
STR_KOREADER_PASSWORD: "KOReader Password"
|
||||||
@@ -295,25 +288,6 @@ STR_HW_BACK_LABEL: "Back (1st button)"
|
|||||||
STR_HW_CONFIRM_LABEL: "Confirm (2nd button)"
|
STR_HW_CONFIRM_LABEL: "Confirm (2nd button)"
|
||||||
STR_HW_LEFT_LABEL: "Left (3rd button)"
|
STR_HW_LEFT_LABEL: "Left (3rd button)"
|
||||||
STR_HW_RIGHT_LABEL: "Right (4th button)"
|
STR_HW_RIGHT_LABEL: "Right (4th button)"
|
||||||
STR_BLUETOOTH: "Bluetooth"
|
|
||||||
STR_TOGGLE_BLUETOOTH: "Toggle Bluetooth"
|
|
||||||
STR_BT_SCAN_PAIR: "Scan & Pair"
|
|
||||||
STR_BT_NO_DEVICES: "No devices found"
|
|
||||||
STR_BT_FREE_HINT1: "Set Free2/3 to Reader Mode and"
|
|
||||||
STR_BT_FREE_HINT2: "Volume Function to pair"
|
|
||||||
STR_BT_DISCONNECT: "Disconnect"
|
|
||||||
STR_BT_CONNECTED_TO: "Connected: %s"
|
|
||||||
STR_BT_NOT_CONNECTED: "Not connected"
|
|
||||||
STR_BT_PAIRED_DEVICES: "Paired Devices"
|
|
||||||
STR_BT_NO_PAIRED: "No paired devices"
|
|
||||||
STR_BT_MAP_BUTTONS: "Map Remote Buttons"
|
|
||||||
STR_BT_PRESS_REMOTE: "Press a button on your remote"
|
|
||||||
STR_BT_CONNECTING_POPUP: "BT Connecting..."
|
|
||||||
STR_BT_PAUSED_LOW_MEM_POPUP: "BT paused (low memory)"
|
|
||||||
STR_STATE_PAUSED: "PAUSED"
|
|
||||||
STR_BT_PAGE_FORWARD: "Page Forward"
|
|
||||||
STR_BT_PAGE_BACK: "Page Back"
|
|
||||||
STR_BT_FORGET_PROMPT: "Hold Confirm to forget"
|
|
||||||
STR_GO_TO_PERCENT: "Go to %"
|
STR_GO_TO_PERCENT: "Go to %"
|
||||||
STR_GO_HOME_BUTTON: "Go Home"
|
STR_GO_HOME_BUTTON: "Go Home"
|
||||||
STR_SYNC_PROGRESS: "Sync Progress"
|
STR_SYNC_PROGRESS: "Sync Progress"
|
||||||
@@ -336,6 +310,7 @@ STR_PROGRESS_FOUND: "Progress found!"
|
|||||||
STR_REMOTE_LABEL: "Remote:"
|
STR_REMOTE_LABEL: "Remote:"
|
||||||
STR_LOCAL_LABEL: "Local:"
|
STR_LOCAL_LABEL: "Local:"
|
||||||
STR_PAGE_OVERALL_FORMAT: "Page %d, %.2f%% overall"
|
STR_PAGE_OVERALL_FORMAT: "Page %d, %.2f%% overall"
|
||||||
|
STR_PERCENT_OVERALL_FORMAT: " %.2f%% overall"
|
||||||
STR_PAGE_TOTAL_OVERALL_FORMAT: "Page %d/%d, %.2f%% overall"
|
STR_PAGE_TOTAL_OVERALL_FORMAT: "Page %d/%d, %.2f%% overall"
|
||||||
STR_DEVICE_FROM_FORMAT: " From: %s"
|
STR_DEVICE_FROM_FORMAT: " From: %s"
|
||||||
STR_APPLY_REMOTE: "Apply remote progress"
|
STR_APPLY_REMOTE: "Apply remote progress"
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ STR_USERNAME: "Käyttäjänimi"
|
|||||||
STR_PASSWORD: "Salasana"
|
STR_PASSWORD: "Salasana"
|
||||||
STR_SYNC_SERVER_URL: "Synkronointipalvelimen osoite"
|
STR_SYNC_SERVER_URL: "Synkronointipalvelimen osoite"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumenttien tunnistus"
|
STR_DOCUMENT_MATCHING: "Dokumenttien tunnistus"
|
||||||
STR_SEND_METADATA: "Lähetä asiakirjan metatiedot"
|
|
||||||
STR_AUTHENTICATE: "Tunnistaudu"
|
STR_AUTHENTICATE: "Tunnistaudu"
|
||||||
STR_KOREADER_USERNAME: "KOReader-käyttäjänimi"
|
STR_KOREADER_USERNAME: "KOReader-käyttäjänimi"
|
||||||
STR_KOREADER_PASSWORD: "KOReader-salasana"
|
STR_KOREADER_PASSWORD: "KOReader-salasana"
|
||||||
@@ -274,5 +273,3 @@ STR_SLEEP_NEVER: "Ei koskaan"
|
|||||||
STR_STEP_HINT_FRONT: "Etupainikkeet:"
|
STR_STEP_HINT_FRONT: "Etupainikkeet:"
|
||||||
STR_STEP_HINT_SIDE: "Sivupainikkeet:"
|
STR_STEP_HINT_SIDE: "Sivupainikkeet:"
|
||||||
STR_TILT_PAGE_TURN: "Sivunkääntö kallistamalla"
|
STR_TILT_PAGE_TURN: "Sivunkääntö kallistamalla"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Lisää piilotettu verkko..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Anna verkon nimi (SSID)"
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ STR_USERNAME: "Nom d’utilisateur"
|
|||||||
STR_PASSWORD: "Mot de passe"
|
STR_PASSWORD: "Mot de passe"
|
||||||
STR_SYNC_SERVER_URL: "URL du serveur"
|
STR_SYNC_SERVER_URL: "URL du serveur"
|
||||||
STR_DOCUMENT_MATCHING: "Correspondance"
|
STR_DOCUMENT_MATCHING: "Correspondance"
|
||||||
STR_SEND_METADATA: "Envoyer métadonnées"
|
|
||||||
STR_AUTHENTICATE: "Connexion"
|
STR_AUTHENTICATE: "Connexion"
|
||||||
STR_KOREADER_USERNAME: "Utilisateur"
|
STR_KOREADER_USERNAME: "Utilisateur"
|
||||||
STR_KOREADER_PASSWORD: "Mot de passe"
|
STR_KOREADER_PASSWORD: "Mot de passe"
|
||||||
@@ -305,5 +304,3 @@ STR_SCREENSHOT_BUTTON: "Capture d'écran"
|
|||||||
STR_AUTO_TURN_ENABLED: "Tourne-page auto : "
|
STR_AUTO_TURN_ENABLED: "Tourne-page auto : "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Tourne-page auto (pages par minute)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Tourne-page auto (pages par minute)"
|
||||||
STR_TILT_PAGE_TURN: "Tourner par inclinaison"
|
STR_TILT_PAGE_TURN: "Tourner par inclinaison"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Ajouter un réseau masqué..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Saisir le nom du réseau (SSID)"
|
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ STR_USERNAME: "Benutzername"
|
|||||||
STR_PASSWORD: "Passwort"
|
STR_PASSWORD: "Passwort"
|
||||||
STR_SYNC_SERVER_URL: "Sync-Server-URL"
|
STR_SYNC_SERVER_URL: "Sync-Server-URL"
|
||||||
STR_DOCUMENT_MATCHING: "Dateizuordnung"
|
STR_DOCUMENT_MATCHING: "Dateizuordnung"
|
||||||
STR_SEND_METADATA: "Metadaten senden"
|
|
||||||
STR_AUTHENTICATE: "Authentifizieren"
|
STR_AUTHENTICATE: "Authentifizieren"
|
||||||
STR_KOREADER_USERNAME: "KOReader-Benutzername"
|
STR_KOREADER_USERNAME: "KOReader-Benutzername"
|
||||||
STR_KOREADER_PASSWORD: "KOReader-Passwort"
|
STR_KOREADER_PASSWORD: "KOReader-Passwort"
|
||||||
@@ -381,5 +380,3 @@ STR_FIRMWARE_WRITE_FAILED: "Schreiben der Firmware-Datei ist fehlgeschlagen"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nicht ausschalten!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nicht ausschalten!"
|
||||||
STR_RECOVERY_MODE: "Wiederherstellungsmodus"
|
STR_RECOVERY_MODE: "Wiederherstellungsmodus"
|
||||||
STR_RECOVERY_MODE_HINT: "Lege firmware.bin im SD-Kartenwurzelverzeichnis ab und wähle es aus"
|
STR_RECOVERY_MODE_HINT: "Lege firmware.bin im SD-Kartenwurzelverzeichnis ab und wähle es aus"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Verstecktes Netzwerk hinzufügen..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Netzwerknamen eingeben (SSID)"
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user