From b2809fdafd596eef53620efc58e82b71f398677f Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 20 Apr 2026 00:14:54 +0200 Subject: [PATCH 01/93] feat: factory LUT grayscale rendering engine and perf optimizations Add GRAY2 absolute encoding for 4-shade e-ink rendering via factory LUTs. Introduce FactoryFast and FactoryQuality GrayscaleMode variants with X3 fallback to Differential. Add direct-pixel BMP rendering path, 2-bit XTC plane support in Xtc parser, and HALF_REFRESH display mode in HalDisplay. displayXtchPlanes uses Differential on X3 (factory LUT not calibrated). Skip fadingFix power-down on first BW render after factory LUT to avoid redundant power cycle (display already off after 0xC7 sequence). --- lib/Epub/Epub/blocks/ImageBlock.cpp | 4 +- lib/Epub/Epub/converters/DirectPixelWriter.h | 131 +++-- .../converters/JpegToFramebufferConverter.cpp | 16 +- .../converters/PngToFramebufferConverter.cpp | 4 +- lib/FsHelpers/FsHelpers.cpp | 2 + lib/FsHelpers/FsHelpers.h | 3 + lib/GfxRenderer/Bitmap.cpp | 45 +- lib/GfxRenderer/Bitmap.h | 10 + lib/GfxRenderer/BitmapHelpers.cpp | 30 +- lib/GfxRenderer/BitmapHelpers.h | 57 ++- lib/GfxRenderer/GfxRenderer.cpp | 459 ++++++++++++++++-- lib/GfxRenderer/GfxRenderer.h | 65 ++- lib/Xtc/Xtc.cpp | 416 +++++++++++----- lib/Xtc/Xtc.h | 2 + lib/Xtc/Xtc/XtcParser.cpp | 128 +++++ lib/Xtc/Xtc/XtcParser.h | 23 + lib/hal/HalDisplay.cpp | 4 +- lib/hal/HalDisplay.h | 2 +- open-x4-sdk | 2 +- 19 files changed, 1146 insertions(+), 257 deletions(-) diff --git a/lib/Epub/Epub/blocks/ImageBlock.cpp b/lib/Epub/Epub/blocks/ImageBlock.cpp index 1b71817a89..d234887a0f 100644 --- a/lib/Epub/Epub/blocks/ImageBlock.cpp +++ b/lib/Epub/Epub/blocks/ImageBlock.cpp @@ -74,13 +74,13 @@ bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, } const int destY = y + row; - pw.beginRow(destY); + pw.beginRow(destY, x); for (int col = 0; col < cachedWidth; 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); + pw.writePixel(pixelValue); } } diff --git a/lib/Epub/Epub/converters/DirectPixelWriter.h b/lib/Epub/Epub/converters/DirectPixelWriter.h index bc66c2f78e..44bb56fd09 100644 --- a/lib/Epub/Epub/converters/DirectPixelWriter.h +++ b/lib/Epub/Epub/converters/DirectPixelWriter.h @@ -6,16 +6,26 @@ // 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. +// and caches render-mode state so the inner loop is: two increments, one shift, +// one AND, and one or two bit-writes per pixel — no multiplies, no branches on +// mode or orientation, 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. +// Usage: +// pw.init(renderer); +// for each row: pw.beginRow(logicalY); // resets running X/Y state to col 0 +// for each col: pw.writePixel(value); // advances running state, writes bit +// +// Caller must call writePixel() for every column in order (0, 1, 2, …) because +// the running state is advanced unconditionally on each call. Caller is also +// responsible for ensuring columns are within screen bounds before entering the +// loop; no bounds checking is performed here. struct DirectPixelWriter { uint8_t* fb; - GfxRenderer::RenderMode mode; - uint16_t displayWidthBytes; // Runtime framebuffer stride (X4: 100, X3: 99) + uint8_t* fb2; // Secondary framebuffer for MSB plane (null = two-pass / not active) + uint8_t writeFbMask; // Bit i set → write to fb when pixelValue==i (pre-computed from render mode) + uint8_t writeFb2Mask; // Bit i set → write to fb2 when pixelValue==i + bool fbClearBit; // true = clear bit (BW black); false = set bit (all gray modes) + uint16_t displayWidthBytes; // Runtime framebuffer stride // Orientation is collapsed into a linear transform: // phyX = phyXBase + x * phyXStepX + y * phyXStepY @@ -24,14 +34,44 @@ struct DirectPixelWriter { 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; + // Pre-computed once in init(): physical-Y advance per logical-X step (in byte-index units). + int32_t byteIdxYStep; + + // Running state — reset by beginRow(), advanced by writePixel(). + int curPhyX; + int32_t curByteIdx; - void init(GfxRenderer& renderer) { + void init(const GfxRenderer& renderer) { fb = renderer.getFrameBuffer(); - mode = renderer.getRenderMode(); + fb2 = renderer.getSecondaryFrameBuffer(); displayWidthBytes = renderer.getDisplayWidthBytes(); + // Pre-compute write masks once so the inner loop has zero mode branches. + writeFbMask = 0; + writeFb2Mask = 0; + fbClearBit = false; + switch (renderer.getRenderMode()) { + case GfxRenderer::BW: + writeFbMask = 0x3; + fbClearBit = true; + break; + case GfxRenderer::GRAYSCALE_MSB: + writeFbMask = 0x6; + break; + case GfxRenderer::GRAYSCALE_LSB: + writeFbMask = 0x2; + break; + case GfxRenderer::GRAY2_LSB: + writeFbMask = 0x5; + if (fb2) writeFb2Mask = 0x3; + break; + case GfxRenderer::GRAY2_MSB: + writeFbMask = 0x3; + break; + default: + break; + } + const int phyW = renderer.getDisplayWidth(); const int phyH = renderer.getDisplayHeight(); @@ -82,52 +122,47 @@ struct DirectPixelWriter { phyYStepY = 1; break; } + + // Per-column advance in physical-Y expressed as a byte-index delta. + byteIdxYStep = static_cast(phyYStepX) * static_cast(displayWidthBytes); } // 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; + // startLogicalX is the X coordinate of the first writePixel() call for this row (default 0). + // Running state is initialised at startLogicalX so every subsequent writePixel() call + // advances to startLogicalX+1, startLogicalX+2, … with zero per-pixel multiplies. + inline void beginRow(int logicalY, int startLogicalX = 0) { + const int rowPhyXBase = phyXBase + logicalY * phyXStepY; + const int rowPhyYBase = phyYBase + logicalY * phyYStepY; + curPhyX = rowPhyXBase + startLogicalX * phyXStepX; + curByteIdx = + static_cast(rowPhyYBase + startLogicalX * phyYStepX) * static_cast(displayWidthBytes); } - // Write a single 2-bit dithered pixel value to the framebuffer. - // Must be called after beginRow() for the current row. + // Write a single 2-bit pixel value to the framebuffer and advance to the next column. + // Must be called after beginRow() for the current row, for every column in order. // 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; - - const uint16_t byteIndex = phyY * displayWidthBytes + (phyX >> 3); + // No mode switch — write masks are pre-computed in init() and stored as members. + inline void writePixel(uint8_t pixelValue) { + const int phyX = curPhyX; + const int32_t byteIdx = curByteIdx; + curPhyX += phyXStepX; + curByteIdx += byteIdxYStep; + + const bool doFb = (writeFbMask >> pixelValue) & 1; + const bool doFb2 = (writeFb2Mask >> pixelValue) & 1; + if (!doFb && !doFb2) return; + + const uint32_t bi = static_cast(byteIdx) + static_cast(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) + if (doFb) { + if (fbClearBit) + fb[bi] &= ~bitMask; + else + fb[bi] |= bitMask; } + if (doFb2) fb2[bi] |= bitMask; } }; diff --git a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp index 4cf55ae3b5..f48d2fb4d5 100644 --- a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp @@ -168,7 +168,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { if (fineScaleFP == FP_ONE) { for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; - pw.beginRow(outY); + pw.beginRow(outY, cfgX + dstXStart); if (caching) cw.beginRow(outY, ctx->config->y); const uint8_t* row = &pixels[(dstY - blockY) * stride]; for (int dstX = dstXStart; dstX < dstXEnd; dstX++) { @@ -181,7 +181,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { dithered = gray / 85; if (dithered > 3) dithered = 3; } - pw.writePixel(outX, dithered); + pw.writePixel(dithered); if (caching) cw.writePixel(outX, dithered); } } @@ -202,7 +202,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; - pw.beginRow(outY); + pw.beginRow(outY, cfgX + dstXStart); if (caching) cw.beginRow(outY, ctx->config->y); const int32_t srcFyFP = dstY * invScaleFP; const int32_t fy = srcFyFP & FP_MASK; @@ -240,7 +240,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { dithered = gray / 85; if (dithered > 3) dithered = 3; } - pw.writePixel(outX, dithered); + pw.writePixel(dithered); if (caching) cw.writePixel(outX, dithered); } @@ -263,7 +263,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { dithered = gray / 85; if (dithered > 3) dithered = 3; } - pw.writePixel(outX, dithered); + pw.writePixel(dithered); if (caching) cw.writePixel(outX, dithered); } @@ -289,7 +289,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { dithered = gray / 85; if (dithered > 3) dithered = 3; } - pw.writePixel(outX, dithered); + pw.writePixel(dithered); if (caching) cw.writePixel(outX, dithered); } } @@ -299,7 +299,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { // === Nearest-neighbor (downscale: fineScale < 1.0) === for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; - pw.beginRow(outY); + pw.beginRow(outY, cfgX + dstXStart); if (caching) cw.beginRow(outY, ctx->config->y); const int32_t srcFyFP = dstY * invScaleFP; int ly = (srcFyFP >> FP_SHIFT) - blockY; @@ -322,7 +322,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { dithered = gray / 85; if (dithered > 3) dithered = 3; } - pw.writePixel(outX, dithered); + pw.writePixel(dithered); if (caching) cw.writePixel(outX, dithered); } } diff --git a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp index 0cc1616abc..ce0a4ab7f0 100644 --- a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp @@ -197,7 +197,7 @@ int pngDrawCallback(PNGDRAW* pDraw) { // Pre-compute orientation and render-mode state once per row DirectPixelWriter pw; pw.init(*ctx->renderer); - pw.beginRow(outY); + pw.beginRow(outY, outXBase); DirectCacheWriter cw; if (caching) { @@ -220,7 +220,7 @@ int pngDrawCallback(PNGDRAW* pDraw) { ditheredGray = gray / 85; if (ditheredGray > 3) ditheredGray = 3; } - pw.writePixel(outX, ditheredGray); + pw.writePixel(ditheredGray); if (caching) cw.writePixel(outX, ditheredGray); } diff --git a/lib/FsHelpers/FsHelpers.cpp b/lib/FsHelpers/FsHelpers.cpp index 616b094b52..3f557f6ebd 100644 --- a/lib/FsHelpers/FsHelpers.cpp +++ b/lib/FsHelpers/FsHelpers.cpp @@ -66,6 +66,8 @@ bool hasPngExtension(std::string_view fileName) { return checkFileExtension(file bool hasBmpExtension(std::string_view fileName) { return checkFileExtension(fileName, ".bmp"); } +bool hasPxcExtension(std::string_view fileName) { return checkFileExtension(fileName, ".pxc"); } + bool hasGifExtension(std::string_view fileName) { return checkFileExtension(fileName, ".gif"); } bool hasEpubExtension(std::string_view fileName) { return checkFileExtension(fileName, ".epub"); } diff --git a/lib/FsHelpers/FsHelpers.h b/lib/FsHelpers/FsHelpers.h index f8af636a08..6880100b95 100644 --- a/lib/FsHelpers/FsHelpers.h +++ b/lib/FsHelpers/FsHelpers.h @@ -31,6 +31,9 @@ inline bool hasPngExtension(const String& fileName) { // Check for .bmp extension (case-insensitive) bool hasBmpExtension(std::string_view fileName); +// Check for .pxc extension (case-insensitive) +bool hasPxcExtension(std::string_view fileName); + // Check for .gif extension (case-insensitive) bool hasGifExtension(std::string_view fileName); inline bool hasGifExtension(const String& fileName) { diff --git a/lib/GfxRenderer/Bitmap.cpp b/lib/GfxRenderer/Bitmap.cpp index 776e52f3b2..6231cfc30f 100644 --- a/lib/GfxRenderer/Bitmap.cpp +++ b/lib/GfxRenderer/Bitmap.cpp @@ -1,5 +1,7 @@ #include "Bitmap.h" +#include + #include #include @@ -19,6 +21,33 @@ Bitmap::~Bitmap() { delete atkinsonDitherer; delete fsDitherer; + + free(readChunkBuf); +} + +int Bitmap::bufferedRead(uint8_t* dst, int n) const { + if (!readChunkBuf) { + readChunkBuf = static_cast(malloc(READ_CHUNK_SIZE)); + if (!readChunkBuf) { + LOG_ERR("BMP", "bufferedRead: malloc failed (%d bytes)", READ_CHUNK_SIZE); + return 0; + } + } + + int done = 0; + while (done < n) { + if (readChunkPos >= readChunkFill) { + readChunkFill = file.read(readChunkBuf, READ_CHUNK_SIZE); + readChunkPos = 0; + if (readChunkFill <= 0) break; + } + const int avail = readChunkFill - readChunkPos; + const int take = (avail < n - done) ? avail : n - done; + memcpy(dst + done, readChunkBuf + readChunkPos, take); + readChunkPos += take; + done += take; + } + return done; } uint16_t Bitmap::readLE16(FsFile& f) { @@ -180,7 +209,7 @@ BmpReaderError Bitmap::parseHeaders() { // packed 2bpp output, 0 = black, 1 = dark gray, 2 = light gray, 3 = white BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const { // Note: rowBuffer should be pre-allocated by the caller to size 'rowBytes' - if (file.read(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow; + if (bufferedRead(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow; prevRowY += 1; @@ -193,16 +222,16 @@ BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const { auto packPixel = [&](const uint8_t lum) { uint8_t color; if (atkinsonDitherer) { - color = atkinsonDitherer->processPixel(adjustPixel(lum), currentX); + color = atkinsonDitherer->processPixel(lum, currentX); } else if (fsDitherer) { - color = fsDitherer->processPixel(adjustPixel(lum), currentX); + color = fsDitherer->processPixel(lum, currentX); } else { if (nativePalette) { - // Palette matches native gray levels: direct mapping (still apply brightness/contrast/gamma) - color = static_cast(adjustPixel(lum) >> 6); + // Palette matches native gray levels: direct 2-bit mapping + color = static_cast(lum >> 6); } else { // Non-native palette with dithering disabled: simple quantization - color = quantize(adjustPixel(lum), currentX, prevRowY); + color = quantize(lum, currentX, prevRowY); } } currentOutByte |= (color << bitShift); @@ -287,6 +316,10 @@ BmpReaderError Bitmap::rewindToData() const { return BmpReaderError::SeekPixelDataFailed; } + // Invalidate chunk buffer — file position changed, buffered bytes are stale. + readChunkPos = 0; + readChunkFill = 0; + // Reset dithering when rewinding if (fsDitherer) fsDitherer->reset(); if (atkinsonDitherer) atkinsonDitherer->reset(); diff --git a/lib/GfxRenderer/Bitmap.h b/lib/GfxRenderer/Bitmap.h index bba4189f29..58abdb6faf 100644 --- a/lib/GfxRenderer/Bitmap.h +++ b/lib/GfxRenderer/Bitmap.h @@ -81,6 +81,10 @@ class Bitmap { static uint16_t readLE16(FsFile& f); static uint32_t readLE32(FsFile& f); + // Reads exactly n bytes from the internal chunk buffer, refilling from SD as needed. + // Returns bytes actually read (< n on EOF/error). + int bufferedRead(uint8_t* dst, int n) const; + FsFile& file; bool dithering = false; int width = 0; @@ -100,4 +104,10 @@ class Bitmap { mutable AtkinsonDitherer* atkinsonDitherer = nullptr; mutable FloydSteinbergDitherer* fsDitherer = nullptr; + + // SD read-ahead buffer: rows are served from here; refilled from SD in READ_CHUNK_SIZE chunks. + static constexpr int READ_CHUNK_SIZE = 4096; // 8 SD sectors per fill — enables CMD18 multi-block + mutable uint8_t* readChunkBuf = nullptr; + mutable int readChunkPos = 0; + mutable int readChunkFill = 0; }; diff --git a/lib/GfxRenderer/BitmapHelpers.cpp b/lib/GfxRenderer/BitmapHelpers.cpp index dca059ec9e..11f93a6e9f 100644 --- a/lib/GfxRenderer/BitmapHelpers.cpp +++ b/lib/GfxRenderer/BitmapHelpers.cpp @@ -5,11 +5,14 @@ #include "Bitmap.h" +// LUT-aware quantization profile: false = factory (default), true = differential +bool g_differentialQuantize = false; + // Brightness/Contrast adjustments: -constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments -constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50) +constexpr bool USE_BRIGHTNESS = true; // true: apply brightness/gamma adjustments +constexpr int BRIGHTNESS_BOOST = 0; // No boost — quality LUT already renders slightly lighter constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) -constexpr float CONTRAST_FACTOR = 1.15f; // Contrast multiplier (1.0 = no change, >1 = more contrast) +constexpr float CONTRAST_FACTOR = 1.2f; // Contrast boost for quality LUT (softer drive needs more contrast) constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering // Integer approximation of gamma correction (brightens midtones) @@ -52,18 +55,21 @@ int adjustPixel(int gray) { return gray; } -// Simple quantization without dithering - divide into 4 levels -// The thresholds are fine-tuned to the X4 display +// Simple quantization without dithering. +// Factory LUT (fast/quality): evenly-spaced thresholds — softer, linear drive. +// Differential LUT: calibrated thresholds from upstream — narrow darkGrey band, +// biased toward lighter levels to compensate for more aggressive drive. uint8_t quantizeSimple(int gray) { - if (gray < 45) { - return 0; - } else if (gray < 70) { - return 1; - } else if (gray < 140) { - return 2; - } else { + if (g_differentialQuantize) { + if (gray < 45) return 0; + if (gray < 70) return 1; + if (gray < 140) return 2; return 3; } + if (gray < 43) return 0; + if (gray < 128) return 1; + if (gray < 213) return 2; + return 3; } // Hash-based noise dithering - survives downsampling without moiré artifacts diff --git a/lib/GfxRenderer/BitmapHelpers.h b/lib/GfxRenderer/BitmapHelpers.h index d9d2d85544..e95a393360 100644 --- a/lib/GfxRenderer/BitmapHelpers.h +++ b/lib/GfxRenderer/BitmapHelpers.h @@ -11,6 +11,10 @@ uint8_t quantizeSimple(int gray); uint8_t quantize1bit(int gray, int x, int y); int adjustPixel(int gray); +// Set true when rendering for differential LUT to use calibrated thresholds. +// Set false (default) for factory LUT (fast/quality) — uses evenly-spaced thresholds. +extern bool g_differentialQuantize; + enum class BmpRowOrder { BottomUp, TopDown }; // Populates a 1-bit BMP header in the provided memory. @@ -128,35 +132,38 @@ class AtkinsonDitherer { if (adjusted > 255) adjusted = 255; // Quantize to 4 levels + // Differential LUT: calibrated thresholds matching upstream (narrow darkGrey band, + // biased toward light to compensate for more aggressive drive). + // Factory LUT (fast/quality): evenly-spaced thresholds — softer drive, linear response. uint8_t quantized; int quantizedValue; - if (false) { // original thresholds - if (adjusted < 43) { + if (g_differentialQuantize) { + if (adjusted < 45) { quantized = 0; quantizedValue = 0; - } else if (adjusted < 128) { + } else if (adjusted < 70) { quantized = 1; quantizedValue = 85; - } else if (adjusted < 213) { + } else if (adjusted < 140) { quantized = 2; quantizedValue = 170; } else { quantized = 3; quantizedValue = 255; } - } else { // fine-tuned to X4 eink display - if (adjusted < 30) { + } else { + if (adjusted < 43) { quantized = 0; - quantizedValue = 15; - } else if (adjusted < 50) { + quantizedValue = 0; + } else if (adjusted < 128) { quantized = 1; - quantizedValue = 30; - } else if (adjusted < 140) { + quantizedValue = 85; + } else if (adjusted < 213) { quantized = 2; - quantizedValue = 80; + quantizedValue = 170; } else { quantized = 3; - quantizedValue = 210; + quantizedValue = 255; } } @@ -231,36 +238,36 @@ class FloydSteinbergDitherer { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // Quantize to 4 levels (0, 85, 170, 255) + // Quantize to 4 levels — see Atkinson ditherer comment for threshold rationale uint8_t quantized; int quantizedValue; - if (false) { // original thresholds - if (adjusted < 43) { + if (g_differentialQuantize) { + if (adjusted < 45) { quantized = 0; quantizedValue = 0; - } else if (adjusted < 128) { + } else if (adjusted < 70) { quantized = 1; quantizedValue = 85; - } else if (adjusted < 213) { + } else if (adjusted < 140) { quantized = 2; quantizedValue = 170; } else { quantized = 3; quantizedValue = 255; } - } else { // fine-tuned to X4 eink display - if (adjusted < 30) { + } else { + if (adjusted < 43) { quantized = 0; - quantizedValue = 15; - } else if (adjusted < 50) { + quantizedValue = 0; + } else if (adjusted < 128) { quantized = 1; - quantizedValue = 30; - } else if (adjusted < 140) { + quantizedValue = 85; + } else if (adjusted < 213) { quantized = 2; - quantizedValue = 80; + quantizedValue = 170; } else { quantized = 3; - quantizedValue = 210; + quantizedValue = 255; } } diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index a343badccd..0e6cd684c0 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1,10 +1,12 @@ #include "GfxRenderer.h" +#include #include #include #include #include +#include "BitmapHelpers.h" #include "FontCacheManager.h" const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const { @@ -137,7 +139,15 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode // We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update renderer.drawPixel(screenX, screenY, false); } else if (renderMode == GfxRenderer::GRAYSCALE_LSB && bmpVal == 1) { - // Dark gray + // Differential LSB: mark dark gray pixels only + renderer.drawPixel(screenX, screenY, false); + } else if (renderMode == GfxRenderer::GRAY2_LSB && !(bmpVal & 1)) { + // Factory absolute LSB (BW RAM): set BW=1 for Black(0) and LightGrey(2) + // clearScreen(0x00) base; drawPixel(false) sets bit to 1 + renderer.drawPixel(screenX, screenY, false); + } else if (renderMode == GfxRenderer::GRAY2_MSB && bmpVal < 2) { + // Factory absolute MSB (RED RAM): set RED=1 for Black(0) and DarkGrey(1) + // clearScreen(0x00) base; drawPixel(false) sets bit to 1 renderer.drawPixel(screenX, screenY, false); } } @@ -160,7 +170,11 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode const uint8_t bit_index = 7 - (pixelPosition & 7); if ((byte >> bit_index) & 1) { - renderer.drawPixel(screenX, screenY, pixelState); + // In GRAY2 modes the framebuffer convention is inverted vs BW: clearScreen(0x00) is + // background and drawPixel(false) marks active pixels. BW-convention callers pass + // pixelState=true for "black" — invert here so 1-bit UI glyphs stay visible. + const bool gray2 = renderMode == GfxRenderer::GRAY2_LSB || renderMode == GfxRenderer::GRAY2_MSB; + renderer.drawPixel(screenX, screenY, gray2 ? !pixelState : pixelState); } } } @@ -179,7 +193,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { // Bounds checking against runtime panel dimensions if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) { - LOG_ERR("GFX", "!! Outside range (%d, %d) -> (%d, %d)", x, y, phyX, phyY); + LOG_DBG("GFX", "!! Outside range (%d, %d) -> (%d, %d)", x, y, phyX, phyY); return; } @@ -189,11 +203,35 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { if (state) { frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit + // Single-pass: erasing a pixel must also clear the MSB plane so UI white fills (e.g. button + // hint backgrounds drawn on top of a full-screen image) fully erase image bits from both + // planes. Without this, image pixels remain in RED RAM and bleed through white areas. + if (renderMode == GRAY2_LSB && secondaryFrameBuffer != nullptr) { + secondaryFrameBuffer[byteIndex] &= ~(1 << bitPosition); + } } else { frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit + // Single-pass: all set-bit draws in GRAY2_LSB mode (1-bit UI elements, text, icons) are + // treated as fully black and mirrored to the MSB plane so they don't render as light gray. + // Image pixels skip drawPixel and go through drawPixelToBuffer directly (see drawBitmap), + // so this path is only hit by 1-bit rendering (renderChar, drawIcon, etc.). + if (renderMode == GRAY2_LSB && secondaryFrameBuffer != nullptr) { + secondaryFrameBuffer[byteIndex] |= 1 << bitPosition; + } } } +// Writes a single pixel (always state=false / set bit) to an arbitrary buffer using the same +// orientation transform as drawPixel. Used by single-pass grayscale to write the MSB plane +// simultaneously with the LSB plane during a single renderFn call. +void GfxRenderer::drawPixelToBuffer(uint8_t* buf, const int x, const int y) const { + int phyX = 0, phyY = 0; + rotateCoordinates(orientation, x, y, &phyX, &phyY, panelWidth, panelHeight); + if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) return; + const uint32_t byteIndex = static_cast(phyY) * panelWidthBytes + (phyX / 8); + buf[byteIndex] |= 1 << (7 - (phyX % 8)); +} + int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontFamily::Style style) const { const auto fontIt = fontMap.find(fontId); if (fontIt == fontMap.end()) { @@ -275,19 +313,22 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const { if (fontCacheManager_ && fontCacheManager_->isScanning()) return; + // In GRAY2 modes the framebuffer convention is inverted vs BW: clearScreen(0x00) is background + // and drawPixel(false) marks active pixels. BW-convention callers pass state=true for "black". + const bool s = (renderMode == GRAY2_LSB || renderMode == GRAY2_MSB) ? !state : state; if (x1 == x2) { if (y2 < y1) { std::swap(y1, y2); } for (int y = y1; y <= y2; y++) { - drawPixel(x1, y, state); + drawPixel(x1, y, s); } } else if (y1 == y2) { if (x2 < x1) { std::swap(x1, x2); } for (int x = x1; x <= x2; x++) { - drawPixel(x, y1, state); + drawPixel(x, y1, s); } } else { // Bresenham's line algorithm — integer arithmetic only @@ -300,7 +341,7 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con int err = dx - dy; while (true) { - drawPixel(x1, y1, state); + drawPixel(x1, y1, s); if (x1 == x2 && y1 == y2) break; int e2 = 2 * err; if (e2 > -dy) { @@ -352,6 +393,8 @@ void GfxRenderer::drawArc(const int maxRadius, const int cx, const int cy, const const int outerRadiusSq = outerRadius * outerRadius; const int innerRadiusSq = innerRadius * innerRadius; + // Do NOT pre-invert state for GRAY2 here: fillRect→drawLine already handles the GRAY2 + // inversion. A pre-inversion here would double-invert (cancel out), rendering the wrong color. int xOuter = outerRadius; int xInner = innerRadius; @@ -454,22 +497,28 @@ void GfxRenderer::drawPixelDither(const int x, const int y) const template <> void GfxRenderer::drawPixelDither(const int x, const int y) const { - drawPixel(x, y, true); + const bool gray2 = renderMode == GRAY2_LSB || renderMode == GRAY2_MSB; + drawPixel(x, y, !gray2); } template <> void GfxRenderer::drawPixelDither(const int x, const int y) const { - drawPixel(x, y, false); + const bool gray2 = renderMode == GRAY2_LSB || renderMode == GRAY2_MSB; + drawPixel(x, y, gray2); } template <> void GfxRenderer::drawPixelDither(const int x, const int y) const { - drawPixel(x, y, x % 2 == 0 && y % 2 == 0); + const bool pix = x % 2 == 0 && y % 2 == 0; + const bool gray2 = renderMode == GRAY2_LSB || renderMode == GRAY2_MSB; + drawPixel(x, y, gray2 ? !pix : pix); } template <> void GfxRenderer::drawPixelDither(const int x, const int y) const { - drawPixel(x, y, (x + y) % 2 == 0); // TODO: maybe find a better pattern? + const bool pix = (x + y) % 2 == 0; // TODO: maybe find a better pattern? + const bool gray2 = renderMode == GRAY2_LSB || renderMode == GRAY2_MSB; + drawPixel(x, y, gray2 ? !pix : pix); } void GfxRenderer::fillRectDither(const int x, const int y, const int width, const int height, Color color) const { @@ -683,12 +732,88 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con return; } + // --- Pre-compute everything that is constant for the entire render --- + + // Orientation: collapse into 6 integer coefficients (same approach as DirectPixelWriter). + // phyX = phyXBase + screenY*phyXStepY + screenX*phyXStepX + // phyY = phyYBase + screenY*phyYStepY + screenX*phyYStepX + int phyXBase, phyYBase, phyXStepX, phyYStepX, phyXStepY, phyYStepY; + switch (orientation) { + case Portrait: + phyXBase = 0; + phyYBase = panelHeight - 1; + phyXStepX = 0; + phyYStepX = -1; + phyXStepY = 1; + phyYStepY = 0; + break; + case LandscapeClockwise: + phyXBase = panelWidth - 1; + phyYBase = panelHeight - 1; + phyXStepX = -1; + phyYStepX = 0; + phyXStepY = 0; + phyYStepY = -1; + break; + case PortraitInverted: + phyXBase = panelWidth - 1; + phyYBase = 0; + phyXStepX = 0; + phyYStepX = 1; + phyXStepY = -1; + phyYStepY = 0; + break; + case LandscapeCounterClockwise: + default: + phyXBase = 0; + phyYBase = 0; + phyXStepX = 1; + phyYStepX = 0; + phyXStepY = 0; + phyYStepY = 1; + break; + } + + // Per-val write masks (val is 2-bit: 0=black,1=darkGrey,2=lightGrey,3=white). + // Bit i of the mask is set when val==i should trigger a write. + // Evaluated once here; zero branch overhead inside the pixel loop. + uint8_t writeFbMask = 0; // which val values write to frameBuffer + uint8_t writeFb2Mask = 0; // which val values write to secondaryFrameBuffer (GRAY2_LSB single-pass only) + bool fbClearBit = false; // true = clear bit (BW black); false = set bit (all gray modes) + uint8_t* const fb2 = secondaryFrameBuffer; + + switch (renderMode) { + case BW: + writeFbMask = 0x3; + fbClearBit = true; + break; // val 0,1 (black+darkGrey) + case GRAYSCALE_MSB: + writeFbMask = 0x6; + break; // val 1,2 + case GRAYSCALE_LSB: + writeFbMask = 0x2; + break; // val 1 + case GRAY2_LSB: + writeFbMask = 0x5; // val 0,2 (LSB plane) + if (fb2) writeFb2Mask = 0x3; + break; // val 0,1 (MSB plane) + case GRAY2_MSB: + writeFbMask = 0x3; + break; // val 0,1 + default: + break; + } + + // Pre-computed for the unscaled incremental inner loop: stride through the physical Y axis per logical X step. + const int32_t byteIdxYStep = static_cast(phyYStepX) * static_cast(panelWidthBytes); + + // --- Outer row loop --- for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) { // The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative). // Screen's (0, 0) is the top-left corner. int screenY = -cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY); if (isScaled) { - screenY = std::floor(screenY * scale); + screenY = static_cast(std::floor(screenY * scale)); } screenY += y; // the offset should not be scaled if (screenY >= getScreenHeight()) { @@ -711,27 +836,63 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con continue; } - for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) { - int screenX = bmpX - cropPixX; - if (isScaled) { - screenX = std::floor(screenX * scale); - } - screenX += x; // the offset should not be scaled - if (screenX >= getScreenWidth()) { - break; - } - if (screenX < 0) { - continue; - } - - const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3; + // Pre-compute the Y-dependent portion of the physical coordinate transform once per row. + const int rowPhyXBase = phyXBase + screenY * phyXStepY; + const int rowPhyYBase = phyYBase + screenY * phyYStepY; - if (renderMode == BW && val < 3) { - drawPixel(screenX, screenY); - } else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) { - drawPixel(screenX, screenY, false); - } else if (renderMode == GRAYSCALE_LSB && val == 1) { - drawPixel(screenX, screenY, false); + if (isScaled) { + // Scaled path: float accumulator replaces per-column multiply. + // Integer coordinate multiplies remain but are rare (scaled images only). + float screenXF = static_cast(x); + for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++, screenXF += scale) { + const int screenX = static_cast(screenXF); + if (screenX >= getScreenWidth()) break; + if (screenX < 0) continue; + + const uint8_t val = (outputRow[bmpX >> 2] >> (6 - (bmpX & 3) * 2)) & 0x3; + const bool doFb = (writeFbMask >> val) & 1; + const bool doFb2 = (writeFb2Mask >> val) & 1; + if (!doFb && !doFb2) continue; + + const int phyX = rowPhyXBase + screenX * phyXStepX; + const int phyY = rowPhyYBase + screenX * phyYStepX; + const uint32_t byteIdx = static_cast(phyY) * panelWidthBytes + (phyX >> 3); + const uint8_t bitMask = 1 << (7 - (phyX & 7)); + if (doFb) { + if (fbClearBit) + frameBuffer[byteIdx] &= ~bitMask; + else + frameBuffer[byteIdx] |= bitMask; + } + if (doFb2) fb2[byteIdx] |= bitMask; + } + } else { + // Unscaled path: fully incremental — zero multiplies, zero float in the pixel loop. + // curPhyX and curByteIdxY start at screenX=x (when bmpX=cropPixX) and advance by + // phyXStepX / byteIdxYStep per column. The for-increment fires on every iteration + // including continue, so running state stays in sync with bmpX even for skipped pixels. + int curPhyX = rowPhyXBase + x * phyXStepX; + int32_t curByteIdx = static_cast(rowPhyYBase + x * phyYStepX) * static_cast(panelWidthBytes); + for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; + bmpX++, curPhyX += phyXStepX, curByteIdx += byteIdxYStep) { + const int screenX = bmpX - cropPixX + x; + if (screenX >= getScreenWidth()) break; + if (screenX < 0) continue; + + const uint8_t val = (outputRow[bmpX >> 2] >> (6 - (bmpX & 3) * 2)) & 0x3; + const bool doFb = (writeFbMask >> val) & 1; + const bool doFb2 = (writeFb2Mask >> val) & 1; + if (!doFb && !doFb2) continue; + + const uint32_t byteIdx = static_cast(curByteIdx) + static_cast(curPhyX >> 3); + const uint8_t bitMask = 1 << (7 - (curPhyX & 7)); + if (doFb) { + if (fbClearBit) + frameBuffer[byteIdx] &= ~bitMask; + else + frameBuffer[byteIdx] |= bitMask; + } + if (doFb2) fb2[byteIdx] |= bitMask; } } } @@ -796,10 +957,13 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, // Get 2-bit value (result of readNextRow quantization) const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3; - // For 1-bit source: 0 or 1 -> map to black (0,1,2) or white (3) - // val < 3 means black pixel (draw it) + // For 1-bit source: val < 3 = black, val == 3 = white if (val < 3) { - drawPixel(screenX, screenY, true); + if (renderMode == GRAY2_LSB || renderMode == GRAY2_MSB) { + drawPixel(screenX, screenY, false); + } else { + drawPixel(screenX, screenY, true); + } } // White pixels (val == 3) are not drawn (leave background) } @@ -885,6 +1049,11 @@ void GfxRenderer::clearScreen(const uint8_t color) const { display.clearScreen(color); } +void GfxRenderer::setScreenshotHook(ScreenshotHook hook, void* ctx) { + screenshotHook = hook; + screenshotHookCtx = ctx; +} + void GfxRenderer::invertScreen() const { for (uint32_t i = 0; i < frameBufferSize; i++) { frameBuffer[i] = ~frameBuffer[i]; @@ -894,7 +1063,21 @@ void GfxRenderer::invertScreen() const { void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { auto elapsed = millis() - start_ms; LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed); - display.displayBuffer(refreshMode, fadingFix); + // After a factory LUT render the display already powered down (0xC7 sequence). + // Requesting turnOffScreen=true here would immediately power on then off again, + // adding a full power cycle. Skip the power-down for this one transition. + const bool turnOff = (displayState == DisplayState::FactoryLut) ? false : fadingFix; + display.displayBuffer(refreshMode, turnOff); + displayState = DisplayState::BW; +} + +void GfxRenderer::displayGrayBuffer(const unsigned char* lut, const bool factoryMode) const { + display.displayGrayBuffer(fadingFix, lut, factoryMode); + if (factoryMode) { + displayState = DisplayState::FactoryLut; + } else { + displayState = DisplayState::BW; + } } std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth, @@ -1172,7 +1355,211 @@ void GfxRenderer::copyGrayscaleLsbBuffers() const { display.copyGrayscaleLsbBuff void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(frameBuffer); } -void GfxRenderer::displayGrayBuffer() const { display.displayGrayBuffer(fadingFix); } +void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), + const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), + const void* preFlashCtx) { + if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { + // Pre-flash to white so the factory LUT can drive particles reliably from any prior state. + // Without this, particles stranded at intermediate grays may not complete their transition: + // from a known-white state only downward transitions are needed, which both LUTs handle cleanly. + // + // HALF_REFRESH (CTRL1_BYPASS_RED) guarantees true white regardless of RED RAM sync state. + // FAST_REFRESH is differential against RED RAM — after any prior grayscale operation the RED RAM + // may be stale (e.g. chapter menu rendered while display shows gray), so pixels the controller + // believes are already white may physically be at gray or chapter-menu positions and won't be + // driven to white, corrupting the subsequent gray render. + clearScreen(); + if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); + displayBuffer(HalDisplay::HALF_REFRESH); + } + + const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; + const RenderMode msbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_MSB : GRAY2_MSB; + const bool factoryMode = (mode != GrayscaleMode::Differential); + const unsigned char* lut = (mode == GrayscaleMode::FactoryFast) ? lut_factory_fast + : (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality + : nullptr; + + g_differentialQuantize = (mode == GrayscaleMode::Differential); + + clearScreen(0x00); + setRenderMode(lsbMode); + renderFn(*this, ctx); + + // Save LSB plane for screenshot hook (needs both planes simultaneously). + uint8_t* lsbCopy = nullptr; + if (screenshotHook && factoryMode) { + lsbCopy = static_cast(malloc(frameBufferSize)); + if (lsbCopy) { + memcpy(lsbCopy, frameBuffer, frameBufferSize); + } else { + // Allocation failed — disarm the one-shot hook so it doesn't fire on a future render. + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + } + } + copyGrayscaleLsbBuffers(); + + clearScreen(0x00); + setRenderMode(msbMode); + renderFn(*this, ctx); + copyGrayscaleMsbBuffers(); + + // Fire hook: LSB = lsbCopy, MSB = frameBuffer (still holds second-pass data). + if (screenshotHook && factoryMode && lsbCopy) { + screenshotHook(lsbCopy, frameBuffer, panelWidth, panelHeight, screenshotHookCtx); + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + } + if (lsbCopy) { + free(lsbCopy); + lsbCopy = nullptr; + } + + g_differentialQuantize = false; + + displayGrayBuffer(lut, factoryMode); + setRenderMode(BW); +} + +void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), + const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), + const void* preFlashCtx) { + if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { + clearScreen(); + if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); + displayBuffer(HalDisplay::HALF_REFRESH); + } + + const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; + const bool factoryMode = (mode != GrayscaleMode::Differential); + const unsigned char* lut = (mode == GrayscaleMode::FactoryFast) ? lut_factory_fast + : (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality + : nullptr; + + g_differentialQuantize = (mode == GrayscaleMode::Differential); + + // Allocate secondary buffer for the MSB plane. + auto* secBuf = static_cast(malloc(frameBufferSize)); + if (!secBuf) { + LOG_ERR("GFX", "renderGrayscaleSinglePass: malloc failed (%lu bytes), falling back to two-pass", + static_cast(frameBufferSize)); + // Disarm hook — the two-pass fallback does not capture both planes simultaneously. + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + // Pre-flash already done; run two-pass directly without repeating it. + clearScreen(0x00); + setRenderMode(lsbMode); + renderFn(*this, ctx); + copyGrayscaleLsbBuffers(); + clearScreen(0x00); + setRenderMode(mode == GrayscaleMode::Differential ? GRAYSCALE_MSB : GRAY2_MSB); + renderFn(*this, ctx); + copyGrayscaleMsbBuffers(); + g_differentialQuantize = false; + displayGrayBuffer(lut, factoryMode); + setRenderMode(BW); + return; + } + memset(secBuf, 0x00, frameBufferSize); + secondaryFrameBuffer = secBuf; + + // Single pass: renderFn writes LSB plane to frameBuffer and MSB plane to secondaryFrameBuffer. + clearScreen(0x00); + setRenderMode(lsbMode); + renderFn(*this, ctx); + + // One-shot screenshot hook: fired while both planes are still in software, before either is + // pushed to the controller. frameBuffer = LSB plane, secBuf = MSB plane. + if (screenshotHook && factoryMode) { + screenshotHook(frameBuffer, secBuf, panelWidth, panelHeight, screenshotHookCtx); + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + } + + // Push LSB plane (frameBuffer) → BW RAM. + copyGrayscaleLsbBuffers(); + + // Push MSB plane (secondaryFrameBuffer → frameBuffer → RED RAM). + memcpy(frameBuffer, secBuf, frameBufferSize); + copyGrayscaleMsbBuffers(); + + free(secBuf); + secondaryFrameBuffer = nullptr; + + g_differentialQuantize = false; + displayGrayBuffer(lut, factoryMode); + setRenderMode(BW); +} + +void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, + const uint16_t pageHeight) { + const size_t colBytes = (pageHeight + 7) / 8; + const uint16_t fbStride = panelWidthBytes; + + // Bounds check: each column c writes colBytes bytes at frameBuffer[c * fbStride]. + // Requires pageWidth <= panelHeight and colBytes <= panelWidthBytes. + if (pageWidth > static_cast(panelHeight) || colBytes > panelWidthBytes) { + LOG_ERR("GFX", "displayXtchPlanes: page %ux%u overflows framebuffer (%ux%u)", pageWidth, pageHeight, panelHeight, + panelWidth); + if (screenshotHook) { + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + } + return; + } + + // Pass 1: plane1 (MSB) → BW RAM via copyGrayscaleLsbBuffers. + clearScreen(0x00); + for (uint16_t c = 0; c < pageWidth; c++) { + const uint8_t* srcCol = plane1 + static_cast(c) * colBytes; + uint8_t* dstRow = frameBuffer + static_cast(c) * fbStride; + for (uint16_t b = 0; b < colBytes; b++) { + dstRow[b] = srcCol[b]; + } + } + + copyGrayscaleLsbBuffers(); + + // Pass 2: plane2 (LSB) → RED RAM via copyGrayscaleMsbBuffers. + clearScreen(0x00); + for (uint16_t c = 0; c < pageWidth; c++) { + const uint8_t* srcCol = plane2 + static_cast(c) * colBytes; + uint8_t* dstRow = frameBuffer + static_cast(c) * fbStride; + for (uint16_t b = 0; b < colBytes; b++) { + dstRow[b] = srcCol[b]; + } + } + copyGrayscaleMsbBuffers(); + + // Fire hook: plane1 input IS already in framebuffer format (colBytes == fbStride for portrait + // pages), so pass it directly — no extra malloc needed. plane2 data is now in frameBuffer. + if (screenshotHook) { + screenshotHook(plane1, frameBuffer, panelWidth, panelHeight, screenshotHookCtx); + screenshotHook = nullptr; + screenshotHookCtx = nullptr; + } + + const bool isX3 = gpio.deviceIsX3(); + displayGrayBuffer(isX3 ? nullptr : lut_factory_quality, !isX3); + setRenderMode(BW); +} + +void GfxRenderer::displayXtcBwPage(const uint8_t* pageBuffer, const uint16_t pageWidth, const uint16_t pageHeight) { + const size_t srcRowBytes = (pageWidth + 7) / 8; + + // 1-bit content has no AA — render as plain BW and use the standard differential fast-refresh + // LUT (same as menus/EPUB). No factory LUT needed; avoids all GRAY2 convention complexity. + clearScreen(); + for (uint16_t y = 0; y < pageHeight; y++) { + for (uint16_t x = 0; x < pageWidth; x++) { + if (!((pageBuffer[y * srcRowBytes + x / 8] >> (7 - (x % 8))) & 1)) { + drawPixel(x, y, true); + } + } + } + displayBuffer(HalDisplay::FAST_REFRESH); +} void GfxRenderer::freeBwBufferChunks() { for (auto& bwBufferChunk : bwBufferChunks) { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index e683e3122e..0f0ffd4a47 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -18,7 +18,13 @@ enum Color : uint8_t { Clear = 0x00, White = 0x01, LightGray = 0x05, DarkGray = class GfxRenderer { public: - enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; + enum RenderMode { + BW, // 1-bit black/white + GRAYSCALE_LSB, // Differential gray: mark pixels for LSB plane (clearScreen(0x00) + drawPixel(false)) + GRAYSCALE_MSB, // Differential gray: mark pixels for MSB plane (clearScreen(0x00) + drawPixel(false)) + GRAY2_LSB, // Factory absolute gray: encode BW RAM = bit0 (clearScreen(0x00) + drawPixel(false)) + GRAY2_MSB, // Factory absolute gray: encode RED RAM = bit1 (clearScreen(0x00) + drawPixel(false)) + }; // Logical screen orientation from the perspective of callers enum Orientation { @@ -28,6 +34,27 @@ class GfxRenderer { LandscapeCounterClockwise // 800x480 logical coordinates, native panel orientation }; + // Selects LUT, pixel-plane encoding, and pre-flash behavior for renderGrayscale(). + enum class GrayscaleMode { + FactoryFast, // Factory absolute 2-bit (lut_factory_fast); HALF_REFRESH pre-flash to white + FactoryQuality, // Factory absolute 2-bit (lut_factory_quality); HALF_REFRESH pre-flash to white + Differential, // Differential 2-bit overlay (no LUT); no pre-flash, requires prior BW state + }; + + // Display state — tracks whether the physical display was last updated via a factory LUT render. + // BW: frameBuffer mirrors the display (menus, EPUB reader). + // FactoryLut: display holds a grayscale image; frameBuffer has been reset to white by + // cleanupGrayscaleWithFrameBuffer() and no longer represents what is visually shown. + enum class DisplayState { BW, FactoryLut }; + + // One-shot hook fired in renderGrayscaleSinglePass after renderFn() writes both planes + // but before they are pushed to the controller. At that moment: + // lsbPlane = frameBuffer (LSB / BW RAM plane) + // msbPlane = secondaryFrameBuffer (MSB / RED RAM plane) + // Hook is cleared automatically after firing. + using ScreenshotHook = void (*)(const uint8_t* lsbPlane, const uint8_t* msbPlane, int physWidth, int physHeight, + void* ctx); + private: static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory @@ -36,12 +63,16 @@ class GfxRenderer { Orientation orientation; bool fadingFix; uint8_t* frameBuffer = nullptr; + uint8_t* secondaryFrameBuffer = nullptr; // MSB plane buffer for single-pass grayscale decode uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH; uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT; uint16_t panelWidthBytes = HalDisplay::DISPLAY_WIDTH_BYTES; uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE; std::vector bwBufferChunks; std::map fontMap; + mutable DisplayState displayState = DisplayState::BW; + ScreenshotHook screenshotHook = nullptr; + void* screenshotHookCtx = nullptr; // Mutable because drawText() is const but needs to delegate scan-mode // recording to the (non-const) FontCacheManager. Same pragmatic compromise @@ -55,6 +86,7 @@ class GfxRenderer { void drawPixelDither(int x, int y) const; template void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir) const; + void drawPixelToBuffer(uint8_t* buf, int x, int y) const; public: explicit GfxRenderer(HalDisplay& halDisplay) @@ -141,21 +173,50 @@ class GfxRenderer { EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; int getTextHeight(int fontId) const; + DisplayState getDisplayState() const { return displayState; } + void setScreenshotHook(ScreenshotHook hook, void* ctx); + // Grayscale functions void setRenderMode(const RenderMode mode) { this->renderMode = mode; } RenderMode getRenderMode() const { return renderMode; } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; - void displayGrayBuffer() const; + void displayGrayBuffer(const unsigned char* lut = nullptr, bool factoryMode = false) const; bool storeBwBuffer(); // Returns true if buffer was stored successfully void restoreBwBuffer(); // Restore and free the stored buffer void cleanupGrayscaleWithFrameBuffer() const; + // Two-pass grayscale render. renderFn is called twice: once with the LSB render mode set + // (writes BW RAM plane), then with the MSB mode set (writes RED RAM plane). The method + // handles pre-flash (FactoryFast only), clearScreen, setRenderMode, buffer copies, + // displayGrayBuffer, and resets renderMode to BW on completion. + // storeBwBuffer / restoreBwBuffer remain the caller's responsibility. + // preFlashOverlayFn (optional): called after clearScreen() but before the pre-flash displayBuffer(), + // allowing callers to draw a loading indicator that appears during the pre-flash without an extra refresh. + void renderGrayscale(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, + void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, + const void* preFlashCtx = nullptr); + // Single-pass variant: calls renderFn once in GRAY2_LSB mode while simultaneously writing + // the MSB plane to a secondary buffer. Cuts SD card reads from 2 to 1 for file-backed renders. + // Falls back to two-pass on secondary buffer allocation failure. + void renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, + void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, + const void* preFlashCtx = nullptr); + + // Direct 2-bit XTCH plane blit using factory LUT. Caller supplies the two decoded bit planes + // (plane1 = BW RAM / LSB, plane2 = RED RAM / MSB) in column-major order matching XTCH encoding. + // Handles pre-flash, both RAM writes, factory LUT fire, and BW controller sync internally. + void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight); + + // 1-bit XTC page via the same grayscale LUT pipeline. Row-major pageBuffer (XTC: 0=black, 1=white). + // BW and RED RAM receive identical data since there are no intermediate gray levels. + void displayXtcBwPage(const uint8_t* pageBuffer, uint16_t pageWidth, uint16_t pageHeight); // Font helpers const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const; // Low level functions uint8_t* getFrameBuffer() const; + uint8_t* getSecondaryFrameBuffer() const { return secondaryFrameBuffer; } size_t getBufferSize() const; uint16_t getDisplayWidth() const { return panelWidth; } uint16_t getDisplayHeight() const { return panelHeight; } diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 5f0388d28f..2b49b78208 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -142,22 +142,131 @@ bool Xtc::generateCoverBmp() const { // Get bit depth const uint8_t bitDepth = parser->getBitDepth(); - // Allocate buffer for page data - // XTG (1-bit): Row-major, ((width+7)/8) * height bytes - // XTH (2-bit): Two bit planes, column-major, ((width * height + 7) / 8) * 2 bytes - size_t bitmapSize; + // --- 2-bit XTCH/XTH: two-pass plane loading to stay within heap limits --- + // Each plane is ~48KB (fits MaxAlloc); both planes together (~96KB) do not. if (bitDepth == 2) { - bitmapSize = ((static_cast(pageInfo.width) * pageInfo.height + 7) / 8) * 2; - } else { - bitmapSize = ((pageInfo.width + 7) / 8) * pageInfo.height; + const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; + const size_t colBytes = (pageInfo.height + 7) / 8; + const uint32_t rowSize2 = ((static_cast(pageInfo.width) * 2 + 31) / 32) * 4; + const std::string tempPath = cachePath + "/cover_tmp.bmp"; + + // Pass 1: load plane1, write raw 2-bit rows to temp file (bit2=0, pixel = bit1<<1) + { + uint8_t* plane1 = static_cast(malloc(planeSize)); + if (!plane1) { + LOG_ERR("XTC", "Failed to alloc plane1 (%lu bytes)", planeSize); + return false; + } + if (const_cast(parser.get())->loadPageMsb(0, plane1, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane1 for cover"); + free(plane1); + return false; + } + FsFile tempFile; + if (!Storage.openFileForWrite("XTC", tempPath, tempFile)) { + LOG_ERR("XTC", "Failed to open temp cover file"); + free(plane1); + return false; + } + uint8_t rowBuf[256]; + for (uint16_t y = 0; y < pageInfo.height; y++) { + memset(rowBuf, 0, rowSize2); + for (uint16_t x = 0; x < pageInfo.width; x++) { + const size_t bo = (pageInfo.width - 1 - x) * colBytes + y / 8; + const uint8_t bit1 = (plane1[bo] >> (7 - (y % 8))) & 1; + rowBuf[x / 4] |= static_cast((bit1 << 1) << (6 - (x % 4) * 2)); + } + tempFile.write(rowBuf, rowSize2); + } + tempFile.close(); + free(plane1); + } + + // Pass 2: load plane2, combine with pass1, write final 2-bit BMP + { + uint8_t* plane2 = static_cast(malloc(planeSize)); + if (!plane2) { + LOG_ERR("XTC", "Failed to alloc plane2 (%lu bytes)", planeSize); + Storage.remove(tempPath.c_str()); + return false; + } + if (const_cast(parser.get())->loadPageLsb(0, plane2, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane2 for cover"); + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } + FsFile tempFile, coverFile; + if (!Storage.openFileForRead("XTC", tempPath, tempFile)) { + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } + if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverFile)) { + tempFile.close(); + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } + // Write 2-bit BMP header + const uint32_t imageSize2 = rowSize2 * pageInfo.height; + const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; + coverFile.write('B'); + coverFile.write('M'); + coverFile.write(reinterpret_cast(&fileSize2), 4); + uint32_t rsv2 = 0; + coverFile.write(reinterpret_cast(&rsv2), 4); + uint32_t doff2 = 14 + 40 + 16; + coverFile.write(reinterpret_cast(&doff2), 4); + uint32_t dibSz2 = 40; + coverFile.write(reinterpret_cast(&dibSz2), 4); + int32_t ww2 = pageInfo.width; + coverFile.write(reinterpret_cast(&ww2), 4); + int32_t hh2 = -static_cast(pageInfo.height); + coverFile.write(reinterpret_cast(&hh2), 4); + uint16_t pl2 = 1; + coverFile.write(reinterpret_cast(&pl2), 2); + uint16_t bpp2 = 2; + coverFile.write(reinterpret_cast(&bpp2), 2); + uint32_t cmp2 = 0, ppm2 = 2835, cu2 = 4, ci2 = 4; + coverFile.write(reinterpret_cast(&cmp2), 4); + coverFile.write(reinterpret_cast(&imageSize2), 4); + coverFile.write(reinterpret_cast(&ppm2), 4); + coverFile.write(reinterpret_cast(&ppm2), 4); + coverFile.write(reinterpret_cast(&cu2), 4); + coverFile.write(reinterpret_cast(&ci2), 4); + static constexpr uint8_t pal2[16] = {0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, + 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; + coverFile.write(pal2, 16); + // For each row: read pass1 row (bit1 only), OR in bit2, write to final + uint8_t rowBuf[256]; + for (uint16_t y = 0; y < pageInfo.height; y++) { + memset(rowBuf, 0, rowSize2); + tempFile.read(rowBuf, rowSize2); + for (uint16_t x = 0; x < pageInfo.width; x++) { + const size_t bo = (pageInfo.width - 1 - x) * colBytes + y / 8; + const uint8_t bit2 = (plane2[bo] >> (7 - (y % 8))) & 1; + rowBuf[x / 4] |= static_cast(bit2 << (6 - (x % 4) * 2)); + } + coverFile.write(rowBuf, rowSize2); + } + coverFile.close(); + tempFile.close(); + free(plane2); + Storage.remove(tempPath.c_str()); + } + LOG_DBG("XTC", "Generated 2-bit cover BMP: %s", getCoverBmpPath().c_str()); + return true; } + + // 1-bit (XTC/XTG) path + const size_t bitmapSize = ((pageInfo.width + 7) / 8) * pageInfo.height; uint8_t* pageBuffer = static_cast(malloc(bitmapSize)); if (!pageBuffer) { LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize); return false; } - // Load first page (cover) size_t bytesRead = const_cast(parser.get())->loadPage(0, pageBuffer, bitmapSize); if (bytesRead == 0) { LOG_ERR("XTC", "Failed to load cover page"); @@ -165,7 +274,6 @@ bool Xtc::generateCoverBmp() const { return false; } - // Create BMP file FsFile coverBmp; if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) { LOG_DBG("XTC", "Failed to create cover BMP file"); @@ -173,29 +281,23 @@ bool Xtc::generateCoverBmp() const { return false; } - // Write 1-bit BMP header (top-down row order) BmpHeader bmpHeader; createBmpHeader(&bmpHeader, pageInfo.width, pageInfo.height, BmpRowOrder::TopDown); coverBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); const uint32_t rowSize = ((pageInfo.width + 31) / 32) * 4; + const size_t srcRowSize = (pageInfo.width + 7) / 8; // Write bitmap data // BMP requires 4-byte row alignment - const size_t dstRowSize = (pageInfo.width + 7) / 8; // 1-bit destination row size + const size_t dstRowSize = (pageInfo.width + 7) / 8; if (bitDepth == 2) { - // XTH 2-bit mode: Two bit planes, column-major order - // - Columns scanned right to left (x = width-1 down to 0) - // - 8 vertical pixels per byte (MSB = topmost pixel in group) - // - First plane: Bit1, Second plane: Bit2 - // - Pixel value = (bit1 << 1) | bit2 const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; - const uint8_t* plane1 = pageBuffer; // Bit1 plane - const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane - const size_t colBytes = (pageInfo.height + 7) / 8; // Bytes per column + const uint8_t* plane1 = pageBuffer; + const uint8_t* plane2 = pageBuffer + planeSize; + const size_t colBytes = (pageInfo.height + 7) / 8; - // Allocate a row buffer for 1-bit output uint8_t* rowBuffer = static_cast(malloc(dstRowSize)); if (!rowBuffer) { free(pageBuffer); @@ -203,32 +305,27 @@ bool Xtc::generateCoverBmp() const { } for (uint16_t y = 0; y < pageInfo.height; y++) { - memset(rowBuffer, 0xFF, dstRowSize); // Start with all white + memset(rowBuffer, 0xFF, dstRowSize); for (uint16_t x = 0; x < pageInfo.width; x++) { - // Column-major, right to left: column index = (width - 1 - x) const size_t colIndex = pageInfo.width - 1 - x; const size_t byteInCol = y / 8; - const size_t bitInByte = 7 - (y % 8); // MSB = topmost pixel + const size_t bitInByte = 7 - (y % 8); const size_t byteOffset = colIndex * colBytes + byteInCol; const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1; const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1; const uint8_t pixelValue = (bit1 << 1) | bit2; - // Threshold: 0=white (1); 1,2,3=black (0) if (pixelValue >= 1) { - // Set bit to 0 (black) in BMP format const size_t dstByte = x / 8; const size_t dstBit = 7 - (x % 8); rowBuffer[dstByte] &= ~(1 << dstBit); } } - // Write converted row coverBmp.write(rowBuffer, dstRowSize); - // Pad to 4-byte boundary uint8_t padding[4] = {0, 0, 0, 0}; size_t paddingSize = rowSize - dstRowSize; if (paddingSize > 0) { @@ -238,14 +335,9 @@ bool Xtc::generateCoverBmp() const { free(rowBuffer); } else { - // 1-bit source: write directly with proper padding - const size_t srcRowSize = (pageInfo.width + 7) / 8; - for (uint16_t y = 0; y < pageInfo.height; y++) { - // Write source row coverBmp.write(pageBuffer + y * srcRowSize, srcRowSize); - // Pad to 4-byte boundary uint8_t padding[4] = {0, 0, 0, 0}; size_t paddingSize = rowSize - srcRowSize; if (paddingSize > 0) { @@ -328,20 +420,162 @@ bool Xtc::generateThumbBmp(int height) const { LOG_DBG("XTC", "Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)", pageInfo.width, pageInfo.height, thumbWidth, thumbHeight, scale); - // Allocate buffer for page data - size_t bitmapSize; + // For 2-bit (XTCH): two-pass plane loading → 2-bit BMP output with 4-level grayscale palette. + // Full page (96KB) exceeds MaxAlloc; load each plane separately (~48KB). if (bitDepth == 2) { - bitmapSize = ((static_cast(pageInfo.width) * pageInfo.height + 7) / 8) * 2; - } else { - bitmapSize = ((pageInfo.width + 7) / 8) * pageInfo.height; + const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; + const size_t colBytes = (pageInfo.height + 7) / 8; + const uint32_t scaleInv_fp2 = static_cast(65536.0f / scale); + const size_t plane1BitsSize = (static_cast(thumbWidth) * thumbHeight + 7) / 8; + uint8_t* plane1Bits = static_cast(malloc(plane1BitsSize)); + if (!plane1Bits) { + LOG_ERR("XTC", "Failed to alloc plane1bits (%lu bytes)", plane1BitsSize); + return false; + } + memset(plane1Bits, 0, plane1BitsSize); + uint8_t* planeBuffer = static_cast(malloc(planeSize)); + if (!planeBuffer) { + LOG_ERR("XTC", "Failed to alloc plane buffer (%lu bytes)", planeSize); + free(plane1Bits); + return false; + } + // Pass 1: plane1 (bit1/MSB) majority vote per output pixel + if (const_cast(parser.get())->loadPageMsb(0, planeBuffer, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane1 for thumb"); + free(planeBuffer); + free(plane1Bits); + return false; + } + for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { + uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; + uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; + if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + if (srcYE <= srcYS) srcYE = srcYS + 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; + uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; + if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + if (srcXE <= srcXS) srcXE = srcXS + 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t darkCount = 0, total = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; + total++; + } + } + if (total > 0 && darkCount * 2 >= total) { + const size_t pi = static_cast(dstY) * thumbWidth + dstX; + plane1Bits[pi / 8] |= static_cast(1u << (7 - (pi % 8))); + } + } + } + // Pass 2: plane2 (bit2/LSB) + combine → write 2-bit BMP + if (const_cast(parser.get())->loadPageLsb(0, planeBuffer, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane2 for thumb"); + free(planeBuffer); + free(plane1Bits); + return false; + } + FsFile thumbBmp2; + if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp2)) { + free(planeBuffer); + free(plane1Bits); + return false; + } + const uint32_t rowSize2 = ((static_cast(thumbWidth) * 2 + 31) / 32) * 4; + const uint32_t imageSize2 = rowSize2 * thumbHeight; + const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; + thumbBmp2.write('B'); + thumbBmp2.write('M'); + thumbBmp2.write(reinterpret_cast(&fileSize2), 4); + uint32_t rsv2 = 0; + thumbBmp2.write(reinterpret_cast(&rsv2), 4); + uint32_t doff2 = 14 + 40 + 16; + thumbBmp2.write(reinterpret_cast(&doff2), 4); + uint32_t dibSz2 = 40; + thumbBmp2.write(reinterpret_cast(&dibSz2), 4); + int32_t ww2 = thumbWidth; + thumbBmp2.write(reinterpret_cast(&ww2), 4); + int32_t hh2 = -static_cast(thumbHeight); + thumbBmp2.write(reinterpret_cast(&hh2), 4); + uint16_t pl2 = 1; + thumbBmp2.write(reinterpret_cast(&pl2), 2); + uint16_t bpp2 = 2; + thumbBmp2.write(reinterpret_cast(&bpp2), 2); + uint32_t cmp2 = 0, imgSz2 = imageSize2, ppm2 = 2835, cu2 = 4, ci2 = 4; + thumbBmp2.write(reinterpret_cast(&cmp2), 4); + thumbBmp2.write(reinterpret_cast(&imgSz2), 4); + thumbBmp2.write(reinterpret_cast(&ppm2), 4); + thumbBmp2.write(reinterpret_cast(&ppm2), 4); + thumbBmp2.write(reinterpret_cast(&cu2), 4); + thumbBmp2.write(reinterpret_cast(&ci2), 4); + // Palette: 0=white, 1=lightGrey(170), 2=darkGrey(85), 3=black — matches XTC pixel value + static constexpr uint8_t pal2[16] = {0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, + 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; + thumbBmp2.write(pal2, 16); + uint8_t* rowBuf2 = static_cast(malloc(rowSize2)); + if (!rowBuf2) { + free(planeBuffer); + free(plane1Bits); + thumbBmp2.close(); + Storage.remove(getThumbBmpPath(height).c_str()); + return false; + } + for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { + memset(rowBuf2, 0, rowSize2); + uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; + uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; + if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + if (srcYE <= srcYS) srcYE = srcYS + 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; + uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; + if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + if (srcXE <= srcXS) srcXE = srcXS + 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t darkCount = 0, total = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; + total++; + } + } + const size_t pi = static_cast(dstY) * thumbWidth + dstX; + const uint8_t bit1 = (plane1Bits[pi / 8] >> (7 - (pi % 8))) & 1; + const uint8_t bit2 = (total > 0 && darkCount * 2 >= total) ? 1 : 0; + const uint8_t twoBit = (bit1 << 1) | bit2; + const size_t bi2 = dstX / 4; + const int bs2 = 6 - static_cast(dstX % 4) * 2; + if (bi2 < rowSize2) rowBuf2[bi2] |= static_cast(twoBit << bs2); + } + thumbBmp2.write(rowBuf2, rowSize2); + } + free(rowBuf2); + free(planeBuffer); + free(plane1Bits); + thumbBmp2.close(); + LOG_DBG("XTC", "Generated 2-bit thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); + return true; } + + // 1-bit (XTC/XTG) path + const size_t bitmapSize = ((pageInfo.width + 7) / 8) * pageInfo.height; uint8_t* pageBuffer = static_cast(malloc(bitmapSize)); if (!pageBuffer) { LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize); return false; } - - // Load first page (cover) size_t bytesRead = const_cast(parser.get())->loadPage(0, pageBuffer, bitmapSize); if (bytesRead == 0) { LOG_ERR("XTC", "Failed to load cover page for thumb"); @@ -349,7 +583,6 @@ bool Xtc::generateThumbBmp(int height) const { return false; } - // Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes) FsFile thumbBmp; if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { LOG_DBG("XTC", "Failed to create thumb BMP file"); @@ -357,34 +590,22 @@ bool Xtc::generateThumbBmp(int height) const { return false; } - // Write 1-bit BMP header (top-down row order) BmpHeader bmpHeader; createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; - - // Allocate row buffer for 1-bit output uint8_t* rowBuffer = static_cast(malloc(rowSize)); if (!rowBuffer) { free(pageBuffer); return false; } - // Fixed-point scale factor (16.16) - uint32_t scaleInv_fp = static_cast(65536.0f / scale); - - // Pre-calculate plane info for 2-bit mode - const size_t planeSize = (bitDepth == 2) ? ((static_cast(pageInfo.width) * pageInfo.height + 7) / 8) : 0; - const uint8_t* plane1 = (bitDepth == 2) ? pageBuffer : nullptr; - const uint8_t* plane2 = (bitDepth == 2) ? pageBuffer + planeSize : nullptr; - const size_t colBytes = (bitDepth == 2) ? ((pageInfo.height + 7) / 8) : 0; - const size_t srcRowBytes = (bitDepth == 1) ? ((pageInfo.width + 7) / 8) : 0; + const uint32_t scaleInv_fp = static_cast(65536.0f / scale); + const size_t srcRowBytes = (pageInfo.width + 7) / 8; for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - memset(rowBuffer, 0xFF, rowSize); // Start with all white (bit 1) - - // Calculate source Y range with bounds checking + memset(rowBuffer, 0xFF, rowSize); uint32_t srcYStart = (static_cast(dstY) * scaleInv_fp) >> 16; uint32_t srcYEnd = (static_cast(dstY + 1) * scaleInv_fp) >> 16; if (srcYStart >= pageInfo.height) srcYStart = pageInfo.height - 1; @@ -393,7 +614,6 @@ bool Xtc::generateThumbBmp(int height) const { if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height; for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - // Calculate source X range with bounds checking uint32_t srcXStart = (static_cast(dstX) * scaleInv_fp) >> 16; uint32_t srcXEnd = (static_cast(dstX + 1) * scaleInv_fp) >> 16; if (srcXStart >= pageInfo.width) srcXStart = pageInfo.width - 1; @@ -401,82 +621,38 @@ bool Xtc::generateThumbBmp(int height) const { if (srcXEnd <= srcXStart) srcXEnd = srcXStart + 1; if (srcXEnd > pageInfo.width) srcXEnd = pageInfo.width; - // Area averaging: sum grayscale values (0-255 range) - uint32_t graySum = 0; - uint32_t totalCount = 0; - + uint32_t graySum = 0, totalCount = 0; for (uint32_t srcY = srcYStart; srcY < srcYEnd && srcY < pageInfo.height; srcY++) { for (uint32_t srcX = srcXStart; srcX < srcXEnd && srcX < pageInfo.width; srcX++) { - uint8_t grayValue = 255; // Default: white - - if (bitDepth == 2) { - // XTH 2-bit mode: pixel value 0-3 - // Bounds check for column index - if (srcX < pageInfo.width) { - const size_t colIndex = pageInfo.width - 1 - srcX; - const size_t byteInCol = srcY / 8; - const size_t bitInByte = 7 - (srcY % 8); - const size_t byteOffset = colIndex * colBytes + byteInCol; - // Bounds check for buffer access - if (byteOffset < planeSize) { - const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1; - const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1; - const uint8_t pixelValue = (bit1 << 1) | bit2; - // Convert 2-bit (0-3) to grayscale: 0=black, 3=white - // pixelValue: 0=white, 1=light gray, 2=dark gray, 3=black (XTC polarity) - grayValue = (3 - pixelValue) * 85; // 0->255, 1->170, 2->85, 3->0 - } - } - } else { - // 1-bit mode - const size_t byteIdx = srcY * srcRowBytes + srcX / 8; - const size_t bitIdx = 7 - (srcX % 8); - // Bounds check for buffer access - if (byteIdx < bitmapSize) { - const uint8_t pixelBit = (pageBuffer[byteIdx] >> bitIdx) & 1; - // XTC 1-bit polarity: 0=black, 1=white (same as BMP palette) - grayValue = pixelBit ? 255 : 0; - } + const size_t byteIdx = srcY * srcRowBytes + srcX / 8; + if (byteIdx < bitmapSize) { + graySum += ((pageBuffer[byteIdx] >> (7 - (srcX % 8))) & 1) ? 255 : 0; + totalCount++; } - - graySum += grayValue; - totalCount++; } } - // Calculate average grayscale and quantize to 1-bit with noise dithering uint8_t avgGray = (totalCount > 0) ? static_cast(graySum / totalCount) : 255; - - // Hash-based noise dithering for 1-bit output uint32_t hash = static_cast(dstX) * 374761393u + static_cast(dstY) * 668265263u; hash = (hash ^ (hash >> 13)) * 1274126177u; - const int threshold = static_cast(hash >> 24); // 0-255 - const int adjustedThreshold = 128 + ((threshold - 128) / 2); // Range: 64-192 - - // Quantize to 1-bit: 0=black, 1=white - uint8_t oneBit = (avgGray >= adjustedThreshold) ? 1 : 0; - - // Pack 1-bit value into row buffer (MSB first, 8 pixels per byte) + const int threshold = static_cast(hash >> 24); + const int adjustedThreshold = 128 + ((threshold - 128) / 2); + const uint8_t oneBit = (avgGray >= adjustedThreshold) ? 1 : 0; const size_t byteIndex = dstX / 8; const size_t bitOffset = 7 - (dstX % 8); - // Bounds check for row buffer access if (byteIndex < rowSize) { - if (oneBit) { - rowBuffer[byteIndex] |= (1 << bitOffset); // Set bit for white - } else { - rowBuffer[byteIndex] &= ~(1 << bitOffset); // Clear bit for black - } + if (oneBit) + rowBuffer[byteIndex] |= (1 << bitOffset); + else + rowBuffer[byteIndex] &= ~(1 << bitOffset); } } - - // Write row (already padded to 4-byte boundary by rowSize) thumbBmp.write(rowBuffer, rowSize); } free(rowBuffer); free(pageBuffer); - - LOG_DBG("XTC", "Generated thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); + LOG_DBG("XTC", "Generated 1-bit thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); return true; } @@ -515,6 +691,20 @@ size_t Xtc::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) con return const_cast(parser.get())->loadPage(pageIndex, buffer, bufferSize); } +size_t Xtc::loadPageMsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) const { + if (!loaded || !parser) { + return 0; + } + return const_cast(parser.get())->loadPageMsb(pageIndex, buffer, bufferSize); +} + +size_t Xtc::loadPageLsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) const { + if (!loaded || !parser) { + return 0; + } + return const_cast(parser.get())->loadPageLsb(pageIndex, buffer, bufferSize); +} + xtc::XtcError Xtc::loadPageStreaming(uint32_t pageIndex, std::function callback, size_t chunkSize) const { diff --git a/lib/Xtc/Xtc.h b/lib/Xtc/Xtc.h index 3dbd160b4f..912f6ff162 100644 --- a/lib/Xtc/Xtc.h +++ b/lib/Xtc/Xtc.h @@ -82,6 +82,8 @@ class Xtc { * @return Number of bytes read */ size_t loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) const; + size_t loadPageMsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) const; + size_t loadPageLsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) const; /** * Load page with streaming callback diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index e30f779642..48f5dcbcea 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -459,6 +459,134 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz return bytesRead; } +size_t XtcParser::loadPageMsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) { + if (m_bitDepth != 2) { + return loadPage(pageIndex, buffer, bufferSize); + } + + if (!m_isOpen) { + m_lastError = XtcError::FILE_NOT_FOUND; + return 0; + } + + if (pageIndex >= m_header.pageCount) { + m_lastError = XtcError::PAGE_OUT_OF_RANGE; + return 0; + } + + PageInfo page; + if (!readPageTableEntry(pageIndex, page)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + + if (!ensureFileOpen()) { + m_lastError = XtcError::FILE_NOT_FOUND; + return 0; + } + + if (!m_file.seek(page.offset)) { + LOG_DBG("XTC", "Failed to seek to page %u at offset %lu", pageIndex, page.offset); + m_lastError = XtcError::READ_ERROR; + return 0; + } + + XtgPageHeader pageHeader; + size_t headerRead = m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)); + if (headerRead != sizeof(XtgPageHeader)) { + LOG_DBG("XTC", "Failed to read page header for page %u", pageIndex); + m_lastError = XtcError::READ_ERROR; + return 0; + } + + if (pageHeader.magic != XTH_MAGIC) { + LOG_DBG("XTC", "Invalid page magic for page %u: 0x%08X", pageIndex, pageHeader.magic); + m_lastError = XtcError::INVALID_MAGIC; + return 0; + } + + // Only plane1 (MSB): (width * height + 7) / 8 bytes + const size_t planeSize = (static_cast(pageHeader.width) * pageHeader.height + 7) / 8; + + if (bufferSize < planeSize) { + LOG_DBG("XTC", "Buffer too small for MSB plane: need %u, have %u", planeSize, bufferSize); + m_lastError = XtcError::MEMORY_ERROR; + return 0; + } + + size_t bytesRead = m_file.read(buffer, planeSize); + if (bytesRead != planeSize) { + LOG_DBG("XTC", "MSB plane read error: expected %u, got %u", planeSize, bytesRead); + m_lastError = XtcError::READ_ERROR; + return 0; + } + + m_lastError = XtcError::OK; + return bytesRead; +} + +size_t XtcParser::loadPageLsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) { + if (m_bitDepth != 2) return loadPage(pageIndex, buffer, bufferSize); + + if (!m_isOpen) { + m_lastError = XtcError::FILE_NOT_FOUND; + return 0; + } + if (pageIndex >= m_header.pageCount) { + m_lastError = XtcError::PAGE_OUT_OF_RANGE; + return 0; + } + + PageInfo page; + if (!readPageTableEntry(pageIndex, page)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + + if (!ensureFileOpen()) { + m_lastError = XtcError::FILE_NOT_FOUND; + return 0; + } + + if (!m_file.seek(page.offset)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + + XtgPageHeader pageHeader; + if (m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)) != sizeof(XtgPageHeader)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + if (pageHeader.magic != XTH_MAGIC) { + LOG_DBG("XTC", "loadPageLsb: invalid magic for page %u: 0x%08X", pageIndex, pageHeader.magic); + m_lastError = XtcError::INVALID_MAGIC; + return 0; + } + + const size_t planeSize = (static_cast(pageHeader.width) * pageHeader.height + 7) / 8; + const uint32_t plane2Offset = page.offset + sizeof(XtgPageHeader) + static_cast(planeSize); + + if (!m_file.seek(plane2Offset)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + + if (bufferSize < planeSize) { + LOG_DBG("XTC", "Buffer too small for LSB plane: need %u, have %u", planeSize, bufferSize); + m_lastError = XtcError::MEMORY_ERROR; + return 0; + } + + size_t bytesRead = m_file.read(buffer, planeSize); + if (bytesRead != planeSize) { + m_lastError = XtcError::READ_ERROR; + return 0; + } + m_lastError = XtcError::OK; + return bytesRead; +} + XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, std::function callback, size_t chunkSize) { diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index b688d79350..3901a72873 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -57,6 +57,29 @@ class XtcParser { */ size_t loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize); + /** + * Load only plane1 (MSB) of a 2-bit page into buffer. + * For 1-bit pages behaves identically to loadPage. + * Requires only half the buffer of loadPage for 2-bit pages, fitting within heap limits. + * + * @param pageIndex Page index (0-based) + * @param buffer Output buffer (caller allocated, must hold planeSize bytes) + * @param bufferSize Buffer size + * @return Number of bytes read on success, 0 on failure + */ + size_t loadPageMsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize); + + /** + * Load only plane2 (LSB) of a 2-bit page into buffer. + * For 1-bit pages behaves identically to loadPage. + * + * @param pageIndex Page index (0-based) + * @param buffer Output buffer (caller allocated, must hold planeSize bytes) + * @param bufferSize Buffer size + * @return Number of bytes read on success, 0 on failure + */ + size_t loadPageLsb(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize); + /** * Streaming page load * Memory-efficient method that reads page data in chunks. diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index 665df5356b..4f1ec764f4 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -80,7 +80,9 @@ void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); } -void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); } +void HalDisplay::displayGrayBuffer(bool turnOffScreen, const unsigned char* lut, bool factoryMode) { + einkDisplay.displayGrayBuffer(turnOffScreen, lut, factoryMode); +} uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index a0a7f92083..d12a5c5b8a 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -47,7 +47,7 @@ class HalDisplay { void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer); void cleanupGrayscaleBuffers(const uint8_t* bwBuffer); - void displayGrayBuffer(bool turnOffScreen = false); + void displayGrayBuffer(bool turnOffScreen = false, const unsigned char* lut = nullptr, bool factoryMode = false); // Runtime geometry passthrough uint16_t getDisplayWidth() const; diff --git a/open-x4-sdk b/open-x4-sdk index a64a3c29be..add90185ab 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit a64a3c29bebc59b2ccdfe15492cfc4b5e4c26360 +Subproject commit add90185ab77bb1704429b26493aa7e80a283e90 From 5e0976b0f60800cd65ba0eea1deedd787c093e9e Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 20 Apr 2026 00:15:02 +0200 Subject: [PATCH 02/93] feat: PXC/XTC viewers, sleep screen, and EPUB factory rendering Add PxcViewerActivity for PXC image display using factory LUT grayscale. Update BmpViewerActivity and SleepActivity with X3-aware grayscale mode. EPUB reader uses FactoryQuality on X4 for image pages, Differential on X3. XTC reader pre-flashes to white on entry; periodic FULL_REFRESH every 32 pages. Add UITheme metrics for PXC viewer layout. --- src/CrossPointSettings.h | 1 - src/activities/Activity.h | 5 + src/activities/ActivityManager.h | 2 + src/activities/boot_sleep/SleepActivity.cpp | 259 ++++++++++++++++--- src/activities/boot_sleep/SleepActivity.h | 8 + src/activities/home/FileBrowserActivity.cpp | 9 +- src/activities/reader/EpubReaderActivity.cpp | 113 ++++---- src/activities/reader/EpubReaderActivity.h | 5 + src/activities/reader/ReaderActivity.cpp | 9 + src/activities/reader/ReaderActivity.h | 2 + src/activities/reader/ReaderUtils.h | 1 + src/activities/reader/XtcReaderActivity.cpp | 218 +++++----------- src/activities/reader/XtcReaderActivity.h | 3 +- src/activities/util/BmpViewerActivity.cpp | 124 +++++++-- src/activities/util/BmpViewerActivity.h | 2 + src/activities/util/PxcViewerActivity.cpp | 209 +++++++++++++++ src/activities/util/PxcViewerActivity.h | 20 ++ src/components/UITheme.cpp | 2 +- 18 files changed, 733 insertions(+), 259 deletions(-) create mode 100644 src/activities/util/PxcViewerActivity.cpp create mode 100644 src/activities/util/PxcViewerActivity.h diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 7e50ac4fc0..f0db01551f 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -199,7 +199,6 @@ class CrossPointSettings { uint8_t showHiddenFiles = 0; // Image rendering mode in EPUB reader uint8_t imageRendering = IMAGES_DISPLAY; - ~CrossPointSettings() = default; // Get singleton instance diff --git a/src/activities/Activity.h b/src/activities/Activity.h index cc3fe44321..4a33f8a702 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -44,6 +44,11 @@ class Activity { virtual bool preventAutoSleep() { return false; } virtual bool isReaderActivity() const { return false; } + // Called when the screenshot combo is pressed and the display is in FactoryLut state. + // Activities that use renderGrayscaleSinglePass should override this to trigger a re-render, + // allowing the installed screenshot hook to capture both planes before they are pushed. + virtual void onScreenshotRequest() {} + // Start a new activity without destroying the current one // Note: requestUpdate() will be invoked automatically once resultHandler finishes void startActivityForResult(std::unique_ptr&& activity, ActivityResultHandler resultHandler); diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index bc975e919e..96a5482f62 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -96,6 +96,8 @@ class ActivityManager { // Note: if popActivity() on last activity on the stack, we will goHome() void popActivity(); + Activity* getCurrentActivity() const { return currentActivity.get(); } + bool preventAutoSleep() const; bool isReaderActivity() const; bool skipLoopDelay() const; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 06efdef7ac..1a2fdc211b 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "Epub/converters/DirectPixelWriter.h" #include "activities/reader/ReaderUtils.h" #include "components/UITheme.h" #include "fontIds.h" @@ -18,13 +20,9 @@ void SleepActivity::onEnter() { Activity::onEnter(); - // Show popup with reader orientation only when going to sleep from reader if (APP_STATE.lastSleepFromReader) { ReaderUtils::applyOrientation(renderer, SETTINGS.orientation); - GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); renderer.setOrientation(GfxRenderer::Orientation::Portrait); - } else { - GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); } switch (SETTINGS.sleepScreen) { @@ -72,14 +70,35 @@ void SleepActivity::renderCustomSleepScreen() const { continue; } - if (!FsHelpers::hasBmpExtension(filename)) { - LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name); + const bool isBmp = FsHelpers::hasBmpExtension(filename); + const bool isPxc = FsHelpers::hasPxcExtension(filename); + if (!isBmp && !isPxc) { + LOG_DBG("SLP", "Skipping non-BMP/PXC file: %s", name); + file.close(); continue; } - Bitmap bitmap(file); - if (bitmap.parseHeaders() != BmpReaderError::Ok) { - LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); - continue; + if (isBmp) { + Bitmap bitmap(file); + if (bitmap.parseHeaders() != BmpReaderError::Ok) { + LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); + file.close(); + continue; + } + } + if (isPxc) { + uint16_t w, h; + if (file.read(&w, 2) != 2 || file.read(&h, 2) != 2) { + LOG_DBG("SLP", "Skipping PXC with unreadable header: %s", name); + file.close(); + continue; + } + const int sw = renderer.getScreenWidth(); + const int sh = renderer.getScreenHeight(); + if (abs(w - sw) > 1 || abs(h - sh) > 1) { + LOG_DBG("SLP", "Skipping PXC size mismatch %dx%d (screen %dx%d): %s", w, h, sw, sh, name); + file.close(); + continue; + } } files.emplace_back(filename); } @@ -97,18 +116,37 @@ void SleepActivity::renderCustomSleepScreen() const { APP_STATE.pushRecentSleep(randomFileIndex); APP_STATE.saveToFile(); const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex]; + LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); + delay(100); + if (FsHelpers::hasPxcExtension(files[randomFileIndex])) { + renderPxcSleepScreen(filename); + dir.close(); + return; + } FsFile file; if (Storage.openFileForRead("SLP", filename, file)) { - LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); - delay(100); Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { + if (bitmap.hasGreyscale() && + SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER) { + lastGrayscalePath = filename; + lastGrayscaleIsPxc = false; + } renderBitmapSleepScreen(bitmap); return; } } } } + if (dir) dir.close(); + + // Check root for sleep.pxc (preferred) or sleep.bmp + if (Storage.exists("/sleep.pxc")) { + LOG_DBG("SLP", "Loading: /sleep.pxc"); + renderPxcSleepScreen("/sleep.pxc"); + return; + } + // Look for sleep.bmp on the root of the sd card to determine if we should // render a custom sleep screen instead of the default. FsFile file; @@ -116,6 +154,11 @@ void SleepActivity::renderCustomSleepScreen() const { Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { LOG_DBG("SLP", "Loading: /sleep.bmp"); + if (bitmap.hasGreyscale() && + SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER) { + lastGrayscalePath = "/sleep.bmp"; + lastGrayscaleIsPxc = false; + } renderBitmapSleepScreen(bitmap); return; } @@ -138,7 +181,118 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.invertScreen(); } - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); +} + +void SleepActivity::renderPxcSleepScreen(const std::string& path) const { + FsFile file; + if (!Storage.openFileForRead("SLP", path, file)) { + LOG_ERR("SLP", "Cannot open PXC: %s", path.c_str()); + return renderDefaultSleepScreen(); + } + + uint16_t pxcWidth, pxcHeight; + if (file.read(&pxcWidth, 2) != 2 || file.read(&pxcHeight, 2) != 2) { + LOG_ERR("SLP", "PXC header read failed: %s", path.c_str()); + file.close(); + return renderDefaultSleepScreen(); + } + + const int screenWidth = renderer.getScreenWidth(); + const int screenHeight = renderer.getScreenHeight(); + if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + LOG_ERR("SLP", "PXC size %dx%d does not match screen %dx%d", pxcWidth, pxcHeight, screenWidth, screenHeight); + file.close(); + return renderDefaultSleepScreen(); + } + + const uint32_t dataOffset = file.position(); // right after the 4-byte header + const auto filter = SETTINGS.sleepScreenCoverFilter; + const int bytesPerRow = (pxcWidth + 3) / 4; + + if (filter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER) { + lastGrayscalePath = path; + lastGrayscaleIsPxc = true; + struct PxcCtx { + FsFile* file; + uint32_t dataOffset; + int width, height; + }; + PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; + + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->file->seek(c->dataOffset); + + const int bpr = (c->width + 3) / 4; + uint8_t* rowBuf = static_cast(malloc(bpr)); + if (!rowBuf) { + LOG_ERR("SLP", "malloc failed for rowBuf (%d bytes, %dx%d)", bpr, c->width, c->height); + return; + } + + DirectPixelWriter pw; + pw.init(r); + + for (int row = 0; row < c->height; row++) { + if (c->file->read(rowBuf, bpr) != bpr) break; + pw.beginRow(row); + for (int col = 0; col < c->width; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + pw.writePixel(pv); + } + } + free(rowBuf); + }, + &ctx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_ENTERING_SLEEP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + } else { + // BLACK_AND_WHITE / INVERTED_BLACK_AND_WHITE: threshold PXC to 1-bit + // (pv 0=Black, 1=DarkGrey map to dark; 2=LightGrey, 3=White map to light) + renderer.clearScreen(); + if (!file.seek(dataOffset)) { + LOG_ERR("SLP", "PXC seek failed: %s", path.c_str()); + file.close(); + return renderDefaultSleepScreen(); + } + + uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); + if (!rowBuf) { + LOG_ERR("SLP", "PXC malloc failed"); + file.close(); + return renderDefaultSleepScreen(); + } + + for (int row = 0; row < pxcHeight; row++) { + if (file.read(rowBuf, bytesPerRow) != bytesPerRow) break; + for (int col = 0; col < pxcWidth; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + if (pv < 2) renderer.drawPixel(col, row, true); + } + } + free(rowBuf); + + if (filter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { + renderer.invertScreen(); + } + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + } + + file.close(); } void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { @@ -182,34 +336,44 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { } LOG_DBG("SLP", "drawing to %d x %d", x, y); - renderer.clearScreen(); const bool hasGreyscale = bitmap.hasGreyscale() && SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER; - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); - - if (SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { - renderer.invertScreen(); - } - - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - if (hasGreyscale) { - bitmap.rewindToData(); - renderer.clearScreen(0x00); - renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); - renderer.copyGrayscaleLsbBuffers(); - - bitmap.rewindToData(); - renderer.clearScreen(0x00); - renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); + struct BitmapGrayCtx { + const Bitmap* bitmap; + int x, y, maxWidth, maxHeight; + float cropX, cropY; + }; + BitmapGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, cropX, cropY}; + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); + }, + &grayCtx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_ENTERING_SLEEP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + } else { + renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); - renderer.copyGrayscaleMsbBuffers(); - - renderer.displayGrayBuffer(); - renderer.setRenderMode(GfxRenderer::BW); + if (SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { + renderer.invertScreen(); + } + renderer.displayBuffer(HalDisplay::FULL_REFRESH); } } @@ -284,6 +448,11 @@ void SleepActivity::renderCoverSleepScreen() const { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { LOG_DBG("SLP", "Rendering sleep cover: %s", coverBmpPath.c_str()); + if (bitmap.hasGreyscale() && + SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER) { + lastGrayscalePath = coverBmpPath; + lastGrayscaleIsPxc = false; + } renderBitmapSleepScreen(bitmap); return; } @@ -294,5 +463,23 @@ void SleepActivity::renderCoverSleepScreen() const { void SleepActivity::renderBlankSleepScreen() const { renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); +} + +void SleepActivity::onScreenshotRequest() { + if (lastGrayscalePath.empty()) return; + if (lastGrayscaleIsPxc) { + renderPxcSleepScreen(lastGrayscalePath); + } else { + FsFile file; + if (Storage.openFileForRead("SLP", lastGrayscalePath.c_str(), file)) { + Bitmap bitmap(file, true); + if (bitmap.parseHeaders() == BmpReaderError::Ok) { + renderBitmapSleepScreen(bitmap); + } + file.close(); + } + } + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); } diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 87df8ba19d..07bb76ea0d 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -1,4 +1,6 @@ #pragma once +#include + #include "../Activity.h" class Bitmap; @@ -8,11 +10,17 @@ class SleepActivity final : public Activity { explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("Sleep", renderer, mappedInput) {} void onEnter() override; + void onScreenshotRequest() override; private: void renderDefaultSleepScreen() const; void renderCustomSleepScreen() const; void renderCoverSleepScreen() const; void renderBitmapSleepScreen(const Bitmap& bitmap) const; + void renderPxcSleepScreen(const std::string& path) const; void renderBlankSleepScreen() const; + + // Tracks the last factory-LUT render so onScreenshotRequest() can re-render the same image. + mutable std::string lastGrayscalePath; + mutable bool lastGrayscaleIsPxc = false; }; diff --git a/src/activities/home/FileBrowserActivity.cpp b/src/activities/home/FileBrowserActivity.cpp index 1a4b455134..1c50520495 100644 --- a/src/activities/home/FileBrowserActivity.cpp +++ b/src/activities/home/FileBrowserActivity.cpp @@ -16,6 +16,11 @@ namespace { constexpr unsigned long GO_HOME_MS = 1000; + +bool isSupportedFile(std::string_view name) { + return FsHelpers::hasEpubExtension(name) || FsHelpers::hasXtcExtension(name) || FsHelpers::hasTxtExtension(name) || + FsHelpers::hasMarkdownExtension(name) || FsHelpers::hasBmpExtension(name) || FsHelpers::hasPxcExtension(name); +} } // namespace void sortFileList(std::vector& strs) { @@ -91,9 +96,7 @@ void FileBrowserActivity::loadFiles() { files.emplace_back(std::string(name) + "/"); } else { std::string_view filename{name}; - if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename) || - FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename) || - FsHelpers::hasBmpExtension(filename)) { + if (isSupportedFile(filename)) { files.emplace_back(filename); } } diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index bd700b761a..08b5780124 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -681,7 +681,12 @@ void EpubReaderActivity::render(RenderLock&& lock) { if (pendingScreenshot) { pendingScreenshot = false; - ScreenshotUtil::takeScreenshot(renderer); + if (lastPageWasFactoryGray) { + ScreenshotUtil::prepareFactoryLutScreenshot(renderer); + onScreenshotRequest(); + } else { + ScreenshotUtil::takeScreenshot(renderer); + } } } @@ -752,34 +757,20 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or LOG_DBG("ERS", "Heap: before=%lu after=%lu delta=%ld", heapBefore, heapAfter, (int32_t)heapAfter - (int32_t)heapBefore); - // Force special handling for pages with images when anti-aliasing is on - bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing; - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); renderStatusBar(); fcm->logStats("bw_render"); const auto tBwRender = millis(); - if (imagePageWithAA) { - // Double FAST_REFRESH with selective image blanking (pablohc's technique): - // HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust. - // Instead, blank only the image area and do two fast refreshes. - // Step 1: Display page with image area blanked (text appears, image area white) - // Step 2: Re-render with images and display again (images appear clean) - int16_t imgX, imgY, imgW, imgH; - if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) { - renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false); - renderer.displayBuffer(HalDisplay::FAST_REFRESH); - - // Re-render page content to restore images into the blanked area - // Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %) - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); - renderer.displayBuffer(HalDisplay::FAST_REFRESH); - } else { - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } - // Double FAST_REFRESH handles ghosting for image pages; don't count toward full refresh cadence + const bool isImagePage = page->hasImages(); + const bool useFactoryGray = SETTINGS.textAntiAliasing && isImagePage && !gpio.deviceIsX3(); + lastPageWasFactoryGray = useFactoryGray; + if (useFactoryGray) { + // Factory gray mode: skip BW display entirely — factory LUT drives pixels absolutely + lastFactoryMarginTop = orientedMarginTop; + lastFactoryMarginLeft = orientedMarginLeft; } else { + // Text-only AA or no AA: BW display with refresh cadence ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh); } const auto tDisplay = millis(); @@ -789,37 +780,40 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or const auto tBwStore = millis(); // grayscale rendering - // TODO: Only do this if font supports it if (SETTINGS.textAntiAliasing) { - renderer.clearScreen(0x00); - renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); - renderer.copyGrayscaleLsbBuffers(); - const auto tGrayLsb = millis(); - - // Render and copy to MSB buffer - renderer.clearScreen(0x00); - renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); - renderer.copyGrayscaleMsbBuffers(); - const auto tGrayMsb = millis(); - - // display grayscale part - renderer.displayGrayBuffer(); - const auto tGrayDisplay = millis(); - renderer.setRenderMode(GfxRenderer::BW); - fcm->logStats("gray"); + struct PageRenderCtx { + Page* page; + int fontId, left, top; + const EpubReaderActivity* activity; + }; + PageRenderCtx grayCtx{page.get(), SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop, this}; + const auto grayFn = [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->page->render(const_cast(r), c->fontId, c->left, c->top); + c->activity->renderStatusBar(); + }; + + const auto tGrayStart = millis(); + const auto grayMode = + useFactoryGray ? GfxRenderer::GrayscaleMode::FactoryQuality : GfxRenderer::GrayscaleMode::Differential; + renderer.renderGrayscale(grayMode, grayFn, &grayCtx); + const auto tGrayEnd = millis(); + fcm->logStats(useFactoryGray ? "gray_factory_quality" : "gray"); - // restore the bw data renderer.restoreBwBuffer(); + if (useFactoryGray) { + // Factory LUT leaves RED RAM in gray-encoded state; sync controller to the + // restored BW framebuffer so subsequent BW page turns render cleanly. + renderer.cleanupGrayscaleWithFrameBuffer(); + } const auto tBwRestore = millis(); const auto tEnd = millis(); LOG_DBG("ERS", - "Page render: prewarm=%lums bw_render=%lums display=%lums bw_store=%lums " - "gray_lsb=%lums gray_msb=%lums gray_display=%lums bw_restore=%lums total=%lums", - tPrewarm - t0, tBwRender - tPrewarm, tDisplay - tBwRender, tBwStore - tDisplay, tGrayLsb - tBwStore, - tGrayMsb - tGrayLsb, tGrayDisplay - tGrayMsb, tBwRestore - tGrayDisplay, tEnd - t0); + "Page render (%s): prewarm=%lums bw_render=%lums display=%lums bw_store=%lums " + "gray=%lums bw_restore=%lums total=%lums", + useFactoryGray ? "factory" : "diff", tPrewarm - t0, tBwRender - tPrewarm, tDisplay - tBwRender, + tBwStore - tDisplay, tGrayEnd - tGrayStart, tBwRestore - tGrayEnd, tEnd - t0); } else { // restore the bw data renderer.restoreBwBuffer(); @@ -833,6 +827,31 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or } } +void EpubReaderActivity::onScreenshotRequest() { + if (!section || !lastPageWasFactoryGray) return; + + auto p = section->loadPageFromSectionFile(); + if (!p) return; + + struct PageRenderCtx { + Page* page; + int fontId, left, top; + const EpubReaderActivity* activity; + }; + PageRenderCtx grayCtx{p.get(), SETTINGS.getReaderFontId(), lastFactoryMarginLeft, lastFactoryMarginTop, this}; + const auto grayFn = [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->page->render(const_cast(r), c->fontId, c->left, c->top); + c->activity->renderStatusBar(); + }; + + renderer.renderGrayscale( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, grayFn, + &grayCtx); + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); +} + void EpubReaderActivity::renderStatusBar() const { // Calculate progress in book const int currentPage = section->currentPage + 1; diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index d786ffed56..59a999c4f7 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -30,6 +30,10 @@ class EpubReaderActivity final : public Activity { bool pendingScreenshot = false; bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit bool automaticPageTurnActive = false; + // Context saved from the last factory-gray image page render, used by onScreenshotRequest(). + bool lastPageWasFactoryGray = false; + int lastFactoryMarginTop = 0; + int lastFactoryMarginLeft = 0; // Footnote support std::vector currentPageFootnotes; @@ -64,5 +68,6 @@ class EpubReaderActivity final : public Activity { void onExit() override; void loop() override; void render(RenderLock&& lock) override; + void onScreenshotRequest() override; bool isReaderActivity() const override { return true; } }; diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index 989e6d2231..aa3c5d03fb 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -12,6 +12,7 @@ #include "XtcReaderActivity.h" #include "activities/util/BmpViewerActivity.h" #include "activities/util/FullScreenMessageActivity.h" +#include "activities/util/PxcViewerActivity.h" bool ReaderActivity::isXtcFile(const std::string& path) { return FsHelpers::hasXtcExtension(path); } @@ -22,6 +23,8 @@ bool ReaderActivity::isTxtFile(const std::string& path) { bool ReaderActivity::isBmpFile(const std::string& path) { return FsHelpers::hasBmpExtension(path); } +bool ReaderActivity::isPxcFile(const std::string& path) { return FsHelpers::hasPxcExtension(path); } + std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { if (!Storage.exists(path.c_str())) { LOG_ERR("READER", "File does not exist: %s", path.c_str()); @@ -83,6 +86,10 @@ void ReaderActivity::onGoToBmpViewer(const std::string& path) { activityManager.replaceActivity(std::make_unique(renderer, mappedInput, path)); } +void ReaderActivity::onGoToPxcViewer(const std::string& path) { + activityManager.replaceActivity(std::make_unique(renderer, mappedInput, path)); +} + void ReaderActivity::onGoToXtcReader(std::unique_ptr xtc) { const auto xtcPath = xtc->getPath(); currentBookPath = xtcPath; @@ -106,6 +113,8 @@ void ReaderActivity::onEnter() { currentBookPath = initialBookPath; if (isBmpFile(initialBookPath)) { onGoToBmpViewer(initialBookPath); + } else if (isPxcFile(initialBookPath)) { + onGoToPxcViewer(initialBookPath); } else if (isXtcFile(initialBookPath)) { auto xtc = loadXtc(initialBookPath); if (!xtc) { diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index f5c61a393d..e4cf6194b2 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -17,12 +17,14 @@ class ReaderActivity final : public Activity { static bool isXtcFile(const std::string& path); static bool isTxtFile(const std::string& path); static bool isBmpFile(const std::string& path); + static bool isPxcFile(const std::string& path); void goToLibrary(const std::string& fromBookPath = ""); void onGoToEpubReader(std::unique_ptr epub); void onGoToXtcReader(std::unique_ptr xtc); void onGoToTxtReader(std::unique_ptr txt); void onGoToBmpViewer(const std::string& path); + void onGoToPxcViewer(const std::string& path); void onGoBack(); diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index 8694be080f..3c8d45c4a5 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -52,6 +52,7 @@ inline PageTurnResult detectPageTurn(const MappedInputManager& input) { inline void displayWithRefreshCycle(const GfxRenderer& renderer, int& pagesUntilFullRefresh) { if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.cleanupGrayscaleWithFrameBuffer(); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index d2b6f18ea7..a2f195f999 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -34,6 +34,10 @@ void XtcReaderActivity::onEnter() { xtc->setupCacheDir(); + // Pre-flash to white so the factory LUT can drive particles reliably from any prior state. + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + // Load saved progress loadProgress(); @@ -151,17 +155,64 @@ void XtcReaderActivity::renderPage() { const uint16_t pageHeight = xtc->getPageHeight(); const uint8_t bitDepth = xtc->getBitDepth(); - // Calculate buffer size for one page - // XTG (1-bit): Row-major, ((width+7)/8) * height bytes - // XTH (2-bit): Two bit planes, column-major, ((width * height + 7) / 8) * 2 bytes - size_t pageBufferSize; if (bitDepth == 2) { - pageBufferSize = ((static_cast(pageWidth) * pageHeight + 7) / 8) * 2; - } else { - pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; + // Load each XTCH plane separately to stay within heap limits. + // Combined size (~96KB) exceeds MaxAlloc; each plane (~48KB) fits. + const size_t planeSize = (static_cast(pageWidth) * pageHeight + 7) / 8; + + uint8_t* plane1 = static_cast(malloc(planeSize)); + if (!plane1) { + LOG_ERR("XTR", "Failed to allocate plane1 (%lu bytes)", planeSize); + renderer.clearScreen(); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_MEMORY_ERROR), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + return; + } + if (xtc->loadPageMsb(currentPage, plane1, planeSize) == 0) { + LOG_ERR("XTR", "Failed to load plane1 for page %lu", currentPage); + free(plane1); + renderer.clearScreen(); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + return; + } + + uint8_t* plane2 = static_cast(malloc(planeSize)); + if (!plane2) { + LOG_ERR("XTR", "Failed to allocate plane2 (%lu bytes)", planeSize); + free(plane1); + renderer.clearScreen(); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_MEMORY_ERROR), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + return; + } + if (xtc->loadPageLsb(currentPage, plane2, planeSize) == 0) { + LOG_ERR("XTR", "Failed to load plane2 for page %lu", currentPage); + free(plane1); + free(plane2); + renderer.clearScreen(); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + return; + } + + // Periodic FULL_REFRESH resets DC balance; every 32 pages. + if (++pagesSinceClean >= 32) { + pagesSinceClean = 0; + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + } + + renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight); + free(plane1); + free(plane2); + + LOG_DBG("XTR", "Rendered page %lu/%lu (2-bit factory)", currentPage + 1, xtc->getPageCount()); + return; } - // Allocate page buffer + // 1-bit XTG path + const size_t pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; uint8_t* pageBuffer = static_cast(malloc(pageBufferSize)); if (!pageBuffer) { LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize); @@ -170,10 +221,7 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(); return; } - - // Load page data - size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); - if (bytesRead == 0) { + if (xtc->loadPage(currentPage, pageBuffer, pageBufferSize) == 0) { LOG_ERR("XTR", "Failed to load page %lu", currentPage); free(pageBuffer); renderer.clearScreen(); @@ -181,151 +229,13 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(); return; } - - // Clear screen first - renderer.clearScreen(); - - // Copy page bitmap using GfxRenderer's drawPixel - // XTC/XTCH pages are pre-rendered with status bar included, so render full page - const uint16_t maxSrcY = pageHeight; - - if (bitDepth == 2) { - // XTH 2-bit mode: Two bit planes, column-major order - // - Columns scanned right to left (x = width-1 down to 0) - // - 8 vertical pixels per byte (MSB = topmost pixel in group) - // - First plane: Bit1, Second plane: Bit2 - // - Pixel value = (bit1 << 1) | bit2 - // - Grayscale: 0=White, 1=Dark Grey, 2=Light Grey, 3=Black - - const size_t planeSize = (static_cast(pageWidth) * pageHeight + 7) / 8; - const uint8_t* plane1 = pageBuffer; // Bit1 plane - const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane - const size_t colBytes = (pageHeight + 7) / 8; // Bytes per column (100 for 800 height) - - // Lambda to get pixel value at (x, y) - auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t { - const size_t colIndex = pageWidth - 1 - x; - const size_t byteInCol = y / 8; - const size_t bitInByte = 7 - (y % 8); - const size_t byteOffset = colIndex * colBytes + byteInCol; - const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1; - const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1; - return (bit1 << 1) | bit2; - }; - - // Optimized grayscale rendering without storeBwBuffer (saves 48KB peak memory) - // Flow: BW display → LSB/MSB passes → grayscale display → re-render BW for next frame - - // Count pixel distribution for debugging - uint32_t pixelCounts[4] = {0, 0, 0, 0}; - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - pixelCounts[getPixelValue(x, y)]++; - } - } - LOG_DBG("XTR", "Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu", pixelCounts[0], - pixelCounts[1], pixelCounts[2], pixelCounts[3]); - - // Pass 1: BW buffer - draw all non-white pixels as black - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - if (getPixelValue(x, y) >= 1) { - renderer.drawPixel(x, y, true); - } - } - } - - // Display BW with conditional refresh based on pagesUntilFullRefresh - if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); - } else { - renderer.displayBuffer(); - pagesUntilFullRefresh--; - } - - // Pass 2: LSB buffer - mark DARK gray only (XTH value 1) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched - renderer.clearScreen(0x00); - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - if (getPixelValue(x, y) == 1) { // Dark grey only - renderer.drawPixel(x, y, false); - } - } - } - renderer.copyGrayscaleLsbBuffers(); - - // Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched - renderer.clearScreen(0x00); - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - const uint8_t pv = getPixelValue(x, y); - if (pv == 1 || pv == 2) { // Dark grey or Light grey - renderer.drawPixel(x, y, false); - } - } - } - renderer.copyGrayscaleMsbBuffers(); - - // Display grayscale overlay - renderer.displayGrayBuffer(); - - // Pass 4: Re-render BW to framebuffer (restore for next frame, instead of restoreBwBuffer) - renderer.clearScreen(); - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - if (getPixelValue(x, y) >= 1) { - renderer.drawPixel(x, y, true); - } - } - } - - // Cleanup grayscale buffers with current frame buffer - renderer.cleanupGrayscaleWithFrameBuffer(); - - free(pageBuffer); - - LOG_DBG("XTR", "Rendered page %lu/%lu (2-bit grayscale)", currentPage + 1, xtc->getPageCount()); - return; - } else { - // 1-bit mode: 8 pixels per byte, MSB first - const size_t srcRowBytes = (pageWidth + 7) / 8; // 60 bytes for 480 width - - for (uint16_t srcY = 0; srcY < maxSrcY; srcY++) { - const size_t srcRowStart = srcY * srcRowBytes; - - for (uint16_t srcX = 0; srcX < pageWidth; srcX++) { - // Read source pixel (MSB first, bit 7 = leftmost pixel) - const size_t srcByte = srcRowStart + srcX / 8; - const size_t srcBit = 7 - (srcX % 8); - const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); // XTC: 0 = black, 1 = white - - if (isBlack) { - renderer.drawPixel(srcX, srcY, true); - } - } - } - } - // White pixels are already cleared by clearScreen() - + renderer.displayXtcBwPage(pageBuffer, pageWidth, pageHeight); free(pageBuffer); - - // XTC pages already have status bar pre-rendered, no need to add our own - - // Display with appropriate refresh - if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); - } else { - renderer.displayBuffer(); - pagesUntilFullRefresh--; - } - - LOG_DBG("XTR", "Rendered page %lu/%lu (%u-bit)", currentPage + 1, xtc->getPageCount(), bitDepth); + LOG_DBG("XTR", "Rendered page %lu/%lu (1-bit)", currentPage + 1, xtc->getPageCount()); } +void XtcReaderActivity::onScreenshotRequest() { renderPage(); } + void XtcReaderActivity::saveProgress() const { FsFile f; if (Storage.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 18effaade5..5830a2b8d9 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -15,7 +15,7 @@ class XtcReaderActivity final : public Activity { std::shared_ptr xtc; uint32_t currentPage = 0; - int pagesUntilFullRefresh = 0; + uint32_t pagesSinceClean = 0; void renderPage(); void saveProgress() const; @@ -28,5 +28,6 @@ class XtcReaderActivity final : public Activity { void onExit() override; void loop() override; void render(RenderLock&&) override; + void onScreenshotRequest() override; bool isReaderActivity() const override { return true; } }; diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index 37fa8fe19f..75b1722228 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -19,8 +20,6 @@ void BmpViewerActivity::onEnter() { const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); - Rect popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); - GUI.fillPopupProgress(renderer, popupRect, 20); // Initial 20% progress // 1. Open the file if (Storage.openFileForRead("BMP", filePath, file)) { Bitmap bitmap(file, true); @@ -48,20 +47,45 @@ void BmpViewerActivity::onEnter() { y = (pageHeight - bitmap.getHeight()) / 2; } - // 4. Prepare Rendering const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); - GUI.fillPopupProgress(renderer, popupRect, 50); - renderer.clearScreen(); - // Assuming drawBitmap defaults to 0,0 crop if omitted, or pass explicitly: drawBitmap(bitmap, x, y, pageWidth, - // pageHeight, 0, 0) - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0); - - // Draw UI hints on the base layer - GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - // Single pass for non-grayscale images - - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + if (bitmap.hasGreyscale()) { + struct BmpGrayCtx { + Bitmap* bitmap; + int x, y, maxWidth, maxHeight; + MappedInputManager::Labels labels; + }; + BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); + GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, + c->labels.btn4); + }, + &grayCtx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_LOADING_POPUP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); + } else { + renderer.clearScreen(); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + } } else { // Handle file parsing error @@ -69,7 +93,7 @@ void BmpViewerActivity::onEnter() { renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Invalid BMP File"); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); } file.close(); @@ -79,8 +103,76 @@ void BmpViewerActivity::onEnter() { renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Could not open file"); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + } +} + +void BmpViewerActivity::renderGrayscaleImage() { + FsFile file; + if (!Storage.openFileForRead("BMP", filePath, file)) return; + + Bitmap bitmap(file, true); + if (bitmap.parseHeaders() != BmpReaderError::Ok || !bitmap.hasGreyscale()) { + file.close(); + return; + } + + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + int x, y; + if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { + float ratio = static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); + const float screenRatio = static_cast(pageWidth) / static_cast(pageHeight); + if (ratio > screenRatio) { + x = 0; + y = std::round((static_cast(pageHeight) - static_cast(pageWidth) / ratio) / 2); + } else { + x = std::round((static_cast(pageWidth) - static_cast(pageHeight) * ratio) / 2); + y = 0; + } + } else { + x = (pageWidth - bitmap.getWidth()) / 2; + y = (pageHeight - bitmap.getHeight()) / 2; } + + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + struct BmpGrayCtx { + Bitmap* bitmap; + int x, y, maxWidth, maxHeight; + MappedInputManager::Labels labels; + }; + BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; + + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); + GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, + c->labels.btn4); + }, + &grayCtx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_LOADING_POPUP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + + file.close(); +} + +void BmpViewerActivity::onScreenshotRequest() { + renderGrayscaleImage(); + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); } void BmpViewerActivity::onExit() { diff --git a/src/activities/util/BmpViewerActivity.h b/src/activities/util/BmpViewerActivity.h index feac448e16..849a94fe0c 100644 --- a/src/activities/util/BmpViewerActivity.h +++ b/src/activities/util/BmpViewerActivity.h @@ -13,7 +13,9 @@ class BmpViewerActivity final : public Activity { void onEnter() override; void onExit() override; void loop() override; + void onScreenshotRequest() override; private: std::string filePath; + void renderGrayscaleImage(); }; \ No newline at end of file diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp new file mode 100644 index 0000000000..bd08e38ae9 --- /dev/null +++ b/src/activities/util/PxcViewerActivity.cpp @@ -0,0 +1,209 @@ +#include "PxcViewerActivity.h" + +#include +#include +#include +#include + +#include "Epub/converters/DirectPixelWriter.h" +#include "components/UITheme.h" +#include "fontIds.h" + +PxcViewerActivity::PxcViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path) + : Activity("PxcViewer", renderer, mappedInput), filePath(std::move(path)) {} + +void PxcViewerActivity::onEnter() { + Activity::onEnter(); + + const int screenWidth = renderer.getScreenWidth(); + const int screenHeight = renderer.getScreenHeight(); + FsFile file; + if (!Storage.openFileForRead("PXC", filePath, file)) { + renderer.clearScreen(); + renderer.drawCenteredText(UI_10_FONT_ID, screenHeight / 2, "Could not open file"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + return; + } + + uint16_t pxcWidth, pxcHeight; + if (file.read(&pxcWidth, 2) != 2 || file.read(&pxcHeight, 2) != 2) { + LOG_ERR("PXC", "Header read failed: %s", filePath.c_str()); + file.close(); + renderer.clearScreen(); + renderer.drawCenteredText(UI_10_FONT_ID, screenHeight / 2, "Invalid PXC file"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + return; + } + + if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + LOG_ERR("PXC", "PXC size %dx%d does not match screen %dx%d", pxcWidth, pxcHeight, screenWidth, screenHeight); + file.close(); + renderer.clearScreen(); + renderer.drawCenteredText(UI_10_FONT_ID, screenHeight / 2, "PXC size mismatch"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + return; + } + + const uint32_t dataOffset = file.position(); + + struct PxcCtx { + FsFile* file; + uint32_t dataOffset; + int width, height; + MappedInputManager::Labels labels; + }; + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; + + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->file->seek(c->dataOffset); + + const int bytesPerRow = (c->width + 3) / 4; + uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); + if (!rowBuf) { + LOG_ERR("PXC", "malloc failed for rowBuf (%d bytes, %dx%d)", bytesPerRow, c->width, c->height); + return; + } + + DirectPixelWriter pw; + pw.init(r); + + for (int row = 0; row < c->height; row++) { + if (c->file->read(rowBuf, bytesPerRow) != bytesPerRow) break; + pw.beginRow(row); + for (int col = 0; col < c->width; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + pw.writePixel(pv); + } + } + free(rowBuf); + + GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, + c->labels.btn4); + }, + &ctx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_LOADING_POPUP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + + file.close(); + + // Sync BW framebuffer state after factory-gray render so onExit's HALF_REFRESH + // does a correct differential (controller BW state = white, not stale gray planes). + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); +} + +void PxcViewerActivity::renderGrayscaleImage() { + FsFile file; + if (!Storage.openFileForRead("PXC", filePath, file)) return; + + uint16_t pxcWidth, pxcHeight; + if (file.read(&pxcWidth, 2) != 2 || file.read(&pxcHeight, 2) != 2) { + file.close(); + return; + } + + const int screenWidth = renderer.getScreenWidth(); + const int screenHeight = renderer.getScreenHeight(); + if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + file.close(); + return; + } + + const uint32_t dataOffset = file.position(); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + struct PxcCtx { + FsFile* file; + uint32_t dataOffset; + int width, height; + MappedInputManager::Labels labels; + }; + PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; + + renderer.renderGrayscaleSinglePass( + gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->file->seek(c->dataOffset); + + const int bytesPerRow = (c->width + 3) / 4; + uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); + if (!rowBuf) { + LOG_ERR("PXC", "malloc failed for rowBuf (%d bytes, %dx%d)", bytesPerRow, c->width, c->height); + return; + } + + DirectPixelWriter pw; + pw.init(r); + + for (int row = 0; row < c->height; row++) { + if (c->file->read(rowBuf, bytesPerRow) != bytesPerRow) break; + pw.beginRow(row); + for (int col = 0; col < c->width; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + pw.writePixel(pv); + } + } + free(rowBuf); + + GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, + c->labels.btn4); + }, + &ctx, + [](const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_LOADING_POPUP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); + }, + nullptr); + + file.close(); +} + +void PxcViewerActivity::onScreenshotRequest() { + renderGrayscaleImage(); + renderer.clearScreen(); + renderer.cleanupGrayscaleWithFrameBuffer(); +} + +void PxcViewerActivity::onExit() { + Activity::onExit(); + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); +} + +void PxcViewerActivity::loop() { + Activity::loop(); + + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + activityManager.goToFileBrowser(filePath); + return; + } +} diff --git a/src/activities/util/PxcViewerActivity.h b/src/activities/util/PxcViewerActivity.h new file mode 100644 index 0000000000..ffa0f5c18d --- /dev/null +++ b/src/activities/util/PxcViewerActivity.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "../Activity.h" +#include "MappedInputManager.h" + +class PxcViewerActivity final : public Activity { + public: + PxcViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string filePath); + + void onEnter() override; + void onExit() override; + void loop() override; + void onScreenshotRequest() override; + + private: + std::string filePath; + void renderGrayscaleImage(); +}; diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 425981a217..36c1c8cd49 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -84,7 +84,7 @@ UIIcon UITheme::getFileIcon(const std::string& filename) { if (FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename)) { return Text; } - if (FsHelpers::hasBmpExtension(filename)) { + if (FsHelpers::hasBmpExtension(filename) || FsHelpers::hasPxcExtension(filename)) { return Image; } return File; From 53e300c80a5d96b6b63cfe56bf9dbe8691c3c510 Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 20 Apr 2026 00:15:07 +0200 Subject: [PATCH 03/93] feat: grayscale screenshot capture for factory LUT renders Add ScreenshotUtil to detect factory LUT display state and re-render the current page via renderGrayscale for accurate screenshot output. X3 falls back to Differential mode for all grayscale paths. --- src/main.cpp | 13 ++- src/util/ScreenshotUtil.cpp | 173 ++++++++++++++++++++++++++++++++++++ src/util/ScreenshotUtil.h | 11 +++ 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index a3929d069c..c7efb24024 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -357,7 +357,18 @@ void loop() { screenshotButtonsReleased = false; { RenderLock lock; - ScreenshotUtil::takeScreenshot(renderer); + if (renderer.getDisplayState() == GfxRenderer::DisplayState::FactoryLut) { + // Display shows a grayscale image held only as physical particle positions. + // frameBuffer has been reset to white — do NOT use the BW screenshot path. + // Only arm the hook when an activity exists to call onScreenshotRequest(). + Activity* activity = activityManager.getCurrentActivity(); + if (activity) { + ScreenshotUtil::prepareFactoryLutScreenshot(renderer); + activity->onScreenshotRequest(); + } + } else { + ScreenshotUtil::takeScreenshot(renderer); + } } } return; diff --git a/src/util/ScreenshotUtil.cpp b/src/util/ScreenshotUtil.cpp index a152488d73..b5ff30d491 100644 --- a/src/util/ScreenshotUtil.cpp +++ b/src/util/ScreenshotUtil.cpp @@ -6,10 +6,16 @@ #include #include +#include #include #include "Bitmap.h" // Required for BmpHeader struct definition +// Static storage for the pending grayscale filename. Lifetime: set by prepareFactoryLutScreenshot, +// consumed (read) inside grayscaleHookCallback when the hook fires. The buffer is never freed — +// it's a fixed-size static so it persists for the lifetime of the firmware. +static char s_pendingFilename[64]; + void ScreenshotUtil::takeScreenshot(GfxRenderer& renderer) { const uint8_t* fb = renderer.getFrameBuffer(); if (fb) { @@ -118,3 +124,170 @@ bool ScreenshotUtil::saveFramebufferAsBmp(const char* filename, const uint8_t* f return true; } + +// --------------------------------------------------------------------------- +// Grayscale (factory LUT) screenshot — hook-based two-plane capture +// --------------------------------------------------------------------------- + +void ScreenshotUtil::prepareFactoryLutScreenshot(GfxRenderer& renderer) { + snprintf(s_pendingFilename, sizeof(s_pendingFilename), "/screenshots/screenshot-%lu.bmp", millis()); + renderer.setScreenshotHook(grayscaleHookCallback, s_pendingFilename); +} + +void ScreenshotUtil::grayscaleHookCallback(const uint8_t* lsbPlane, const uint8_t* msbPlane, int physWidth, + int physHeight, void* ctx) { + const char* filename = static_cast(ctx); + if (saveGrayscaleBmp(filename, lsbPlane, msbPlane, physWidth, physHeight)) { + LOG_DBG("SCR", "Grayscale screenshot saved: %s", filename); + } else { + LOG_ERR("SCR", "Failed to save grayscale screenshot"); + } +} + +bool ScreenshotUtil::saveGrayscaleBmp(const char* filename, const uint8_t* lsbPlane, const uint8_t* msbPlane, + int physWidth, int physHeight) { + // Logical output after 90° CCW rotation (same orientation as BW screenshots): + // outWidth = physHeight (BMP columns = physical rows) + // outHeight = physWidth (BMP rows = physical columns) + const int outWidth = physHeight; + const int outHeight = physWidth; + + // Ensure /screenshots directory exists. + if (!Storage.exists("/screenshots")) { + if (!Storage.mkdir("/screenshots")) { + return false; + } + } + + FsFile file; + if (!Storage.openFileForWrite("SCR", filename, file)) { + LOG_ERR("SCR", "Failed to open grayscale screenshot file"); + return false; + } + + // --- BMP file header (14 bytes) + DIB header (40 bytes) --- + // 8-bit palette BMP: pixel data offset = 14 + 40 + 256*4 = 1078 bytes. + const uint32_t rowSizePadded = (static_cast(outWidth) + 3u) & ~3u; + const uint32_t imageSize = rowSizePadded * static_cast(outHeight); + const uint32_t fileSize = 1078u + imageSize; + +#pragma pack(push, 1) + struct Bmp8Header { + // File header + uint16_t bfType; + uint32_t bfSize; + uint16_t bfReserved1; + uint16_t bfReserved2; + uint32_t bfOffBits; + // DIB header (BITMAPINFOHEADER) + uint32_t biSize; + int32_t biWidth; + int32_t biHeight; + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + uint32_t biSizeImage; + int32_t biXPelsPerMeter; + int32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; + }; +#pragma pack(pop) + + Bmp8Header hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.bfType = 0x4D42u; + hdr.bfSize = fileSize; + hdr.bfReserved1 = 0u; + hdr.bfReserved2 = 0u; + hdr.bfOffBits = 1078u; + hdr.biSize = 40u; + hdr.biWidth = outWidth; + hdr.biHeight = outHeight; // positive = bottom-up row order + hdr.biPlanes = 1u; + hdr.biBitCount = 8u; + hdr.biCompression = 0u; // BI_RGB (uncompressed) + hdr.biSizeImage = imageSize; + hdr.biXPelsPerMeter = 2835; + hdr.biYPelsPerMeter = 2835; + hdr.biClrUsed = 256u; + hdr.biClrImportant = 0u; + + bool write_error = false; + if (file.write(reinterpret_cast(&hdr), sizeof(hdr)) != sizeof(hdr)) { + write_error = true; + } + + if (!write_error) { + // Palette: 256 entries × 4 bytes = 1024 bytes. + // GRAY2_LSB encoding: index = lsb | (msb << 1) + // 0 → White (0xFF) lsb=0, msb=0 + // 1 → LightGrey lsb=1, msb=0 + // 2 → DarkGrey lsb=0, msb=1 + // 3 → Black (0x00) lsb=1, msb=1 + static constexpr uint8_t kPalette[16] = { + 0xFF, 0xFF, 0xFF, 0x00, // index 0: White + 0xAA, 0xAA, 0xAA, 0x00, // index 1: LightGrey + 0x55, 0x55, 0x55, 0x00, // index 2: DarkGrey + 0x00, 0x00, 0x00, 0x00, // index 3: Black + }; + if (file.write(kPalette, sizeof(kPalette)) != sizeof(kPalette)) { + write_error = true; + } + if (!write_error) { + // Write 252 zero entries to complete the 256-entry palette table. + uint8_t zeros[32] = {}; + uint32_t remaining = 252u * 4u; + while (remaining > 0u && !write_error) { + const uint32_t chunk = (remaining > sizeof(zeros)) ? static_cast(sizeof(zeros)) : remaining; + if (file.write(zeros, chunk) != chunk) write_error = true; + remaining -= chunk; + } + } + } + + if (write_error) { + file.close(); + Storage.remove(filename); + return false; + } + + // --- Pixel data: one byte per pixel (palette index 0–3), bottom-to-top rows --- + // Row buffer: outWidth bytes padded to 4-byte boundary (> 256 bytes; heap-allocated). + uint8_t* rowBuf = static_cast(malloc(rowSizePadded)); + if (!rowBuf) { + LOG_ERR("SCR", "saveGrayscaleBmp: malloc failed for row buffer (%u bytes)", rowSizePadded); + file.close(); + Storage.remove(filename); + return false; + } + + // physWidth pixels per physical row; each byte holds 8 pixels (1-bit planes). + const int bytesPerPhysRow = physWidth / 8; + + for (int outY = 0; outY < outHeight && !write_error; outY++) { + memset(rowBuf, 0, rowSizePadded); + for (int outX = 0; outX < outWidth; outX++) { + // 90° CCW rotation (same transform as BW saveFramebufferAsBmp): + // BMP rows are bottom-to-top, so outY=0 is the bottom of the logical image. + const int srcX = physWidth - 1 - outY; + const int srcY = physHeight - 1 - outX; + const int byteIdx = srcY * bytesPerPhysRow + (srcX / 8); + const int bitPos = 7 - (srcX % 8); + const uint8_t lsb = (lsbPlane[byteIdx] >> bitPos) & 1u; + const uint8_t msb = (msbPlane[byteIdx] >> bitPos) & 1u; + rowBuf[outX] = lsb | static_cast(msb << 1); + } + if (file.write(rowBuf, rowSizePadded) != rowSizePadded) write_error = true; + } + + free(rowBuf); + file.close(); + + if (write_error) { + Storage.remove(filename); + return false; + } + + return true; +} diff --git a/src/util/ScreenshotUtil.h b/src/util/ScreenshotUtil.h index 96d459e616..f866a6ecb6 100644 --- a/src/util/ScreenshotUtil.h +++ b/src/util/ScreenshotUtil.h @@ -5,4 +5,15 @@ class ScreenshotUtil { public: static void takeScreenshot(GfxRenderer& renderer); static bool saveFramebufferAsBmp(const char* filename, const uint8_t* framebuffer, int width, int height); + + // Called when displayState == FactoryLut. Installs a one-shot hook on the renderer; + // the caller must then call currentActivity->onScreenshotRequest() to trigger the re-render + // that fires the hook and captures both grayscale planes. + static void prepareFactoryLutScreenshot(GfxRenderer& renderer); + + private: + static void grayscaleHookCallback(const uint8_t* lsbPlane, const uint8_t* msbPlane, int physWidth, int physHeight, + void* ctx); + static bool saveGrayscaleBmp(const char* filename, const uint8_t* lsbPlane, const uint8_t* msbPlane, int physWidth, + int physHeight); }; From 29e8d75a2724c4771223382b7fa9b4010827385a Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Thu, 23 Apr 2026 00:34:30 +0200 Subject: [PATCH 04/93] fix: remove contrast boost from grayscale image quantization --- lib/GfxRenderer/BitmapHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/GfxRenderer/BitmapHelpers.cpp b/lib/GfxRenderer/BitmapHelpers.cpp index 11f93a6e9f..9da450f057 100644 --- a/lib/GfxRenderer/BitmapHelpers.cpp +++ b/lib/GfxRenderer/BitmapHelpers.cpp @@ -12,7 +12,7 @@ bool g_differentialQuantize = false; constexpr bool USE_BRIGHTNESS = true; // true: apply brightness/gamma adjustments constexpr int BRIGHTNESS_BOOST = 0; // No boost — quality LUT already renders slightly lighter constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) -constexpr float CONTRAST_FACTOR = 1.2f; // Contrast boost for quality LUT (softer drive needs more contrast) +constexpr float CONTRAST_FACTOR = 1.0f; // No contrast adjustment constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering // Integer approximation of gamma correction (brightens midtones) From a405dd0116126a02a696dd56ba2ff33b84058141 Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Thu, 23 Apr 2026 00:42:13 +0200 Subject: [PATCH 05/93] fix: scope contrast boost to 1-bit thumbnails only, not 2-bit image quantization --- lib/GfxRenderer/BitmapHelpers.cpp | 2 +- lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/GfxRenderer/BitmapHelpers.cpp b/lib/GfxRenderer/BitmapHelpers.cpp index 9da450f057..f737fb92ae 100644 --- a/lib/GfxRenderer/BitmapHelpers.cpp +++ b/lib/GfxRenderer/BitmapHelpers.cpp @@ -12,7 +12,7 @@ bool g_differentialQuantize = false; constexpr bool USE_BRIGHTNESS = true; // true: apply brightness/gamma adjustments constexpr int BRIGHTNESS_BOOST = 0; // No boost — quality LUT already renders slightly lighter constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) -constexpr float CONTRAST_FACTOR = 1.0f; // No contrast adjustment +constexpr float CONTRAST_FACTOR = 1.2f; // Contrast boost for 1-bit thumbnails (applied inside Atkinson1BitDitherer) constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering // Integer approximation of gamma correction (brightens midtones) diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index 0dd7872753..44bd504673 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -245,14 +245,13 @@ static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) if (ctx->atkinson1BitDitherer) ctx->atkinson1BitDitherer->nextRow(); } else { for (int x = 0; x < ctx->outWidth; x++) { - const uint8_t gray = adjustPixel(srcRow[x]); uint8_t twoBit; if (ctx->atkinsonDitherer) { - twoBit = ctx->atkinsonDitherer->processPixel(gray, x); + twoBit = ctx->atkinsonDitherer->processPixel(srcRow[x], x); } else if (ctx->fsDitherer) { - twoBit = ctx->fsDitherer->processPixel(gray, x); + twoBit = ctx->fsDitherer->processPixel(srcRow[x], x); } else { - twoBit = quantize(gray, x, outY); + twoBit = quantize(srcRow[x], x, outY); } ctx->bmpRow[(x * 2) / 8] |= (twoBit << (6 - ((x * 2) % 8))); } @@ -284,7 +283,7 @@ static void flushScaledRow(BmpConvertCtx* ctx) { if (ctx->atkinson1BitDitherer) ctx->atkinson1BitDitherer->nextRow(); } else { for (int x = 0; x < ctx->outWidth; x++) { - const uint8_t gray = adjustPixel((ctx->rowCount[x] > 0) ? (ctx->rowAccum[x] / ctx->rowCount[x]) : 0); + const uint8_t gray = (ctx->rowCount[x] > 0) ? (ctx->rowAccum[x] / ctx->rowCount[x]) : 0; uint8_t twoBit; if (ctx->atkinsonDitherer) { twoBit = ctx->atkinsonDitherer->processPixel(gray, x); From a65c4bd750f59b60816a698d55c2154179c1288c Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Thu, 23 Apr 2026 00:43:30 +0200 Subject: [PATCH 06/93] chore: clang-format --- lib/GfxRenderer/BitmapHelpers.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/GfxRenderer/BitmapHelpers.cpp b/lib/GfxRenderer/BitmapHelpers.cpp index f737fb92ae..96a9135c13 100644 --- a/lib/GfxRenderer/BitmapHelpers.cpp +++ b/lib/GfxRenderer/BitmapHelpers.cpp @@ -9,10 +9,10 @@ bool g_differentialQuantize = false; // Brightness/Contrast adjustments: -constexpr bool USE_BRIGHTNESS = true; // true: apply brightness/gamma adjustments -constexpr int BRIGHTNESS_BOOST = 0; // No boost — quality LUT already renders slightly lighter -constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) -constexpr float CONTRAST_FACTOR = 1.2f; // Contrast boost for 1-bit thumbnails (applied inside Atkinson1BitDitherer) +constexpr bool USE_BRIGHTNESS = true; // true: apply brightness/gamma adjustments +constexpr int BRIGHTNESS_BOOST = 0; // No boost — quality LUT already renders slightly lighter +constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) +constexpr float CONTRAST_FACTOR = 1.2f; // Contrast boost for 1-bit thumbnails (applied inside Atkinson1BitDitherer) constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering // Integer approximation of gamma correction (brightens midtones) From 1d135f4ac48b787f4f0ceed9af7eb997a22b256e Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Thu, 23 Apr 2026 22:52:37 +0200 Subject: [PATCH 07/93] chore: point open-x4-sdk to community-sdk PR#33 (factory LUT grayscale) --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index add90185ab..7dd61fafa8 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit add90185ab77bb1704429b26493aa7e80a283e90 +Subproject commit 7dd61fafa87a44898baa660f343399a6a68b8c3d From edea5a4a439794068bfbbe13ce3b65543fbb6e39 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 13:00:26 +0200 Subject: [PATCH 08/93] Use HALF_REFRESH instead of FULL_REFRESH for Blank and Logo sleep screen modes --- src/activities/boot_sleep/SleepActivity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 1a2fdc211b..35f51f9181 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -181,7 +181,7 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.invertScreen(); } - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } void SleepActivity::renderPxcSleepScreen(const std::string& path) const { @@ -463,7 +463,7 @@ void SleepActivity::renderCoverSleepScreen() const { void SleepActivity::renderBlankSleepScreen() const { renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } void SleepActivity::onScreenshotRequest() { From 548c84be7fbb52611847d357016db444c2ffa15a Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 15:28:49 +0200 Subject: [PATCH 09/93] Use direct XTCH plane rendering for 2-bit sleep screen covers --- src/activities/boot_sleep/SleepActivity.cpp | 35 ++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 35f51f9181..e7dc4262fb 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -397,13 +397,46 @@ void SleepActivity::renderCoverSleepScreen() const { // Check if the current book is XTC, TXT, or EPUB if (FsHelpers::hasXtcExtension(APP_STATE.openEpubPath)) { - // Handle XTC file Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint"); if (!lastXtc.load()) { LOG_ERR("SLP", "Failed to load last XTC"); return (this->*renderNoCoverSleepScreen)(); } + if (lastXtc.getBitDepth() == 2) { + const size_t planeSize = (static_cast(lastXtc.getPageWidth()) * lastXtc.getPageHeight() + 7) / 8; + uint8_t* plane1 = static_cast(malloc(planeSize)); + if (!plane1) { + LOG_ERR("SLP", "Failed to alloc plane1 for direct XTCH render (%lu bytes)", static_cast(planeSize)); + return (this->*renderNoCoverSleepScreen)(); + } + uint8_t* plane2 = static_cast(malloc(planeSize)); + if (!plane2) { + LOG_ERR("SLP", "Failed to alloc plane2 for direct XTCH render (%lu bytes)", static_cast(planeSize)); + free(plane1); + return (this->*renderNoCoverSleepScreen)(); + } + + if (lastXtc.loadPageMsb(0, plane1, planeSize) == 0) { + LOG_ERR("SLP", "Failed to load XTCH plane1 for sleep cover"); + free(plane1); + free(plane2); + return (this->*renderNoCoverSleepScreen)(); + } + if (lastXtc.loadPageLsb(0, plane2, planeSize) == 0) { + LOG_ERR("SLP", "Failed to load XTCH plane2 for sleep cover"); + free(plane1); + free(plane2); + return (this->*renderNoCoverSleepScreen)(); + } + + LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); + renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight()); + free(plane1); + free(plane2); + return; + } + if (!lastXtc.generateCoverBmp()) { LOG_ERR("SLP", "Failed to generate XTC cover bmp"); return (this->*renderNoCoverSleepScreen)(); From 228ac9440656c705e1a2eeb3057a172883e39cbf Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 15:32:36 +0200 Subject: [PATCH 10/93] Use direct XTC page rendering for 1-bit sleep screen covers --- src/activities/boot_sleep/SleepActivity.cpp | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index e7dc4262fb..125dfedec6 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -403,6 +403,7 @@ void SleepActivity::renderCoverSleepScreen() const { return (this->*renderNoCoverSleepScreen)(); } +<<<<<<< HEAD if (lastXtc.getBitDepth() == 2) { const size_t planeSize = (static_cast(lastXtc.getPageWidth()) * lastXtc.getPageHeight() + 7) / 8; uint8_t* plane1 = static_cast(malloc(planeSize)); @@ -437,6 +438,27 @@ void SleepActivity::renderCoverSleepScreen() const { return; } + if (lastXtc.getBitDepth() == 1) { + const size_t bufferSize = (static_cast(lastXtc.getPageWidth() + 7) / 8) * lastXtc.getPageHeight(); + uint8_t* pageBuffer = static_cast(malloc(bufferSize)); + if (!pageBuffer) { + LOG_ERR("SLP", "Failed to alloc page buffer for direct XTC render (%lu bytes)", static_cast(bufferSize)); + return (this->*renderNoCoverSleepScreen)(); + } + if (lastXtc.loadPage(0, pageBuffer, bufferSize) == 0) { + LOG_ERR("SLP", "Failed to load XTC page for sleep cover"); + free(pageBuffer); + return (this->*renderNoCoverSleepScreen)(); + } + LOG_DBG("SLP", "Direct XTC page render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + renderer.displayXtcBwPage(pageBuffer, lastXtc.getPageWidth(), lastXtc.getPageHeight()); + free(pageBuffer); + return; + } + } + if (!lastXtc.generateCoverBmp()) { LOG_ERR("SLP", "Failed to generate XTC cover bmp"); return (this->*renderNoCoverSleepScreen)(); From c791f093526c9a469f5bb8ce31758d1a679889d9 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 22:02:06 +0200 Subject: [PATCH 11/93] fix: add HALF_REFRESH pre-conditioning for XTCH 2-bit sleep cover from Home When entering sleep from Home (not Reader), the display particle state is in BW mode from FAST_REFRESH renders. Without pre-conditioning, the subsequent factory LUT render produces a dirty appearance. Added a clearScreen + HALF_REFRESH before displayXtchPlanes to reset particles to a known state. --- src/activities/boot_sleep/SleepActivity.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 125dfedec6..387e7fe78c 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -432,6 +432,10 @@ void SleepActivity::renderCoverSleepScreen() const { } LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); + if (!APP_STATE.lastSleepFromReader) { + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight()); free(plane1); free(plane2); From bb69393159ac16cd8c282f2158328f28b553f189 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 22:17:05 +0200 Subject: [PATCH 12/93] fix: restore 'Entering Sleep' overlay in sleep screen --- src/activities/boot_sleep/SleepActivity.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 387e7fe78c..f034ff33a1 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -19,6 +19,7 @@ void SleepActivity::onEnter() { Activity::onEnter(); + GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); if (APP_STATE.lastSleepFromReader) { ReaderUtils::applyOrientation(renderer, SETTINGS.orientation); @@ -408,12 +409,14 @@ void SleepActivity::renderCoverSleepScreen() const { const size_t planeSize = (static_cast(lastXtc.getPageWidth()) * lastXtc.getPageHeight() + 7) / 8; uint8_t* plane1 = static_cast(malloc(planeSize)); if (!plane1) { - LOG_ERR("SLP", "Failed to alloc plane1 for direct XTCH render (%lu bytes)", static_cast(planeSize)); + LOG_ERR("SLP", "Failed to alloc plane1 for direct XTCH render (%lu bytes)", + static_cast(planeSize)); return (this->*renderNoCoverSleepScreen)(); } uint8_t* plane2 = static_cast(malloc(planeSize)); if (!plane2) { - LOG_ERR("SLP", "Failed to alloc plane2 for direct XTCH render (%lu bytes)", static_cast(planeSize)); + LOG_ERR("SLP", "Failed to alloc plane2 for direct XTCH render (%lu bytes)", + static_cast(planeSize)); free(plane1); return (this->*renderNoCoverSleepScreen)(); } @@ -446,7 +449,8 @@ void SleepActivity::renderCoverSleepScreen() const { const size_t bufferSize = (static_cast(lastXtc.getPageWidth() + 7) / 8) * lastXtc.getPageHeight(); uint8_t* pageBuffer = static_cast(malloc(bufferSize)); if (!pageBuffer) { - LOG_ERR("SLP", "Failed to alloc page buffer for direct XTC render (%lu bytes)", static_cast(bufferSize)); + LOG_ERR("SLP", "Failed to alloc page buffer for direct XTC render (%lu bytes)", + static_cast(bufferSize)); return (this->*renderNoCoverSleepScreen)(); } if (lastXtc.loadPage(0, pageBuffer, bufferSize) == 0) { From ee67faa622235d3d619574e9d9e745266a44acf4 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 20:18:57 +0200 Subject: [PATCH 13/93] fix: Atkinson 1-bit dithering for XTCH 2-bit home cover thumbnails Replace the 2-bit BMP thumbnail generation for XTCH files with 1-bit BMP using Atkinson dithering (matching EPUB thumbnail quality). Key changes: - Load both XTCH bitplanes separately for area-averaged downsampling - Apply Atkinson 1-bit dithering (3 error rows, 6 neighbors, 6/8 error) - Apply 1.2x contrast boost for better tonal separation - Inverted bitplane luminance to match display polarity - Falls back to 2-bit BMP on memory allocation failure --- lib/Xtc/Xtc.cpp | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 2b49b78208..cc2ba87beb 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -426,6 +426,144 @@ bool Xtc::generateThumbBmp(int height) const { const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; const size_t colBytes = (pageInfo.height + 7) / 8; const uint32_t scaleInv_fp2 = static_cast(65536.0f / scale); + + { + uint8_t* plane1Buf = static_cast(malloc(planeSize)); + if (!plane1Buf) { + LOG_ERR("XTC", "Failed to alloc plane1 for thumb dither (%lu bytes)", static_cast(planeSize)); + return false; + } + if (const_cast(parser.get())->loadPageMsb(0, plane1Buf, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane1 for thumb"); + free(plane1Buf); + return false; + } + + uint8_t* plane2Buf = static_cast(malloc(planeSize)); + if (!plane2Buf) { + LOG_ERR("XTC", "Failed to alloc plane2 for thumb dither (%lu bytes), falling back to 2-bit BMP", + static_cast(planeSize)); + free(plane1Buf); + goto fallback_2bit_thumb; + } + if (const_cast(parser.get())->loadPageLsb(0, plane2Buf, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane2 for thumb, falling back to 2-bit BMP"); + free(plane1Buf); + free(plane2Buf); + goto fallback_2bit_thumb; + } + + int16_t* errRow0 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + int16_t* errRow1 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + int16_t* errRow2 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + if (!errRow0 || !errRow1 || !errRow2) { + LOG_ERR("XTC", "Failed to alloc dither buffers, falling back to 2-bit BMP"); + free(plane1Buf); + free(plane2Buf); + free(errRow0); + free(errRow1); + free(errRow2); + goto fallback_2bit_thumb; + } + memset(errRow0, 0, (thumbWidth + 4) * sizeof(int16_t)); + memset(errRow1, 0, (thumbWidth + 4) * sizeof(int16_t)); + memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + + FsFile thumbBmp; + if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { + free(plane1Buf); + free(plane2Buf); + free(errRow0); + free(errRow1); + free(errRow2); + return false; + } + + BmpHeader bmpHeader; + createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); + thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); + + const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; + uint8_t* rowBuf = static_cast(malloc(rowSize)); + if (!rowBuf) { + free(plane1Buf); + free(plane2Buf); + free(errRow0); + free(errRow1); + free(errRow2); + thumbBmp.close(); + return false; + } + + for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { + memset(rowBuf, 0xFF, rowSize); + uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; + uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; + if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + if (srcYE <= srcYS) srcYE = srcYS + 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; + uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; + if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + if (srcXE <= srcXS) srcXE = srcXS + 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + int lumSum = 0, total = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + const uint8_t b1 = (plane1Buf[bo] >> (7 - (sy % 8))) & 1; + const uint8_t b2 = (plane2Buf[bo] >> (7 - (sy % 8))) & 1; + lumSum += (1 - b1) * 85 + (1 - b2) * 170; + total++; + } + } + const int avgLum = (total > 0) ? (lumSum * 255 / total) / 255 : 255; + int adjusted = avgLum; + adjusted = ((adjusted - 128) * 120) / 100 + 128; + if (adjusted < 0) adjusted = 0; + if (adjusted > 255) adjusted = 255; + adjusted += errRow0[dstX + 2]; + if (adjusted < 0) adjusted = 0; + if (adjusted > 255) adjusted = 255; + const bool dark = adjusted < 128; + const int quantizedValue = dark ? 0 : 255; + const int error = (adjusted - quantizedValue) >> 3; + errRow0[dstX + 3] += error; + errRow0[dstX + 4] += error; + errRow1[dstX + 1] += error; + errRow1[dstX + 2] += error; + errRow1[dstX + 3] += error; + errRow2[dstX + 2] += error; + if (dark) { + const size_t bi = dstX / 8; + if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); + } + } + thumbBmp.write(rowBuf, rowSize); + int16_t* tmp = errRow0; + errRow0 = errRow1; + errRow1 = errRow2; + errRow2 = tmp; + memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + } + + free(rowBuf); + free(plane1Buf); + free(plane2Buf); + free(errRow0); + free(errRow1); + free(errRow2); + thumbBmp.close(); + LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, + getThumbBmpPath(height).c_str()); + return true; + } + + fallback_2bit_thumb: const size_t plane1BitsSize = (static_cast(thumbWidth) * thumbHeight + 7) / 8; uint8_t* plane1Bits = static_cast(malloc(plane1BitsSize)); if (!plane1Bits) { From 2bab76cb0dee7cc616b0981fc5a8eada78901938 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 20:33:39 +0200 Subject: [PATCH 14/93] fix: Atkinson 1-bit dithering for XTC 1-bit home cover thumbnails Replace hash-based noise dithering with Atkinson 1-bit dithering to match EPUB thumbnail quality. Key changes: - Atkinson 1-bit dithering (3 error rows, 6 neighbors, 6/8 error) - 1.2x contrast boost for better edge definition - Same BMP convention as XTCH path (memset 0xFF + clear bit for dark) --- lib/Xtc/Xtc.cpp | 58 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index cc2ba87beb..4f6261ea4c 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -739,6 +739,21 @@ bool Xtc::generateThumbBmp(int height) const { return false; } + int16_t* errRow0 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + int16_t* errRow1 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + int16_t* errRow2 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); + if (!errRow0 || !errRow1 || !errRow2) { + free(pageBuffer); + free(rowBuffer); + free(errRow0); + free(errRow1); + free(errRow2); + return false; + } + memset(errRow0, 0, (thumbWidth + 4) * sizeof(int16_t)); + memset(errRow1, 0, (thumbWidth + 4) * sizeof(int16_t)); + memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + const uint32_t scaleInv_fp = static_cast(65536.0f / scale); const size_t srcRowBytes = (pageInfo.width + 7) / 8; @@ -770,27 +785,42 @@ bool Xtc::generateThumbBmp(int height) const { } } - uint8_t avgGray = (totalCount > 0) ? static_cast(graySum / totalCount) : 255; - uint32_t hash = static_cast(dstX) * 374761393u + static_cast(dstY) * 668265263u; - hash = (hash ^ (hash >> 13)) * 1274126177u; - const int threshold = static_cast(hash >> 24); - const int adjustedThreshold = 128 + ((threshold - 128) / 2); - const uint8_t oneBit = (avgGray >= adjustedThreshold) ? 1 : 0; - const size_t byteIndex = dstX / 8; - const size_t bitOffset = 7 - (dstX % 8); - if (byteIndex < rowSize) { - if (oneBit) - rowBuffer[byteIndex] |= (1 << bitOffset); - else - rowBuffer[byteIndex] &= ~(1 << bitOffset); + int adjusted = (totalCount > 0) ? static_cast(graySum * 255 / totalCount) / 255 : 255; + adjusted = ((adjusted - 128) * 120) / 100 + 128; + if (adjusted < 0) adjusted = 0; + if (adjusted > 255) adjusted = 255; + adjusted += errRow0[dstX + 2]; + if (adjusted < 0) adjusted = 0; + if (adjusted > 255) adjusted = 255; + const bool dark = adjusted < 128; + const int quantizedValue = dark ? 0 : 255; + const int error = (adjusted - quantizedValue) >> 3; + errRow0[dstX + 3] += error; + errRow0[dstX + 4] += error; + errRow1[dstX + 1] += error; + errRow1[dstX + 2] += error; + errRow1[dstX + 3] += error; + errRow2[dstX + 2] += error; + if (dark) { + const size_t bi = dstX / 8; + if (bi < rowSize) rowBuffer[bi] &= ~(1 << (7 - (dstX % 8))); } } thumbBmp.write(rowBuffer, rowSize); + int16_t* tmp = errRow0; + errRow0 = errRow1; + errRow1 = errRow2; + errRow2 = tmp; + memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); } free(rowBuffer); free(pageBuffer); - LOG_DBG("XTC", "Generated 1-bit thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); + free(errRow0); + free(errRow1); + free(errRow2); + LOG_DBG("XTC", "Generated 1-bit thumb BMP with Atkinson dithering (%dx%d): %s", thumbWidth, thumbHeight, + getThumbBmpPath(height).c_str()); return true; } From 565c77f1d99c3695dda7576628d105e8701b1ab7 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 20:55:35 +0200 Subject: [PATCH 15/93] refactor: use Atkinson1BitDitherer and computeSrcRange helper Eliminate ~80 lines of duplicated code: - Replace inline Atkinson dithering with Atkinson1BitDitherer class (same class used by EPUB thumbnail generation) - Extract computeSrcRange() helper for downscale coordinate calc (was duplicated 4 times across XTCH/XTC paths) --- lib/Xtc/Xtc.cpp | 137 ++++++++++-------------------------------------- 1 file changed, 27 insertions(+), 110 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 4f6261ea4c..5593b25fda 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -11,6 +11,16 @@ #include #include +static inline void computeSrcRange(uint32_t dstCoord, uint32_t scaleInv, uint32_t maxSrc, + uint32_t& srcStart, uint32_t& srcEnd) { + srcStart = (static_cast(dstCoord) * scaleInv) >> 16; + srcEnd = (static_cast(dstCoord + 1) * scaleInv) >> 16; + if (srcStart >= maxSrc) srcStart = maxSrc - 1; + if (srcEnd > maxSrc) srcEnd = maxSrc; + if (srcEnd <= srcStart) srcEnd = srcStart + 1; + if (srcEnd > maxSrc) srcEnd = maxSrc; +} + bool Xtc::load() { LOG_DBG("XTC", "Loading XTC: %s", filepath.c_str()); @@ -453,29 +463,12 @@ bool Xtc::generateThumbBmp(int height) const { goto fallback_2bit_thumb; } - int16_t* errRow0 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - int16_t* errRow1 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - int16_t* errRow2 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - if (!errRow0 || !errRow1 || !errRow2) { - LOG_ERR("XTC", "Failed to alloc dither buffers, falling back to 2-bit BMP"); - free(plane1Buf); - free(plane2Buf); - free(errRow0); - free(errRow1); - free(errRow2); - goto fallback_2bit_thumb; - } - memset(errRow0, 0, (thumbWidth + 4) * sizeof(int16_t)); - memset(errRow1, 0, (thumbWidth + 4) * sizeof(int16_t)); - memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + Atkinson1BitDitherer ditherer(thumbWidth); FsFile thumbBmp; if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { free(plane1Buf); free(plane2Buf); - free(errRow0); - free(errRow1); - free(errRow2); return false; } @@ -488,28 +481,17 @@ bool Xtc::generateThumbBmp(int height) const { if (!rowBuf) { free(plane1Buf); free(plane2Buf); - free(errRow0); - free(errRow1); - free(errRow2); thumbBmp.close(); return false; } for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { memset(rowBuf, 0xFF, rowSize); - uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; - uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; - if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - if (srcYE <= srcYS) srcYE = srcYS + 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; + uint32_t srcYS, srcYE; + computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; - uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; - if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - if (srcXE <= srcXS) srcXE = srcXS + 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t srcXS, srcXE; + computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); int lumSum = 0, total = 0; for (uint32_t sy = srcYS; sy < srcYE; sy++) for (uint32_t sx = srcXS; sx < srcXE; sx++) { @@ -522,41 +504,19 @@ bool Xtc::generateThumbBmp(int height) const { } } const int avgLum = (total > 0) ? (lumSum * 255 / total) / 255 : 255; - int adjusted = avgLum; - adjusted = ((adjusted - 128) * 120) / 100 + 128; - if (adjusted < 0) adjusted = 0; - if (adjusted > 255) adjusted = 255; - adjusted += errRow0[dstX + 2]; - if (adjusted < 0) adjusted = 0; - if (adjusted > 255) adjusted = 255; - const bool dark = adjusted < 128; - const int quantizedValue = dark ? 0 : 255; - const int error = (adjusted - quantizedValue) >> 3; - errRow0[dstX + 3] += error; - errRow0[dstX + 4] += error; - errRow1[dstX + 1] += error; - errRow1[dstX + 2] += error; - errRow1[dstX + 3] += error; - errRow2[dstX + 2] += error; - if (dark) { + const uint8_t bit = ditherer.processPixel(avgLum, dstX); + if (!bit) { const size_t bi = dstX / 8; if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); } } thumbBmp.write(rowBuf, rowSize); - int16_t* tmp = errRow0; - errRow0 = errRow1; - errRow1 = errRow2; - errRow2 = tmp; - memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + ditherer.nextRow(); } free(rowBuf); free(plane1Buf); free(plane2Buf); - free(errRow0); - free(errRow1); - free(errRow2); thumbBmp.close(); LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); @@ -739,40 +699,18 @@ bool Xtc::generateThumbBmp(int height) const { return false; } - int16_t* errRow0 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - int16_t* errRow1 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - int16_t* errRow2 = static_cast(malloc((thumbWidth + 4) * sizeof(int16_t))); - if (!errRow0 || !errRow1 || !errRow2) { - free(pageBuffer); - free(rowBuffer); - free(errRow0); - free(errRow1); - free(errRow2); - return false; - } - memset(errRow0, 0, (thumbWidth + 4) * sizeof(int16_t)); - memset(errRow1, 0, (thumbWidth + 4) * sizeof(int16_t)); - memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); - + Atkinson1BitDitherer ditherer(thumbWidth); const uint32_t scaleInv_fp = static_cast(65536.0f / scale); const size_t srcRowBytes = (pageInfo.width + 7) / 8; for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { memset(rowBuffer, 0xFF, rowSize); - uint32_t srcYStart = (static_cast(dstY) * scaleInv_fp) >> 16; - uint32_t srcYEnd = (static_cast(dstY + 1) * scaleInv_fp) >> 16; - if (srcYStart >= pageInfo.height) srcYStart = pageInfo.height - 1; - if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height; - if (srcYEnd <= srcYStart) srcYEnd = srcYStart + 1; - if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height; + uint32_t srcYStart, srcYEnd; + computeSrcRange(dstY, scaleInv_fp, pageInfo.height, srcYStart, srcYEnd); for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXStart = (static_cast(dstX) * scaleInv_fp) >> 16; - uint32_t srcXEnd = (static_cast(dstX + 1) * scaleInv_fp) >> 16; - if (srcXStart >= pageInfo.width) srcXStart = pageInfo.width - 1; - if (srcXEnd > pageInfo.width) srcXEnd = pageInfo.width; - if (srcXEnd <= srcXStart) srcXEnd = srcXStart + 1; - if (srcXEnd > pageInfo.width) srcXEnd = pageInfo.width; + uint32_t srcXStart, srcXEnd; + computeSrcRange(dstX, scaleInv_fp, pageInfo.width, srcXStart, srcXEnd); uint32_t graySum = 0, totalCount = 0; for (uint32_t srcY = srcYStart; srcY < srcYEnd && srcY < pageInfo.height; srcY++) { @@ -785,40 +723,19 @@ bool Xtc::generateThumbBmp(int height) const { } } - int adjusted = (totalCount > 0) ? static_cast(graySum * 255 / totalCount) / 255 : 255; - adjusted = ((adjusted - 128) * 120) / 100 + 128; - if (adjusted < 0) adjusted = 0; - if (adjusted > 255) adjusted = 255; - adjusted += errRow0[dstX + 2]; - if (adjusted < 0) adjusted = 0; - if (adjusted > 255) adjusted = 255; - const bool dark = adjusted < 128; - const int quantizedValue = dark ? 0 : 255; - const int error = (adjusted - quantizedValue) >> 3; - errRow0[dstX + 3] += error; - errRow0[dstX + 4] += error; - errRow1[dstX + 1] += error; - errRow1[dstX + 2] += error; - errRow1[dstX + 3] += error; - errRow2[dstX + 2] += error; - if (dark) { + const int avgGray = (totalCount > 0) ? static_cast(graySum * 255 / totalCount) / 255 : 255; + const uint8_t bit = ditherer.processPixel(avgGray, dstX); + if (!bit) { const size_t bi = dstX / 8; if (bi < rowSize) rowBuffer[bi] &= ~(1 << (7 - (dstX % 8))); } } thumbBmp.write(rowBuffer, rowSize); - int16_t* tmp = errRow0; - errRow0 = errRow1; - errRow1 = errRow2; - errRow2 = tmp; - memset(errRow2, 0, (thumbWidth + 4) * sizeof(int16_t)); + ditherer.nextRow(); } free(rowBuffer); free(pageBuffer); - free(errRow0); - free(errRow1); - free(errRow2); LOG_DBG("XTC", "Generated 1-bit thumb BMP with Atkinson dithering (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); return true; From 50bb37ea6b2653fb3357d266cb4504d60d740ae1 Mon Sep 17 00:00:00 2001 From: pablohc Date: Thu, 23 Apr 2026 22:21:58 +0200 Subject: [PATCH 16/93] style: apply clang-format to Xtc.cpp --- lib/Xtc/Xtc.cpp | 148 ++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 5593b25fda..e1cda14cda 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -11,8 +11,8 @@ #include #include -static inline void computeSrcRange(uint32_t dstCoord, uint32_t scaleInv, uint32_t maxSrc, - uint32_t& srcStart, uint32_t& srcEnd) { +static inline void computeSrcRange(uint32_t dstCoord, uint32_t scaleInv, uint32_t maxSrc, uint32_t& srcStart, + uint32_t& srcEnd) { srcStart = (static_cast(dstCoord) * scaleInv) >> 16; srcEnd = (static_cast(dstCoord + 1) * scaleInv) >> 16; if (srcStart >= maxSrc) srcStart = maxSrc - 1; @@ -438,89 +438,89 @@ bool Xtc::generateThumbBmp(int height) const { const uint32_t scaleInv_fp2 = static_cast(65536.0f / scale); { - uint8_t* plane1Buf = static_cast(malloc(planeSize)); - if (!plane1Buf) { - LOG_ERR("XTC", "Failed to alloc plane1 for thumb dither (%lu bytes)", static_cast(planeSize)); - return false; - } - if (const_cast(parser.get())->loadPageMsb(0, plane1Buf, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane1 for thumb"); - free(plane1Buf); - return false; - } + uint8_t* plane1Buf = static_cast(malloc(planeSize)); + if (!plane1Buf) { + LOG_ERR("XTC", "Failed to alloc plane1 for thumb dither (%lu bytes)", static_cast(planeSize)); + return false; + } + if (const_cast(parser.get())->loadPageMsb(0, plane1Buf, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane1 for thumb"); + free(plane1Buf); + return false; + } - uint8_t* plane2Buf = static_cast(malloc(planeSize)); - if (!plane2Buf) { - LOG_ERR("XTC", "Failed to alloc plane2 for thumb dither (%lu bytes), falling back to 2-bit BMP", - static_cast(planeSize)); - free(plane1Buf); - goto fallback_2bit_thumb; - } - if (const_cast(parser.get())->loadPageLsb(0, plane2Buf, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane2 for thumb, falling back to 2-bit BMP"); - free(plane1Buf); - free(plane2Buf); - goto fallback_2bit_thumb; - } + uint8_t* plane2Buf = static_cast(malloc(planeSize)); + if (!plane2Buf) { + LOG_ERR("XTC", "Failed to alloc plane2 for thumb dither (%lu bytes), falling back to 2-bit BMP", + static_cast(planeSize)); + free(plane1Buf); + goto fallback_2bit_thumb; + } + if (const_cast(parser.get())->loadPageLsb(0, plane2Buf, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane2 for thumb, falling back to 2-bit BMP"); + free(plane1Buf); + free(plane2Buf); + goto fallback_2bit_thumb; + } - Atkinson1BitDitherer ditherer(thumbWidth); + Atkinson1BitDitherer ditherer(thumbWidth); - FsFile thumbBmp; - if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { - free(plane1Buf); - free(plane2Buf); - return false; - } + FsFile thumbBmp; + if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { + free(plane1Buf); + free(plane2Buf); + return false; + } - BmpHeader bmpHeader; - createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); - thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); + BmpHeader bmpHeader; + createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); + thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); - const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; - uint8_t* rowBuf = static_cast(malloc(rowSize)); - if (!rowBuf) { - free(plane1Buf); - free(plane2Buf); - thumbBmp.close(); - return false; - } + const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; + uint8_t* rowBuf = static_cast(malloc(rowSize)); + if (!rowBuf) { + free(plane1Buf); + free(plane2Buf); + thumbBmp.close(); + return false; + } - for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - memset(rowBuf, 0xFF, rowSize); - uint32_t srcYS, srcYE; - computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); - for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXS, srcXE; - computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); - int lumSum = 0, total = 0; - for (uint32_t sy = srcYS; sy < srcYE; sy++) - for (uint32_t sx = srcXS; sx < srcXE; sx++) { - const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; - if (bo < planeSize) { - const uint8_t b1 = (plane1Buf[bo] >> (7 - (sy % 8))) & 1; - const uint8_t b2 = (plane2Buf[bo] >> (7 - (sy % 8))) & 1; - lumSum += (1 - b1) * 85 + (1 - b2) * 170; - total++; + for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { + memset(rowBuf, 0xFF, rowSize); + uint32_t srcYS, srcYE; + computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcXS, srcXE; + computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); + int lumSum = 0, total = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + const uint8_t b1 = (plane1Buf[bo] >> (7 - (sy % 8))) & 1; + const uint8_t b2 = (plane2Buf[bo] >> (7 - (sy % 8))) & 1; + lumSum += (1 - b1) * 85 + (1 - b2) * 170; + total++; + } } + const int avgLum = (total > 0) ? (lumSum * 255 / total) / 255 : 255; + const uint8_t bit = ditherer.processPixel(avgLum, dstX); + if (!bit) { + const size_t bi = dstX / 8; + if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); } - const int avgLum = (total > 0) ? (lumSum * 255 / total) / 255 : 255; - const uint8_t bit = ditherer.processPixel(avgLum, dstX); - if (!bit) { - const size_t bi = dstX / 8; - if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); } + thumbBmp.write(rowBuf, rowSize); + ditherer.nextRow(); } - thumbBmp.write(rowBuf, rowSize); - ditherer.nextRow(); - } - free(rowBuf); - free(plane1Buf); - free(plane2Buf); - thumbBmp.close(); - LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, - getThumbBmpPath(height).c_str()); - return true; + free(rowBuf); + free(plane1Buf); + free(plane2Buf); + thumbBmp.close(); + LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, + getThumbBmpPath(height).c_str()); + return true; } fallback_2bit_thumb: From 9f3bb618322ade741924cedf09f5f7dd3e753574 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 00:57:59 +0200 Subject: [PATCH 17/93] fix: resolve leftover conflict markers in SleepActivity.cpp --- src/activities/boot_sleep/SleepActivity.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index f034ff33a1..a441a7a2df 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -404,7 +404,6 @@ void SleepActivity::renderCoverSleepScreen() const { return (this->*renderNoCoverSleepScreen)(); } -<<<<<<< HEAD if (lastXtc.getBitDepth() == 2) { const size_t planeSize = (static_cast(lastXtc.getPageWidth()) * lastXtc.getPageHeight() + 7) / 8; uint8_t* plane1 = static_cast(malloc(planeSize)); @@ -465,7 +464,6 @@ void SleepActivity::renderCoverSleepScreen() const { free(pageBuffer); return; } - } if (!lastXtc.generateCoverBmp()) { LOG_ERR("SLP", "Failed to generate XTC cover bmp"); From b8fa515bcc97eae4c87ba48be2d2b25537695538 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 11:40:59 +0200 Subject: [PATCH 18/93] fix: unify XTCH 2-bit thumb generation to sequential dithered 1-bit BMP After reader sessions, heap fragmentation reduces MaxAlloc to ~65KB, preventing the primary path from allocating both planes simultaneously (96KB). The fallback produced a raw 2-bit BMP with no dithering, resulting in poor visual quality on the Home Screen. Unify to a single sequential path: load plane1 (48KB) -> majority-vote -> free, load plane2 (48KB) -> combine -> Atkinson dithering -> 1-bit BMP. Peak memory ~52KB, always within MaxAlloc constraints. This matches the same approach used by XTC 1-bit thumbnails, ensuring consistent quality across all themes and memory states. --- lib/Xtc/Xtc.cpp | 175 ++++++++++-------------------------------------- 1 file changed, 37 insertions(+), 138 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index e1cda14cda..5c27cf28f7 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -430,113 +430,28 @@ bool Xtc::generateThumbBmp(int height) const { LOG_DBG("XTC", "Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)", pageInfo.width, pageInfo.height, thumbWidth, thumbHeight, scale); - // For 2-bit (XTCH): two-pass plane loading → 2-bit BMP output with 4-level grayscale palette. - // Full page (96KB) exceeds MaxAlloc; load each plane separately (~48KB). + // For 2-bit (XTCH): sequential plane loading → Atkinson dithered 1-bit BMP. + // Loads each plane separately (~48KB) to stay within memory constraints after reader sessions. if (bitDepth == 2) { const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; const size_t colBytes = (pageInfo.height + 7) / 8; const uint32_t scaleInv_fp2 = static_cast(65536.0f / scale); - { - uint8_t* plane1Buf = static_cast(malloc(planeSize)); - if (!plane1Buf) { - LOG_ERR("XTC", "Failed to alloc plane1 for thumb dither (%lu bytes)", static_cast(planeSize)); - return false; - } - if (const_cast(parser.get())->loadPageMsb(0, plane1Buf, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane1 for thumb"); - free(plane1Buf); - return false; - } - - uint8_t* plane2Buf = static_cast(malloc(planeSize)); - if (!plane2Buf) { - LOG_ERR("XTC", "Failed to alloc plane2 for thumb dither (%lu bytes), falling back to 2-bit BMP", - static_cast(planeSize)); - free(plane1Buf); - goto fallback_2bit_thumb; - } - if (const_cast(parser.get())->loadPageLsb(0, plane2Buf, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane2 for thumb, falling back to 2-bit BMP"); - free(plane1Buf); - free(plane2Buf); - goto fallback_2bit_thumb; - } - - Atkinson1BitDitherer ditherer(thumbWidth); - - FsFile thumbBmp; - if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { - free(plane1Buf); - free(plane2Buf); - return false; - } - - BmpHeader bmpHeader; - createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); - thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); - - const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; - uint8_t* rowBuf = static_cast(malloc(rowSize)); - if (!rowBuf) { - free(plane1Buf); - free(plane2Buf); - thumbBmp.close(); - return false; - } - - for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - memset(rowBuf, 0xFF, rowSize); - uint32_t srcYS, srcYE; - computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); - for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXS, srcXE; - computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); - int lumSum = 0, total = 0; - for (uint32_t sy = srcYS; sy < srcYE; sy++) - for (uint32_t sx = srcXS; sx < srcXE; sx++) { - const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; - if (bo < planeSize) { - const uint8_t b1 = (plane1Buf[bo] >> (7 - (sy % 8))) & 1; - const uint8_t b2 = (plane2Buf[bo] >> (7 - (sy % 8))) & 1; - lumSum += (1 - b1) * 85 + (1 - b2) * 170; - total++; - } - } - const int avgLum = (total > 0) ? (lumSum * 255 / total) / 255 : 255; - const uint8_t bit = ditherer.processPixel(avgLum, dstX); - if (!bit) { - const size_t bi = dstX / 8; - if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); - } - } - thumbBmp.write(rowBuf, rowSize); - ditherer.nextRow(); - } - - free(rowBuf); - free(plane1Buf); - free(plane2Buf); - thumbBmp.close(); - LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, - getThumbBmpPath(height).c_str()); - return true; - } - - fallback_2bit_thumb: const size_t plane1BitsSize = (static_cast(thumbWidth) * thumbHeight + 7) / 8; uint8_t* plane1Bits = static_cast(malloc(plane1BitsSize)); if (!plane1Bits) { - LOG_ERR("XTC", "Failed to alloc plane1bits (%lu bytes)", plane1BitsSize); + LOG_ERR("XTC", "Failed to alloc plane1bits (%lu bytes)", static_cast(plane1BitsSize)); return false; } memset(plane1Bits, 0, plane1BitsSize); + uint8_t* planeBuffer = static_cast(malloc(planeSize)); if (!planeBuffer) { - LOG_ERR("XTC", "Failed to alloc plane buffer (%lu bytes)", planeSize); + LOG_ERR("XTC", "Failed to alloc plane buffer (%lu bytes)", static_cast(planeSize)); free(plane1Bits); return false; } + // Pass 1: plane1 (bit1/MSB) majority vote per output pixel if (const_cast(parser.get())->loadPageMsb(0, planeBuffer, planeSize) == 0) { LOG_ERR("XTC", "Failed to load plane1 for thumb"); @@ -573,60 +488,39 @@ bool Xtc::generateThumbBmp(int height) const { } } } - // Pass 2: plane2 (bit2/LSB) + combine → write 2-bit BMP + + // Pass 2: plane2 (bit2/LSB) + combine → Atkinson dithering → 1-bit BMP if (const_cast(parser.get())->loadPageLsb(0, planeBuffer, planeSize) == 0) { LOG_ERR("XTC", "Failed to load plane2 for thumb"); free(planeBuffer); free(plane1Bits); return false; } - FsFile thumbBmp2; - if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp2)) { + + Atkinson1BitDitherer ditherer(thumbWidth); + + FsFile thumbBmp; + if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { free(planeBuffer); free(plane1Bits); return false; } - const uint32_t rowSize2 = ((static_cast(thumbWidth) * 2 + 31) / 32) * 4; - const uint32_t imageSize2 = rowSize2 * thumbHeight; - const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; - thumbBmp2.write('B'); - thumbBmp2.write('M'); - thumbBmp2.write(reinterpret_cast(&fileSize2), 4); - uint32_t rsv2 = 0; - thumbBmp2.write(reinterpret_cast(&rsv2), 4); - uint32_t doff2 = 14 + 40 + 16; - thumbBmp2.write(reinterpret_cast(&doff2), 4); - uint32_t dibSz2 = 40; - thumbBmp2.write(reinterpret_cast(&dibSz2), 4); - int32_t ww2 = thumbWidth; - thumbBmp2.write(reinterpret_cast(&ww2), 4); - int32_t hh2 = -static_cast(thumbHeight); - thumbBmp2.write(reinterpret_cast(&hh2), 4); - uint16_t pl2 = 1; - thumbBmp2.write(reinterpret_cast(&pl2), 2); - uint16_t bpp2 = 2; - thumbBmp2.write(reinterpret_cast(&bpp2), 2); - uint32_t cmp2 = 0, imgSz2 = imageSize2, ppm2 = 2835, cu2 = 4, ci2 = 4; - thumbBmp2.write(reinterpret_cast(&cmp2), 4); - thumbBmp2.write(reinterpret_cast(&imgSz2), 4); - thumbBmp2.write(reinterpret_cast(&ppm2), 4); - thumbBmp2.write(reinterpret_cast(&ppm2), 4); - thumbBmp2.write(reinterpret_cast(&cu2), 4); - thumbBmp2.write(reinterpret_cast(&ci2), 4); - // Palette: 0=white, 1=lightGrey(170), 2=darkGrey(85), 3=black — matches XTC pixel value - static constexpr uint8_t pal2[16] = {0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, - 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; - thumbBmp2.write(pal2, 16); - uint8_t* rowBuf2 = static_cast(malloc(rowSize2)); - if (!rowBuf2) { + + BmpHeader bmpHeader; + createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown); + thumbBmp.write(reinterpret_cast(&bmpHeader), sizeof(bmpHeader)); + + const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; + uint8_t* rowBuf = static_cast(malloc(rowSize)); + if (!rowBuf) { free(planeBuffer); free(plane1Bits); - thumbBmp2.close(); - Storage.remove(getThumbBmpPath(height).c_str()); + thumbBmp.close(); return false; } + for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - memset(rowBuf2, 0, rowSize2); + memset(rowBuf, 0xFF, rowSize); uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; @@ -652,18 +546,23 @@ bool Xtc::generateThumbBmp(int height) const { const size_t pi = static_cast(dstY) * thumbWidth + dstX; const uint8_t bit1 = (plane1Bits[pi / 8] >> (7 - (pi % 8))) & 1; const uint8_t bit2 = (total > 0 && darkCount * 2 >= total) ? 1 : 0; - const uint8_t twoBit = (bit1 << 1) | bit2; - const size_t bi2 = dstX / 4; - const int bs2 = 6 - static_cast(dstX % 4) * 2; - if (bi2 < rowSize2) rowBuf2[bi2] |= static_cast(twoBit << bs2); + const uint8_t lum = (1 - bit1) * 85 + (1 - bit2) * 170; + const uint8_t bit = ditherer.processPixel(lum, dstX); + if (!bit) { + const size_t bi = dstX / 8; + if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); + } } - thumbBmp2.write(rowBuf2, rowSize2); + thumbBmp.write(rowBuf, rowSize); + ditherer.nextRow(); } - free(rowBuf2); + + free(rowBuf); free(planeBuffer); free(plane1Bits); - thumbBmp2.close(); - LOG_DBG("XTC", "Generated 2-bit thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); + thumbBmp.close(); + LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, + getThumbBmpPath(height).c_str()); return true; } From f9be6d91f94b68fd3b6d3d774ebc9cf299d43073 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 13:24:45 +0200 Subject: [PATCH 19/93] fix: use area-averaged strip processing for XTCH 2-bit thumb generation --- lib/Xtc/Xtc.cpp | 183 +++++++++++++++++++++++++----------------------- 1 file changed, 95 insertions(+), 88 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 5c27cf28f7..f17c3c8a5d 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -430,79 +430,35 @@ bool Xtc::generateThumbBmp(int height) const { LOG_DBG("XTC", "Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)", pageInfo.width, pageInfo.height, thumbWidth, thumbHeight, scale); - // For 2-bit (XTCH): sequential plane loading → Atkinson dithered 1-bit BMP. - // Loads each plane separately (~48KB) to stay within memory constraints after reader sessions. + // For 2-bit (XTCH): strip-based sequential plane loading → area-averaged Atkinson dithered 1-bit BMP. + // Loads each plane separately (~48KB) in strips to stay within memory constraints after reader sessions. + // Uses area-averaging (same formula as the 1-bit path) for accurate luminance computation. if (bitDepth == 2) { const size_t planeSize = (static_cast(pageInfo.width) * pageInfo.height + 7) / 8; const size_t colBytes = (pageInfo.height + 7) / 8; const uint32_t scaleInv_fp2 = static_cast(65536.0f / scale); - const size_t plane1BitsSize = (static_cast(thumbWidth) * thumbHeight + 7) / 8; - uint8_t* plane1Bits = static_cast(malloc(plane1BitsSize)); - if (!plane1Bits) { - LOG_ERR("XTC", "Failed to alloc plane1bits (%lu bytes)", static_cast(plane1BitsSize)); - return false; - } - memset(plane1Bits, 0, plane1BitsSize); - uint8_t* planeBuffer = static_cast(malloc(planeSize)); if (!planeBuffer) { LOG_ERR("XTC", "Failed to alloc plane buffer (%lu bytes)", static_cast(planeSize)); - free(plane1Bits); return false; } - // Pass 1: plane1 (bit1/MSB) majority vote per output pixel - if (const_cast(parser.get())->loadPageMsb(0, planeBuffer, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane1 for thumb"); - free(planeBuffer); - free(plane1Bits); - return false; - } - for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; - uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; - if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - if (srcYE <= srcYS) srcYE = srcYS + 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; - uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; - if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - if (srcXE <= srcXS) srcXE = srcXS + 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - uint32_t darkCount = 0, total = 0; - for (uint32_t sy = srcYS; sy < srcYE; sy++) - for (uint32_t sx = srcXS; sx < srcXE; sx++) { - const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; - if (bo < planeSize) { - if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; - total++; - } - } - if (total > 0 && darkCount * 2 >= total) { - const size_t pi = static_cast(dstY) * thumbWidth + dstX; - plane1Bits[pi / 8] |= static_cast(1u << (7 - (pi % 8))); - } - } - } + const size_t stripBudget = (static_cast(thumbWidth) * thumbHeight + 7) / 8; + const int stripRows = std::min(static_cast(thumbHeight), static_cast(stripBudget / thumbWidth)); - // Pass 2: plane2 (bit2/LSB) + combine → Atkinson dithering → 1-bit BMP - if (const_cast(parser.get())->loadPageLsb(0, planeBuffer, planeSize) == 0) { - LOG_ERR("XTC", "Failed to load plane2 for thumb"); + uint8_t* darkCount1Buf = static_cast(malloc(static_cast(stripRows) * thumbWidth)); + if (!darkCount1Buf) { + LOG_ERR("XTC", "Failed to alloc darkCount1 buffer (%lu bytes)", + static_cast(static_cast(stripRows) * thumbWidth)); free(planeBuffer); - free(plane1Bits); return false; } - Atkinson1BitDitherer ditherer(thumbWidth); - FsFile thumbBmp; if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) { + free(darkCount1Buf); free(planeBuffer); - free(plane1Bits); return false; } @@ -513,53 +469,104 @@ bool Xtc::generateThumbBmp(int height) const { const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; uint8_t* rowBuf = static_cast(malloc(rowSize)); if (!rowBuf) { + free(darkCount1Buf); free(planeBuffer); - free(plane1Bits); thumbBmp.close(); return false; } - for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) { - memset(rowBuf, 0xFF, rowSize); - uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; - uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; - if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - if (srcYE <= srcYS) srcYE = srcYS + 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; - uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; - if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - if (srcXE <= srcXS) srcXE = srcXS + 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - uint32_t darkCount = 0, total = 0; - for (uint32_t sy = srcYS; sy < srcYE; sy++) - for (uint32_t sx = srcXS; sx < srcXE; sx++) { - const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; - if (bo < planeSize) { - if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; - total++; + Atkinson1BitDitherer ditherer(thumbWidth); + + for (int stripStart = 0; stripStart < static_cast(thumbHeight); stripStart += stripRows) { + const int curRows = std::min(stripRows, static_cast(thumbHeight) - stripStart); + + // Pass 1: plane1 (bit1/MSB) — count dark pixels per output pixel + if (const_cast(parser.get())->loadPageMsb(0, planeBuffer, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane1 for thumb"); + free(rowBuf); + free(darkCount1Buf); + free(planeBuffer); + thumbBmp.close(); + return false; + } + for (int dy = 0; dy < curRows; dy++) { + const uint16_t dstY = static_cast(stripStart + dy); + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; + uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; + if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + if (srcYE <= srcYS) srcYE = srcYS + 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; + uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; + if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + if (srcXE <= srcXS) srcXE = srcXS + 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t darkCount = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; + } + } + darkCount1Buf[static_cast(dy) * thumbWidth + dstX] = static_cast(darkCount); + } + } + + // Pass 2: plane2 (bit2/LSB) — combine with darkCount1 → area-average → dither → write BMP rows + if (const_cast(parser.get())->loadPageLsb(0, planeBuffer, planeSize) == 0) { + LOG_ERR("XTC", "Failed to load plane2 for thumb"); + free(rowBuf); + free(darkCount1Buf); + free(planeBuffer); + thumbBmp.close(); + return false; + } + for (int dy = 0; dy < curRows; dy++) { + memset(rowBuf, 0xFF, rowSize); + const uint16_t dstY = static_cast(stripStart + dy); + for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { + uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; + uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; + if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + if (srcYE <= srcYS) srcYE = srcYS + 1; + if (srcYE > pageInfo.height) srcYE = pageInfo.height; + uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; + uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; + if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + if (srcXE <= srcXS) srcXE = srcXS + 1; + if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t darkCount = 0, totalCount = 0; + for (uint32_t sy = srcYS; sy < srcYE; sy++) + for (uint32_t sx = srcXS; sx < srcXE; sx++) { + const size_t bo = (pageInfo.width - 1 - sx) * colBytes + sy / 8; + if (bo < planeSize) { + if ((planeBuffer[bo] >> (7 - (sy % 8))) & 1) darkCount++; + totalCount++; + } } + const uint32_t dark1 = darkCount1Buf[static_cast(dy) * thumbWidth + dstX]; + const int lumSum = static_cast((totalCount - dark1) * 85 + (totalCount - darkCount) * 170); + const int avgLum = (totalCount > 0) ? (lumSum * 255 / totalCount) / 255 : 255; + const uint8_t bit = ditherer.processPixel(avgLum, dstX); + if (!bit) { + const size_t bi = dstX / 8; + if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); } - const size_t pi = static_cast(dstY) * thumbWidth + dstX; - const uint8_t bit1 = (plane1Bits[pi / 8] >> (7 - (pi % 8))) & 1; - const uint8_t bit2 = (total > 0 && darkCount * 2 >= total) ? 1 : 0; - const uint8_t lum = (1 - bit1) * 85 + (1 - bit2) * 170; - const uint8_t bit = ditherer.processPixel(lum, dstX); - if (!bit) { - const size_t bi = dstX / 8; - if (bi < rowSize) rowBuf[bi] &= ~(1 << (7 - (dstX % 8))); } + thumbBmp.write(rowBuf, rowSize); + ditherer.nextRow(); } - thumbBmp.write(rowBuf, rowSize); - ditherer.nextRow(); } free(rowBuf); + free(darkCount1Buf); free(planeBuffer); - free(plane1Bits); thumbBmp.close(); LOG_DBG("XTC", "Generated 1-bit thumb BMP with dithering (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str()); From 56f5e90b9cc6ed813bf06ebc80e511fd6c9142a2 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 13:36:35 +0200 Subject: [PATCH 20/93] fix: validate I/O return values in 2-bit cover BMP generation --- lib/Xtc/Xtc.cpp | 77 +++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index f17c3c8a5d..36dc36d9d1 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -186,7 +186,13 @@ bool Xtc::generateCoverBmp() const { const uint8_t bit1 = (plane1[bo] >> (7 - (y % 8))) & 1; rowBuf[x / 4] |= static_cast((bit1 << 1) << (6 - (x % 4) * 2)); } - tempFile.write(rowBuf, rowSize2); + if (tempFile.write(rowBuf, rowSize2) != rowSize2) { + LOG_ERR("XTC", "Failed to write temp cover row %u", y); + tempFile.close(); + free(plane1); + Storage.remove(tempPath.c_str()); + return false; + } } tempFile.close(); free(plane1); @@ -221,44 +227,59 @@ bool Xtc::generateCoverBmp() const { // Write 2-bit BMP header const uint32_t imageSize2 = rowSize2 * pageInfo.height; const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; - coverFile.write('B'); - coverFile.write('M'); - coverFile.write(reinterpret_cast(&fileSize2), 4); - uint32_t rsv2 = 0; - coverFile.write(reinterpret_cast(&rsv2), 4); - uint32_t doff2 = 14 + 40 + 16; - coverFile.write(reinterpret_cast(&doff2), 4); - uint32_t dibSz2 = 40; - coverFile.write(reinterpret_cast(&dibSz2), 4); + static constexpr uint8_t bmpHeader2[70] = { + 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, + 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t hdr[70]; + memcpy(hdr, bmpHeader2, 70); + memcpy(hdr + 2, &fileSize2, 4); + const uint32_t doff2 = 14 + 40 + 16; + memcpy(hdr + 10, &doff2, 4); int32_t ww2 = pageInfo.width; - coverFile.write(reinterpret_cast(&ww2), 4); + memcpy(hdr + 18, &ww2, 4); int32_t hh2 = -static_cast(pageInfo.height); - coverFile.write(reinterpret_cast(&hh2), 4); - uint16_t pl2 = 1; - coverFile.write(reinterpret_cast(&pl2), 2); - uint16_t bpp2 = 2; - coverFile.write(reinterpret_cast(&bpp2), 2); - uint32_t cmp2 = 0, ppm2 = 2835, cu2 = 4, ci2 = 4; - coverFile.write(reinterpret_cast(&cmp2), 4); - coverFile.write(reinterpret_cast(&imageSize2), 4); - coverFile.write(reinterpret_cast(&ppm2), 4); - coverFile.write(reinterpret_cast(&ppm2), 4); - coverFile.write(reinterpret_cast(&cu2), 4); - coverFile.write(reinterpret_cast(&ci2), 4); - static constexpr uint8_t pal2[16] = {0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, - 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; - coverFile.write(pal2, 16); + memcpy(hdr + 22, &hh2, 4); + memcpy(hdr + 34, &imageSize2, 4); + const uint32_t ppm2 = 2835; + memcpy(hdr + 38, &ppm2, 4); + memcpy(hdr + 42, &ppm2, 4); + if (coverFile.write(hdr, 70) != 70) { + LOG_ERR("XTC", "Failed to write 2-bit BMP header"); + coverFile.close(); + tempFile.close(); + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } // For each row: read pass1 row (bit1 only), OR in bit2, write to final uint8_t rowBuf[256]; for (uint16_t y = 0; y < pageInfo.height; y++) { memset(rowBuf, 0, rowSize2); - tempFile.read(rowBuf, rowSize2); + if (tempFile.read(rowBuf, rowSize2) != rowSize2) { + LOG_ERR("XTC", "Failed to read temp cover row %u", y); + coverFile.close(); + tempFile.close(); + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } for (uint16_t x = 0; x < pageInfo.width; x++) { const size_t bo = (pageInfo.width - 1 - x) * colBytes + y / 8; const uint8_t bit2 = (plane2[bo] >> (7 - (y % 8))) & 1; rowBuf[x / 4] |= static_cast(bit2 << (6 - (x % 4) * 2)); } - coverFile.write(rowBuf, rowSize2); + if (coverFile.write(rowBuf, rowSize2) != rowSize2) { + LOG_ERR("XTC", "Failed to write cover row %u", y); + coverFile.close(); + tempFile.close(); + free(plane2); + Storage.remove(tempPath.c_str()); + return false; + } } coverFile.close(); tempFile.close(); From 7fa2b3d741e8196ee95b8b50cc4c2e1180a77365 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 13:36:54 +0200 Subject: [PATCH 21/93] fix: reject oversized PXC files to prevent out-of-bounds write --- lib/Xtc/Xtc.cpp | 10 ++++------ src/activities/util/PxcViewerActivity.cpp | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 36dc36d9d1..b6196854f5 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -228,12 +228,10 @@ bool Xtc::generateCoverBmp() const { const uint32_t imageSize2 = rowSize2 * pageInfo.height; const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; static constexpr uint8_t bmpHeader2[70] = { - 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, - 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; + 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, + 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; uint8_t hdr[70]; memcpy(hdr, bmpHeader2, 70); memcpy(hdr + 2, &fileSize2, 4); diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index bd08e38ae9..45ef139efa 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -39,7 +39,7 @@ void PxcViewerActivity::onEnter() { return; } - if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + if (pxcWidth > screenWidth || pxcHeight > screenHeight) { LOG_ERR("PXC", "PXC size %dx%d does not match screen %dx%d", pxcWidth, pxcHeight, screenWidth, screenHeight); file.close(); renderer.clearScreen(); @@ -125,7 +125,7 @@ void PxcViewerActivity::renderGrayscaleImage() { const int screenWidth = renderer.getScreenWidth(); const int screenHeight = renderer.getScreenHeight(); - if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + if (pxcWidth > screenWidth || pxcHeight > screenHeight) { file.close(); return; } From 3c06bc0a955c955bc91ddc346598edacb6754ef4 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 13:58:58 +0200 Subject: [PATCH 22/93] fix: correct BMP header array size from 70 to 74 bytes --- lib/Xtc/Xtc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index b6196854f5..32e47b41d7 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -227,13 +227,13 @@ bool Xtc::generateCoverBmp() const { // Write 2-bit BMP header const uint32_t imageSize2 = rowSize2 * pageInfo.height; const uint32_t fileSize2 = 14 + 40 + 16 + imageSize2; - static constexpr uint8_t bmpHeader2[70] = { - 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, + static constexpr uint8_t bmpHeader2[74] = { + 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, 0x00, 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00}; - uint8_t hdr[70]; - memcpy(hdr, bmpHeader2, 70); + uint8_t hdr[74]; + memcpy(hdr, bmpHeader2, 74); memcpy(hdr + 2, &fileSize2, 4); const uint32_t doff2 = 14 + 40 + 16; memcpy(hdr + 10, &doff2, 4); @@ -245,7 +245,7 @@ bool Xtc::generateCoverBmp() const { const uint32_t ppm2 = 2835; memcpy(hdr + 38, &ppm2, 4); memcpy(hdr + 42, &ppm2, 4); - if (coverFile.write(hdr, 70) != 70) { + if (coverFile.write(hdr, 74) != 74) { LOG_ERR("XTC", "Failed to write 2-bit BMP header"); coverFile.close(); tempFile.close(); From c47074b7d683ff5f710c95972ea6974cd02cfd90 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 15:56:55 +0200 Subject: [PATCH 23/93] fix: remove truncated thumbnail on failure in 2-bit thumb generation --- lib/Xtc/Xtc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 32e47b41d7..b942807ed8 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -491,6 +491,7 @@ bool Xtc::generateThumbBmp(int height) const { free(darkCount1Buf); free(planeBuffer); thumbBmp.close(); + Storage.remove(getThumbBmpPath(height).c_str()); return false; } @@ -506,6 +507,7 @@ bool Xtc::generateThumbBmp(int height) const { free(darkCount1Buf); free(planeBuffer); thumbBmp.close(); + Storage.remove(getThumbBmpPath(height).c_str()); return false; } for (int dy = 0; dy < curRows; dy++) { @@ -542,6 +544,7 @@ bool Xtc::generateThumbBmp(int height) const { free(darkCount1Buf); free(planeBuffer); thumbBmp.close(); + Storage.remove(getThumbBmpPath(height).c_str()); return false; } for (int dy = 0; dy < curRows; dy++) { From 1233a12591a4c42d40efc188ff3736ca429584a0 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 16:33:33 +0200 Subject: [PATCH 24/93] refactor: replace duplicated clamp-and-scale with computeSrcRange in 2-bit thumb path --- lib/Xtc/Xtc.cpp | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index b942807ed8..1313bfe0b8 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -513,18 +513,9 @@ bool Xtc::generateThumbBmp(int height) const { for (int dy = 0; dy < curRows; dy++) { const uint16_t dstY = static_cast(stripStart + dy); for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; - uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; - if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - if (srcYE <= srcYS) srcYE = srcYS + 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; - uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; - if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - if (srcXE <= srcXS) srcXE = srcXS + 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t srcYS, srcYE, srcXS, srcXE; + computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); + computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); uint32_t darkCount = 0; for (uint32_t sy = srcYS; sy < srcYE; sy++) for (uint32_t sx = srcXS; sx < srcXE; sx++) { @@ -551,18 +542,9 @@ bool Xtc::generateThumbBmp(int height) const { memset(rowBuf, 0xFF, rowSize); const uint16_t dstY = static_cast(stripStart + dy); for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) { - uint32_t srcYS = (static_cast(dstY) * scaleInv_fp2) >> 16; - uint32_t srcYE = (static_cast(dstY + 1) * scaleInv_fp2) >> 16; - if (srcYS >= pageInfo.height) srcYS = pageInfo.height - 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - if (srcYE <= srcYS) srcYE = srcYS + 1; - if (srcYE > pageInfo.height) srcYE = pageInfo.height; - uint32_t srcXS = (static_cast(dstX) * scaleInv_fp2) >> 16; - uint32_t srcXE = (static_cast(dstX + 1) * scaleInv_fp2) >> 16; - if (srcXS >= pageInfo.width) srcXS = pageInfo.width - 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; - if (srcXE <= srcXS) srcXE = srcXS + 1; - if (srcXE > pageInfo.width) srcXE = pageInfo.width; + uint32_t srcYS, srcYE, srcXS, srcXE; + computeSrcRange(dstY, scaleInv_fp2, pageInfo.height, srcYS, srcYE); + computeSrcRange(dstX, scaleInv_fp2, pageInfo.width, srcXS, srcXE); uint32_t darkCount = 0, totalCount = 0; for (uint32_t sy = srcYS; sy < srcYE; sy++) for (uint32_t sx = srcXS; sx < srcXE; sx++) { From 16fec5ceed24b2606e27c668eff8301dbae35040 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 27 Apr 2026 03:14:37 -0400 Subject: [PATCH 25/93] Remove X3 device-specific grayscale mode handling Standardize all grayscale rendering to use FactoryQuality mode instead of conditionally using Differential mode for X3 devices. Also inverts the useFactoryGray logic in EpubReaderActivity to enable factory grayscale for X3 devices on image pages. --- lib/GfxRenderer/GfxRenderer.cpp | 3 +-- src/activities/boot_sleep/SleepActivity.cpp | 4 ++-- src/activities/reader/EpubReaderActivity.cpp | 6 ++---- src/activities/util/BmpViewerActivity.cpp | 4 ++-- src/activities/util/PxcViewerActivity.cpp | 4 ++-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 0e6cd684c0..094ffc0767 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1540,8 +1540,7 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 screenshotHookCtx = nullptr; } - const bool isX3 = gpio.deviceIsX3(); - displayGrayBuffer(isX3 ? nullptr : lut_factory_quality, !isX3); + displayGrayBuffer(lut_factory_quality, true); setRenderMode(BW); } diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index a441a7a2df..b36d563dc7 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -222,7 +222,7 @@ void SleepActivity::renderPxcSleepScreen(const std::string& path) const { PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); c->file->seek(c->dataOffset); @@ -349,7 +349,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { }; BitmapGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, cropX, cropY}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 08b5780124..e48df9900e 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -763,7 +763,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or const auto tBwRender = millis(); const bool isImagePage = page->hasImages(); - const bool useFactoryGray = SETTINGS.textAntiAliasing && isImagePage && !gpio.deviceIsX3(); + const bool useFactoryGray = SETTINGS.textAntiAliasing && (isImagePage || gpio.deviceIsX3()); lastPageWasFactoryGray = useFactoryGray; if (useFactoryGray) { // Factory gray mode: skip BW display entirely — factory LUT drives pixels absolutely @@ -845,9 +845,7 @@ void EpubReaderActivity::onScreenshotRequest() { c->activity->renderStatusBar(); }; - renderer.renderGrayscale( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, grayFn, - &grayCtx); + renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, grayFn, &grayCtx); renderer.clearScreen(); renderer.cleanupGrayscaleWithFrameBuffer(); } diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index 75b1722228..89777dcb00 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -57,7 +57,7 @@ void BmpViewerActivity::onEnter() { }; BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); @@ -144,7 +144,7 @@ void BmpViewerActivity::renderGrayscaleImage() { BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index 45ef139efa..d8eeb9e95b 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -62,7 +62,7 @@ void PxcViewerActivity::onEnter() { PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); c->file->seek(c->dataOffset); @@ -141,7 +141,7 @@ void PxcViewerActivity::renderGrayscaleImage() { PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; renderer.renderGrayscaleSinglePass( - gpio.deviceIsX3() ? GfxRenderer::GrayscaleMode::Differential : GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); c->file->seek(c->dataOffset); From f0a032f8dc4897ac3539ea274af3e58c227d5d73 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 27 Apr 2026 03:32:03 -0400 Subject: [PATCH 26/93] Disable factory gray mode for X3 devices Remove X3-specific factory gray rendering path and associated pre-flash logic. Factory gray mode now only applies to image pages when text anti-aliasing is enabled, regardless of device type. --- lib/GfxRenderer/GfxRenderer.cpp | 13 ------------- src/activities/reader/EpubReaderActivity.cpp | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 094ffc0767..05f595666d 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1359,15 +1359,6 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), const void* preFlashCtx) { if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { - // Pre-flash to white so the factory LUT can drive particles reliably from any prior state. - // Without this, particles stranded at intermediate grays may not complete their transition: - // from a known-white state only downward transitions are needed, which both LUTs handle cleanly. - // - // HALF_REFRESH (CTRL1_BYPASS_RED) guarantees true white regardless of RED RAM sync state. - // FAST_REFRESH is differential against RED RAM — after any prior grayscale operation the RED RAM - // may be stale (e.g. chapter menu rendered while display shows gray), so pixels the controller - // believes are already white may physically be at gray or chapter-menu positions and won't be - // driven to white, corrupting the subsequent gray render. clearScreen(); if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); displayBuffer(HalDisplay::HALF_REFRESH); @@ -1386,7 +1377,6 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx setRenderMode(lsbMode); renderFn(*this, ctx); - // Save LSB plane for screenshot hook (needs both planes simultaneously). uint8_t* lsbCopy = nullptr; if (screenshotHook && factoryMode) { lsbCopy = static_cast(malloc(frameBufferSize)); @@ -1439,15 +1429,12 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) g_differentialQuantize = (mode == GrayscaleMode::Differential); - // Allocate secondary buffer for the MSB plane. auto* secBuf = static_cast(malloc(frameBufferSize)); if (!secBuf) { LOG_ERR("GFX", "renderGrayscaleSinglePass: malloc failed (%lu bytes), falling back to two-pass", static_cast(frameBufferSize)); - // Disarm hook — the two-pass fallback does not capture both planes simultaneously. screenshotHook = nullptr; screenshotHookCtx = nullptr; - // Pre-flash already done; run two-pass directly without repeating it. clearScreen(0x00); setRenderMode(lsbMode); renderFn(*this, ctx); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index e48df9900e..852dc02c62 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -763,7 +763,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or const auto tBwRender = millis(); const bool isImagePage = page->hasImages(); - const bool useFactoryGray = SETTINGS.textAntiAliasing && (isImagePage || gpio.deviceIsX3()); + const bool useFactoryGray = SETTINGS.textAntiAliasing && isImagePage; lastPageWasFactoryGray = useFactoryGray; if (useFactoryGray) { // Factory gray mode: skip BW display entirely — factory LUT drives pixels absolutely From 763d06b32163bcffcad6be3d7d8367e6a9dc035d Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 27 Apr 2026 04:59:12 -0400 Subject: [PATCH 27/93] Remove outdated display mode comments Remove obsolete comments about factory gray mode and text-only antialiasing display handling that are no longer relevant to the current implementation. --- src/activities/reader/EpubReaderActivity.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 852dc02c62..0f85f1e6d4 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -766,11 +766,9 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or const bool useFactoryGray = SETTINGS.textAntiAliasing && isImagePage; lastPageWasFactoryGray = useFactoryGray; if (useFactoryGray) { - // Factory gray mode: skip BW display entirely — factory LUT drives pixels absolutely lastFactoryMarginTop = orientedMarginTop; lastFactoryMarginLeft = orientedMarginLeft; } else { - // Text-only AA or no AA: BW display with refresh cadence ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh); } const auto tDisplay = millis(); From 8ac04d0ec31778eb845db9ab6fbd2f0de152f568 Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 27 Apr 2026 15:48:24 +0200 Subject: [PATCH 28/93] fix: gate 1-bit XTC sleep pre-flash on lastSleepFromReader The 1-bit XTC sleep path unconditionally cleared the screen and issued a HALF_REFRESH before calling displayXtcBwPage, which itself runs clearScreen + FAST_REFRESH. Match the 2-bit branch by skipping the pre-flash when the previous activity was the reader, avoiding the redundant refresh. Also dedupes a few render callbacks while in the area: - Extract the 'Entering sleep' overlay lambda into drawEnteringSleepOverlay. - Promote PageRenderCtx + grayFn to a private nested struct and static renderPageCallback shared by renderContents and onScreenshotRequest. - Consolidate PxcCtx and the rendering/overlay callbacks into file-scope statics behind a renderPxcToFramebuffer helper used by both onEnter and renderGrayscaleImage. --- src/activities/boot_sleep/SleepActivity.cpp | 51 +++--- src/activities/reader/EpubReaderActivity.cpp | 31 +--- src/activities/reader/EpubReaderActivity.h | 7 + src/activities/util/PxcViewerActivity.cpp | 163 +++++++------------ src/activities/util/PxcViewerActivity.h | 4 + 5 files changed, 98 insertions(+), 158 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index b36d563dc7..31287ab4c4 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -17,6 +17,21 @@ #include "fontIds.h" #include "images/Logo120.h" +namespace { +void drawEnteringSleepOverlay(const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_ENTERING_SLEEP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); +} +} // namespace + void SleepActivity::onEnter() { Activity::onEnter(); GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); @@ -247,20 +262,7 @@ void SleepActivity::renderPxcSleepScreen(const std::string& path) const { } free(rowBuf); }, - &ctx, - [](const GfxRenderer& r, const void*) { - constexpr int margin = 15; - const char* msg = tr(STR_ENTERING_SLEEP); - const int y = static_cast(r.getScreenHeight() * 0.075f); - const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); - const int w = textWidth + margin * 2; - const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int x = (r.getScreenWidth() - w) / 2; - r.fillRect(x - 2, y - 2, w + 4, h + 4, true); - r.fillRect(x, y, w, h, false); - r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); - }, - nullptr); + &ctx, &drawEnteringSleepOverlay, nullptr); } else { // BLACK_AND_WHITE / INVERTED_BLACK_AND_WHITE: threshold PXC to 1-bit // (pv 0=Black, 1=DarkGrey map to dark; 2=LightGrey, 3=White map to light) @@ -354,20 +356,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); }, - &grayCtx, - [](const GfxRenderer& r, const void*) { - constexpr int margin = 15; - const char* msg = tr(STR_ENTERING_SLEEP); - const int y = static_cast(r.getScreenHeight() * 0.075f); - const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); - const int w = textWidth + margin * 2; - const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int x = (r.getScreenWidth() - w) / 2; - r.fillRect(x - 2, y - 2, w + 4, h + 4, true); - r.fillRect(x, y, w, h, false); - r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); - }, - nullptr); + &grayCtx, &drawEnteringSleepOverlay, nullptr); } else { renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); @@ -458,8 +447,10 @@ void SleepActivity::renderCoverSleepScreen() const { return (this->*renderNoCoverSleepScreen)(); } LOG_DBG("SLP", "Direct XTC page render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); - renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); + if (!APP_STATE.lastSleepFromReader) { + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } renderer.displayXtcBwPage(pageBuffer, lastXtc.getPageWidth(), lastXtc.getPageHeight()); free(pageBuffer); return; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 0f85f1e6d4..434761066b 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -45,6 +45,12 @@ int clampPercent(int percent) { } // namespace +void EpubReaderActivity::renderPageCallback(const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->page->render(const_cast(r), c->fontId, c->left, c->top); + c->activity->renderStatusBar(); +} + void EpubReaderActivity::onEnter() { Activity::onEnter(); @@ -779,22 +785,12 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // grayscale rendering if (SETTINGS.textAntiAliasing) { - struct PageRenderCtx { - Page* page; - int fontId, left, top; - const EpubReaderActivity* activity; - }; PageRenderCtx grayCtx{page.get(), SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop, this}; - const auto grayFn = [](const GfxRenderer& r, const void* raw) { - const auto* c = static_cast(raw); - c->page->render(const_cast(r), c->fontId, c->left, c->top); - c->activity->renderStatusBar(); - }; const auto tGrayStart = millis(); const auto grayMode = useFactoryGray ? GfxRenderer::GrayscaleMode::FactoryQuality : GfxRenderer::GrayscaleMode::Differential; - renderer.renderGrayscale(grayMode, grayFn, &grayCtx); + renderer.renderGrayscale(grayMode, &renderPageCallback, &grayCtx); const auto tGrayEnd = millis(); fcm->logStats(useFactoryGray ? "gray_factory_quality" : "gray"); @@ -831,19 +827,8 @@ void EpubReaderActivity::onScreenshotRequest() { auto p = section->loadPageFromSectionFile(); if (!p) return; - struct PageRenderCtx { - Page* page; - int fontId, left, top; - const EpubReaderActivity* activity; - }; PageRenderCtx grayCtx{p.get(), SETTINGS.getReaderFontId(), lastFactoryMarginLeft, lastFactoryMarginTop, this}; - const auto grayFn = [](const GfxRenderer& r, const void* raw) { - const auto* c = static_cast(raw); - c->page->render(const_cast(r), c->fontId, c->left, c->top); - c->activity->renderStatusBar(); - }; - - renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, grayFn, &grayCtx); + renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, &renderPageCallback, &grayCtx); renderer.clearScreen(); renderer.cleanupGrayscaleWithFrameBuffer(); } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 59a999c4f7..72dac076a3 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -45,6 +45,13 @@ class EpubReaderActivity final : public Activity { SavedPosition savedPositions[MAX_FOOTNOTE_DEPTH] = {}; int footnoteDepth = 0; + struct PageRenderCtx { + Page* page; + int fontId, left, top; + const EpubReaderActivity* activity; + }; + static void renderPageCallback(const GfxRenderer& r, const void* raw); + void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar() const; diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index d8eeb9e95b..ccec33f7d7 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -9,9 +9,65 @@ #include "components/UITheme.h" #include "fontIds.h" +namespace { +struct PxcCtx { + FsFile* file; + uint32_t dataOffset; + int width, height; + MappedInputManager::Labels labels; +}; + +void pxcRenderCallback(const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->file->seek(c->dataOffset); + + const int bytesPerRow = (c->width + 3) / 4; + uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); + if (!rowBuf) { + LOG_ERR("PXC", "malloc failed for rowBuf (%d bytes, %dx%d)", bytesPerRow, c->width, c->height); + return; + } + + DirectPixelWriter pw; + pw.init(r); + + for (int row = 0; row < c->height; row++) { + if (c->file->read(rowBuf, bytesPerRow) != bytesPerRow) break; + pw.beginRow(row); + for (int col = 0; col < c->width; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + pw.writePixel(pv); + } + } + free(rowBuf); + + GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, c->labels.btn4); +} + +void pxcLoadingOverlay(const GfxRenderer& r, const void*) { + constexpr int margin = 15; + const char* msg = tr(STR_LOADING_POPUP); + const int y = static_cast(r.getScreenHeight() * 0.075f); + const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); + const int w = textWidth + margin * 2; + const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; + const int x = (r.getScreenWidth() - w) / 2; + r.fillRect(x - 2, y - 2, w + 4, h + 4, true); + r.fillRect(x, y, w, h, false); + r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); +} +} // namespace + PxcViewerActivity::PxcViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path) : Activity("PxcViewer", renderer, mappedInput), filePath(std::move(path)) {} +void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset) { + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + PxcCtx ctx{&file, dataOffset, width, height, labels}; + renderer.renderGrayscaleSinglePass(GfxRenderer::GrayscaleMode::FactoryQuality, &pxcRenderCallback, &ctx, + &pxcLoadingOverlay, nullptr); +} + void PxcViewerActivity::onEnter() { Activity::onEnter(); @@ -51,59 +107,7 @@ void PxcViewerActivity::onEnter() { } const uint32_t dataOffset = file.position(); - - struct PxcCtx { - FsFile* file; - uint32_t dataOffset; - int width, height; - MappedInputManager::Labels labels; - }; - const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); - PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; - - renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, - [](const GfxRenderer& r, const void* raw) { - const auto* c = static_cast(raw); - c->file->seek(c->dataOffset); - - const int bytesPerRow = (c->width + 3) / 4; - uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); - if (!rowBuf) { - LOG_ERR("PXC", "malloc failed for rowBuf (%d bytes, %dx%d)", bytesPerRow, c->width, c->height); - return; - } - - DirectPixelWriter pw; - pw.init(r); - - for (int row = 0; row < c->height; row++) { - if (c->file->read(rowBuf, bytesPerRow) != bytesPerRow) break; - pw.beginRow(row); - for (int col = 0; col < c->width; col++) { - const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; - pw.writePixel(pv); - } - } - free(rowBuf); - - GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, - c->labels.btn4); - }, - &ctx, - [](const GfxRenderer& r, const void*) { - constexpr int margin = 15; - const char* msg = tr(STR_LOADING_POPUP); - const int y = static_cast(r.getScreenHeight() * 0.075f); - const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); - const int w = textWidth + margin * 2; - const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int x = (r.getScreenWidth() - w) / 2; - r.fillRect(x - 2, y - 2, w + 4, h + 4, true); - r.fillRect(x, y, w, h, false); - r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); - }, - nullptr); + renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset); file.close(); @@ -131,58 +135,7 @@ void PxcViewerActivity::renderGrayscaleImage() { } const uint32_t dataOffset = file.position(); - const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); - struct PxcCtx { - FsFile* file; - uint32_t dataOffset; - int width, height; - MappedInputManager::Labels labels; - }; - PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight, labels}; - - renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, - [](const GfxRenderer& r, const void* raw) { - const auto* c = static_cast(raw); - c->file->seek(c->dataOffset); - - const int bytesPerRow = (c->width + 3) / 4; - uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); - if (!rowBuf) { - LOG_ERR("PXC", "malloc failed for rowBuf (%d bytes, %dx%d)", bytesPerRow, c->width, c->height); - return; - } - - DirectPixelWriter pw; - pw.init(r); - - for (int row = 0; row < c->height; row++) { - if (c->file->read(rowBuf, bytesPerRow) != bytesPerRow) break; - pw.beginRow(row); - for (int col = 0; col < c->width; col++) { - const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; - pw.writePixel(pv); - } - } - free(rowBuf); - - GUI.drawButtonHints(const_cast(r), c->labels.btn1, c->labels.btn2, c->labels.btn3, - c->labels.btn4); - }, - &ctx, - [](const GfxRenderer& r, const void*) { - constexpr int margin = 15; - const char* msg = tr(STR_LOADING_POPUP); - const int y = static_cast(r.getScreenHeight() * 0.075f); - const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); - const int w = textWidth + margin * 2; - const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int x = (r.getScreenWidth() - w) / 2; - r.fillRect(x - 2, y - 2, w + 4, h + 4, true); - r.fillRect(x, y, w, h, false); - r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); - }, - nullptr); + renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset); file.close(); } diff --git a/src/activities/util/PxcViewerActivity.h b/src/activities/util/PxcViewerActivity.h index ffa0f5c18d..377a2936b8 100644 --- a/src/activities/util/PxcViewerActivity.h +++ b/src/activities/util/PxcViewerActivity.h @@ -1,5 +1,8 @@ #pragma once +#include + +#include #include #include "../Activity.h" @@ -17,4 +20,5 @@ class PxcViewerActivity final : public Activity { private: std::string filePath; void renderGrayscaleImage(); + void renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset); }; From adb7ab244fe6edd4c5d223b0e2e927cdc0f9c5b6 Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 27 Apr 2026 16:07:14 +0200 Subject: [PATCH 29/93] fix: tighten PXC validation and screenshot BW restore - Reject PXC files whose dimensions don't exactly match the runtime screen size. The previous abs(w-sw) > 1 tolerance allowed a one-pixel oversize, which would cause out-of-bounds writes in DirectPixelWriter (no bounds checking on writePixel). Strict equality is correct per device since renderer.getScreenWidth/Height are device-aware (X4 800x480 vs X3 792x528). - Make renderPxcSleepScreen return bool so the /sleep.pxc path can fall through to /sleep.bmp when the PXC is missing/invalid, instead of short-circuiting straight to the default sleep screen. Other callers (random wallpaper picker, screenshot replay) explicitly fall back to renderDefaultSleepScreen on failure to preserve prior behaviour. - In onScreenshotRequest, store the BW page before the factory-gray render and restore it before cleanupGrayscaleWithFrameBuffer, so the controller's BW state is synced to the actual page rather than a cleared framebuffer. Mirrors the pattern in the normal render path. --- src/activities/boot_sleep/SleepActivity.cpp | 30 ++++++++++++-------- src/activities/boot_sleep/SleepActivity.h | 2 +- src/activities/reader/EpubReaderActivity.cpp | 6 +++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 31287ab4c4..71fcfcdfaa 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -110,7 +110,7 @@ void SleepActivity::renderCustomSleepScreen() const { } const int sw = renderer.getScreenWidth(); const int sh = renderer.getScreenHeight(); - if (abs(w - sw) > 1 || abs(h - sh) > 1) { + if (w != sw || h != sh) { LOG_DBG("SLP", "Skipping PXC size mismatch %dx%d (screen %dx%d): %s", w, h, sw, sh, name); file.close(); continue; @@ -135,7 +135,9 @@ void SleepActivity::renderCustomSleepScreen() const { LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); delay(100); if (FsHelpers::hasPxcExtension(files[randomFileIndex])) { - renderPxcSleepScreen(filename); + if (!renderPxcSleepScreen(filename)) { + renderDefaultSleepScreen(); + } dir.close(); return; } @@ -159,8 +161,9 @@ void SleepActivity::renderCustomSleepScreen() const { // Check root for sleep.pxc (preferred) or sleep.bmp if (Storage.exists("/sleep.pxc")) { LOG_DBG("SLP", "Loading: /sleep.pxc"); - renderPxcSleepScreen("/sleep.pxc"); - return; + if (renderPxcSleepScreen("/sleep.pxc")) { + return; + } } // Look for sleep.bmp on the root of the sd card to determine if we should @@ -200,26 +203,26 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.displayBuffer(HalDisplay::HALF_REFRESH); } -void SleepActivity::renderPxcSleepScreen(const std::string& path) const { +bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { FsFile file; if (!Storage.openFileForRead("SLP", path, file)) { LOG_ERR("SLP", "Cannot open PXC: %s", path.c_str()); - return renderDefaultSleepScreen(); + return false; } uint16_t pxcWidth, pxcHeight; if (file.read(&pxcWidth, 2) != 2 || file.read(&pxcHeight, 2) != 2) { LOG_ERR("SLP", "PXC header read failed: %s", path.c_str()); file.close(); - return renderDefaultSleepScreen(); + return false; } const int screenWidth = renderer.getScreenWidth(); const int screenHeight = renderer.getScreenHeight(); - if (abs(pxcWidth - screenWidth) > 1 || abs(pxcHeight - screenHeight) > 1) { + if (pxcWidth != screenWidth || pxcHeight != screenHeight) { LOG_ERR("SLP", "PXC size %dx%d does not match screen %dx%d", pxcWidth, pxcHeight, screenWidth, screenHeight); file.close(); - return renderDefaultSleepScreen(); + return false; } const uint32_t dataOffset = file.position(); // right after the 4-byte header @@ -270,14 +273,14 @@ void SleepActivity::renderPxcSleepScreen(const std::string& path) const { if (!file.seek(dataOffset)) { LOG_ERR("SLP", "PXC seek failed: %s", path.c_str()); file.close(); - return renderDefaultSleepScreen(); + return false; } uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); if (!rowBuf) { LOG_ERR("SLP", "PXC malloc failed"); file.close(); - return renderDefaultSleepScreen(); + return false; } for (int row = 0; row < pxcHeight; row++) { @@ -296,6 +299,7 @@ void SleepActivity::renderPxcSleepScreen(const std::string& path) const { } file.close(); + return true; } void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { @@ -521,7 +525,9 @@ void SleepActivity::renderBlankSleepScreen() const { void SleepActivity::onScreenshotRequest() { if (lastGrayscalePath.empty()) return; if (lastGrayscaleIsPxc) { - renderPxcSleepScreen(lastGrayscalePath); + if (!renderPxcSleepScreen(lastGrayscalePath)) { + renderDefaultSleepScreen(); + } } else { FsFile file; if (Storage.openFileForRead("SLP", lastGrayscalePath.c_str(), file)) { diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 07bb76ea0d..2952009341 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -17,7 +17,7 @@ class SleepActivity final : public Activity { void renderCustomSleepScreen() const; void renderCoverSleepScreen() const; void renderBitmapSleepScreen(const Bitmap& bitmap) const; - void renderPxcSleepScreen(const std::string& path) const; + bool renderPxcSleepScreen(const std::string& path) const; void renderBlankSleepScreen() const; // Tracks the last factory-LUT render so onScreenshotRequest() can re-render the same image. diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 434761066b..1ca25ecb5a 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -827,9 +827,13 @@ void EpubReaderActivity::onScreenshotRequest() { auto p = section->loadPageFromSectionFile(); if (!p) return; + // Preserve the BW page across the gray render so cleanupGrayscaleWithFrameBuffer + // syncs the controller to the actual page, not a cleared framebuffer. + if (!renderer.storeBwBuffer()) return; + PageRenderCtx grayCtx{p.get(), SETTINGS.getReaderFontId(), lastFactoryMarginLeft, lastFactoryMarginTop, this}; renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, &renderPageCallback, &grayCtx); - renderer.clearScreen(); + renderer.restoreBwBuffer(); renderer.cleanupGrayscaleWithFrameBuffer(); } From 2b831ecf84dc79512abe3c04c1f28e7a67b62342 Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Mon, 27 Apr 2026 16:34:59 +0200 Subject: [PATCH 30/93] docs: explain why sleep screenshot leaves controller desynced The clearScreen + cleanupGrayscaleWithFrameBuffer in onScreenshotRequest intentionally puts the controller into a 'BW = white' state. This is the correct precondition for the HALF_REFRESH pre-flash done by the next SleepActivity render (triggered by goToSleep on the way into deep sleep) and the panel resets across deep sleep, so no preservation of the displayed image is needed here. --- src/activities/boot_sleep/SleepActivity.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 71fcfcdfaa..dff3be525a 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -538,6 +538,7 @@ void SleepActivity::onScreenshotRequest() { file.close(); } } + // Device enters deep sleep next; on wake the new activity will full-refresh anyway. renderer.clearScreen(); renderer.cleanupGrayscaleWithFrameBuffer(); } From 265fb12bb9dc15f71aeade9463385d75eb20404f Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Tue, 28 Apr 2026 02:35:11 +0200 Subject: [PATCH 31/93] fix: correct Bayer dither thresholds for 4-level palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thresholds were 64/128/192, evenly partitioning [0,255] but mismatched to the actual output palette {0, 85, 170, 255}. The white cutoff at 192 (vs the correct midpoint 213) snapped grays in 192–212 — and via the ±40 dither offset, sources as low as ~157 — to pure white, blowing out highlights in EPUB JPEG/PNG renders. Use the palette midpoints 43/128/213 to match the calibration already used by the Atkinson and Floyd-Steinberg ditherers. --- lib/Epub/Epub/converters/DitherUtils.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index ec63a76840..e40115ff39 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -20,8 +20,9 @@ inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - if (adjusted < 64) return 0; + // Midpoint thresholds for output palette {0, 85, 170, 255}. + if (adjusted < 43) return 0; if (adjusted < 128) return 1; - if (adjusted < 192) return 2; + if (adjusted < 213) return 2; return 3; } From 1d72e2399f7318839b9c9d25ce469acdfcbf99ca Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Tue, 28 Apr 2026 18:38:17 +0200 Subject: [PATCH 32/93] fix: raise EPUB image dither thresholds for factory LUT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EPUB image pages render on the factory LUT, where palette levels are physically lighter than on the differential LUT used upstream. With the symmetric midpoint thresholds {43, 128, 213} mid-light tones (sRGB 128–213) blew out toward white. Raise T12 to 170 and T23 to 235 so mid-bright pixels land in palette 1 (drive 85) and only true highlights come out pure white. Brightness is now roughly equivalent to upstream's differential rendering. --- lib/Epub/Epub/converters/DitherUtils.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index e40115ff39..47886fcdbb 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -20,9 +20,18 @@ inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // Midpoint thresholds for output palette {0, 85, 170, 255}. + // Output palette {0, 85, 170, 255}. EPUB image pages render on the factory + // LUT (EpubReaderActivity: useFactoryGray), where palette levels are + // physically lighter than on the differential LUT used upstream. To get + // brightness roughly equivalent to upstream's differential rendering, we + // raise T12 and T23 to push more pixels into the darker palette indices: + // T01 = 43 — calibrated shadow boundary (linear midpoint, unchanged) + // T12 = 170 — widens palette 1 band so mid-bright pixels (sRGB 128–170) + // render as gray 85 instead of gray 170 + // T23 = 235 — narrows the white band so only true highlights come out + // pure white; mid-light tones stay at gray 170 if (adjusted < 43) return 0; - if (adjusted < 128) return 1; - if (adjusted < 213) return 2; + if (adjusted < 170) return 1; + if (adjusted < 235) return 2; return 3; } From 5c14403aed418c1f67bfe3c32c137ce9e5a4ee8c Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Tue, 28 Apr 2026 20:22:30 +0200 Subject: [PATCH 33/93] Revert "fix: raise EPUB image dither thresholds for factory LUT" This reverts commit 1d72e2399f7318839b9c9d25ce469acdfcbf99ca. --- lib/Epub/Epub/converters/DitherUtils.h | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index 47886fcdbb..e40115ff39 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -20,18 +20,9 @@ inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // Output palette {0, 85, 170, 255}. EPUB image pages render on the factory - // LUT (EpubReaderActivity: useFactoryGray), where palette levels are - // physically lighter than on the differential LUT used upstream. To get - // brightness roughly equivalent to upstream's differential rendering, we - // raise T12 and T23 to push more pixels into the darker palette indices: - // T01 = 43 — calibrated shadow boundary (linear midpoint, unchanged) - // T12 = 170 — widens palette 1 band so mid-bright pixels (sRGB 128–170) - // render as gray 85 instead of gray 170 - // T23 = 235 — narrows the white band so only true highlights come out - // pure white; mid-light tones stay at gray 170 + // Midpoint thresholds for output palette {0, 85, 170, 255}. if (adjusted < 43) return 0; - if (adjusted < 170) return 1; - if (adjusted < 235) return 2; + if (adjusted < 128) return 1; + if (adjusted < 213) return 2; return 3; } From 423c086512323d2ee6863750fd31eb71b603a5ef Mon Sep 17 00:00:00 2001 From: Patryk Radtke Date: Tue, 28 Apr 2026 20:50:56 +0200 Subject: [PATCH 34/93] fix: adjust dither thresholds and add soft-shoulder for factory LUT --- lib/Epub/Epub/converters/DitherUtils.h | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index e40115ff39..cbdcf4fa51 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -13,16 +13,30 @@ inline const uint8_t bayer4x4[4][4] = { // 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) { + // Soft-shoulder darkening for factory LUT: EPUB image pages render on the + // factory LUT, where palette levels are physically lighter than on the + // differential LUT. Apply a -12 offset to mid-bright pixels onward to bring + // highlights/midtones back down without crushing deep shadow detail. + // Ramp the offset from 0 to 12 across gray [0, 64], flat -12 above 64. + int g = gray; + int offset = (g < 64) ? g * 12 / 64 : 12; + g -= offset; + int bayer = bayer4x4[y & 3][x & 3]; int dither = (bayer - 8) * 5; // Scale to +/-40 (half of quantization step 85) - int adjusted = gray + dither; + int adjusted = g + dither; if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // Midpoint thresholds for output palette {0, 85, 170, 255}. - if (adjusted < 43) return 0; - if (adjusted < 128) return 1; - if (adjusted < 213) return 2; + // T12 raised from 128 to 150 so mid-bright source pixels (sRGB 150–170) + // land in the palette 1 / palette 2 dither zone, producing ~50% perceived + // reflectance via 57/43 mixing — the perceptual mid-gray that factory LUT + // can't reach with palette 2 alone (~70% reflectance). + // T23 raised from 192 to 210 to keep mid-bright pixels (sRGB 180–210) from + // blowing out to pure white after the soft-shoulder offset is applied. + if (adjusted < 64) return 0; + if (adjusted < 150) return 1; + if (adjusted < 210) return 2; return 3; } From e2ffc4b183af1818f710cd043b118b580e46ec94 Mon Sep 17 00:00:00 2001 From: pablohc Date: Tue, 5 May 2026 22:30:04 +0200 Subject: [PATCH 35/93] fix: reduce Bayer dither soft-shoulder and rebalance thresholds for factory LUT Reduce darkening offset from -12 to -8 and set thresholds to {48, 133, 218} to preserve shadow/midtone detail while compensating factory LUT brightness. Previous values (-12, {64, 150, 210}) caused image darkening artifacts and visual glitches on screen suspend from EPUB image pages. --- lib/Epub/Epub/converters/DitherUtils.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index cbdcf4fa51..f8614047da 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -15,11 +15,11 @@ inline const uint8_t bayer4x4[4][4] = { inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { // Soft-shoulder darkening for factory LUT: EPUB image pages render on the // factory LUT, where palette levels are physically lighter than on the - // differential LUT. Apply a -12 offset to mid-bright pixels onward to bring + // differential LUT. Apply a -8 offset to mid-bright pixels onward to bring // highlights/midtones back down without crushing deep shadow detail. - // Ramp the offset from 0 to 12 across gray [0, 64], flat -12 above 64. + // Ramp the offset from 0 to 8 across gray [0, 64], flat -8 above 64. int g = gray; - int offset = (g < 64) ? g * 12 / 64 : 12; + int offset = (g < 64) ? g * 8 / 64 : 8; g -= offset; int bayer = bayer4x4[y & 3][x & 3]; @@ -29,14 +29,14 @@ inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // T12 raised from 128 to 150 so mid-bright source pixels (sRGB 150–170) - // land in the palette 1 / palette 2 dither zone, producing ~50% perceived - // reflectance via 57/43 mixing — the perceptual mid-gray that factory LUT - // can't reach with palette 2 alone (~70% reflectance). - // T23 raised from 192 to 210 to keep mid-bright pixels (sRGB 180–210) from - // blowing out to pure white after the soft-shoulder offset is applied. - if (adjusted < 64) return 0; - if (adjusted < 150) return 1; - if (adjusted < 210) return 2; + // T01=48: slightly above original 43, keeps shadow detail while allowing + // near-black pixels to lift to dark gray. + // T12=133: raised from 128 to push more mid-bright source pixels into the + // palette 1 / palette 2 dither zone for perceptual mid-gray. + // T23=218: raised from 192 to expand light-gray range, preserving + // highlight detail on the brighter factory LUT. + if (adjusted < 48) return 0; + if (adjusted < 133) return 1; + if (adjusted < 218) return 2; return 3; } From f6a6ddb0f476146ac56b112d3c029e2a23d75123 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 8 May 2026 18:34:26 +0200 Subject: [PATCH 36/93] fix: PXC sleep screens always use factory LUT grayscale - Remove BW/Inverted filter path for PXC (bypassed factory LUT) - PXC now always uses FactoryQuality grayscale mode - DirectPixelWriter handles orientation transforms automatically - Filter settings (BLACK_AND_WHITE/INVERTED) ignored for PXC - Consistent with PxcViewerActivity behavior - Maintains PR 1614 intent (factory LUT + PXC support) --- src/activities/boot_sleep/SleepActivity.cpp | 101 +++++++------------- 1 file changed, 34 insertions(+), 67 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 144190de24..f84b8e28a1 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -242,77 +242,44 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { } const uint32_t dataOffset = file.position(); // right after the 4-byte header - const auto filter = SETTINGS.sleepScreenCoverFilter; - const int bytesPerRow = (pxcWidth + 3) / 4; - - if (filter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER) { - lastGrayscalePath = path; - lastGrayscaleIsPxc = true; - struct PxcCtx { - FsFile* file; - uint32_t dataOffset; - int width, height; - }; - PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; - renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, - [](const GfxRenderer& r, const void* raw) { - const auto* c = static_cast(raw); - c->file->seek(c->dataOffset); - - const int bpr = (c->width + 3) / 4; - uint8_t* rowBuf = static_cast(malloc(bpr)); - if (!rowBuf) { - LOG_ERR("SLP", "malloc failed for rowBuf (%d bytes, %dx%d)", bpr, c->width, c->height); - return; - } + // PXC is always 2-bit grayscale - always use factory LUT + lastGrayscalePath = path; + lastGrayscaleIsPxc = true; + struct PxcCtx { + FsFile* file; + uint32_t dataOffset; + int width, height; + }; + PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; + + renderer.renderGrayscaleSinglePass( + GfxRenderer::GrayscaleMode::FactoryQuality, + [](const GfxRenderer& r, const void* raw) { + const auto* c = static_cast(raw); + c->file->seek(c->dataOffset); + + const int bpr = (c->width + 3) / 4; + uint8_t* rowBuf = static_cast(malloc(bpr)); + if (!rowBuf) { + LOG_ERR("SLP", "malloc failed for rowBuf (%d bytes, %dx%d)", bpr, c->width, c->height); + return; + } - DirectPixelWriter pw; - pw.init(r); + DirectPixelWriter pw; + pw.init(r); - for (int row = 0; row < c->height; row++) { - if (c->file->read(rowBuf, bpr) != bpr) break; - pw.beginRow(row); - for (int col = 0; col < c->width; col++) { - const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; - pw.writePixel(pv); - } + for (int row = 0; row < c->height; row++) { + if (c->file->read(rowBuf, bpr) != bpr) break; + pw.beginRow(row); + for (int col = 0; col < c->width; col++) { + const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; + pw.writePixel(pv); } - free(rowBuf); - }, - &ctx, &drawEnteringSleepOverlay, nullptr); - } else { - // BLACK_AND_WHITE / INVERTED_BLACK_AND_WHITE: threshold PXC to 1-bit - // (pv 0=Black, 1=DarkGrey map to dark; 2=LightGrey, 3=White map to light) - renderer.clearScreen(); - if (!file.seek(dataOffset)) { - LOG_ERR("SLP", "PXC seek failed: %s", path.c_str()); - file.close(); - return false; - } - - uint8_t* rowBuf = static_cast(malloc(bytesPerRow)); - if (!rowBuf) { - LOG_ERR("SLP", "PXC malloc failed"); - file.close(); - return false; - } - - for (int row = 0; row < pxcHeight; row++) { - if (file.read(rowBuf, bytesPerRow) != bytesPerRow) break; - for (int col = 0; col < pxcWidth; col++) { - const uint8_t pv = (rowBuf[col >> 2] >> (6 - (col & 3) * 2)) & 0x03; - if (pv < 2) renderer.drawPixel(col, row, true); - } - } - free(rowBuf); - - if (filter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { - renderer.invertScreen(); - } - renderer.displayBuffer(HalDisplay::FULL_REFRESH); - } + } + free(rowBuf); + }, + &ctx, &drawEnteringSleepOverlay, nullptr); file.close(); return true; From c0bea867de1d0612682277d914f57148c52df712 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 8 May 2026 19:34:24 +0200 Subject: [PATCH 37/93] chore: update SDK to fix X3 factory fast mode fallback --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index 9ea2f02f56..7df6023954 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 9ea2f02f56477d927a1bacd03821a70fc41829c7 +Subproject commit 7df6023954dae1b10db480551a4ef7e673f5326e From 52ddb1f2cf15f493d913c83b34b9ae1065029e4c Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 8 May 2026 19:45:59 +0200 Subject: [PATCH 38/93] chore: update SDK to fix grayscaleRevert logic bug --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index 7df6023954..e9dbce287c 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 7df6023954dae1b10db480551a4ef7e673f5326e +Subproject commit e9dbce287c6618220b184cb2849ddc438c5b9fdb From f4960a3a90e4d85ca3ffbc843272fc31b4f1f4b6 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 8 May 2026 22:19:42 +0200 Subject: [PATCH 39/93] fix: resolve PR1614 xtc status bar merge --- lib/GfxRenderer/GfxRenderer.cpp | 13 ++- lib/GfxRenderer/GfxRenderer.h | 8 +- lib/I18n/translations/english.yaml | 3 + src/CrossPointSettings.h | 7 ++ src/SettingsList.h | 3 + src/activities/home/FileBrowserActivity.cpp | 15 +-- src/activities/reader/XtcReaderActivity.cpp | 96 ++++++++++++++++++- src/activities/reader/XtcReaderActivity.h | 15 +++ .../settings/StatusBarSettingsActivity.cpp | 17 +++- src/main.cpp | 12 ++- 10 files changed, 173 insertions(+), 16 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 940df8b61c..4cb0236044 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1509,7 +1509,7 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) } void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, - const uint16_t pageHeight) { + const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx) { const size_t colBytes = (pageHeight + 7) / 8; const uint16_t fbStride = panelWidthBytes; @@ -1534,6 +1534,8 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 dstRow[b] = srcCol[b]; } } + setRenderMode(GRAY2_LSB); + if (overlayFn) overlayFn(*this, overlayCtx); copyGrayscaleLsbBuffers(); @@ -1546,6 +1548,8 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 dstRow[b] = srcCol[b]; } } + setRenderMode(GRAY2_MSB); + if (overlayFn) overlayFn(*this, overlayCtx); copyGrayscaleMsbBuffers(); // Fire hook: plane1 input IS already in framebuffer format (colBytes == fbStride for portrait @@ -1560,7 +1564,9 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 setRenderMode(BW); } -void GfxRenderer::displayXtcBwPage(const uint8_t* pageBuffer, const uint16_t pageWidth, const uint16_t pageHeight) { +void GfxRenderer::displayXtcBwPage(const uint8_t* pageBuffer, const uint16_t pageWidth, const uint16_t pageHeight, + const HalDisplay::RefreshMode refreshMode, RenderHook overlayFn, + const void* overlayCtx) { const size_t srcRowBytes = (pageWidth + 7) / 8; // 1-bit content has no AA — render as plain BW and use the standard differential fast-refresh @@ -1573,7 +1579,8 @@ void GfxRenderer::displayXtcBwPage(const uint8_t* pageBuffer, const uint16_t pag } } } - displayBuffer(HalDisplay::FAST_REFRESH); + if (overlayFn) overlayFn(*this, overlayCtx); + displayBuffer(refreshMode); } void GfxRenderer::freeBwBufferChunks() { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 3b00ab9654..fc6aa0ea5d 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -54,6 +54,7 @@ class GfxRenderer { // Hook is cleared automatically after firing. using ScreenshotHook = void (*)(const uint8_t* lsbPlane, const uint8_t* msbPlane, int physWidth, int physHeight, void* ctx); + using RenderHook = void (*)(const GfxRenderer&, const void*); private: static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory @@ -206,11 +207,14 @@ class GfxRenderer { // Direct 2-bit XTCH plane blit using factory LUT. Caller supplies the two decoded bit planes // (plane1 = BW RAM / LSB, plane2 = RED RAM / MSB) in column-major order matching XTCH encoding. // Handles pre-flash, both RAM writes, factory LUT fire, and BW controller sync internally. - void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight); + void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight, + RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr); // 1-bit XTC page via the same grayscale LUT pipeline. Row-major pageBuffer (XTC: 0=black, 1=white). // BW and RED RAM receive identical data since there are no intermediate gray levels. - void displayXtcBwPage(const uint8_t* pageBuffer, uint16_t pageWidth, uint16_t pageHeight); + void displayXtcBwPage(const uint8_t* pageBuffer, uint16_t pageWidth, uint16_t pageHeight, + HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH, RenderHook overlayFn = nullptr, + const void* overlayCtx = nullptr); // Font helpers const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const; diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index b662389992..bcc1a20b19 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -229,6 +229,9 @@ STR_EXAMPLE_BOOK: "Book Title" STR_PREVIEW: "Preview" STR_TITLE: "Title" STR_BATTERY: "Battery" +STR_XTC_STATUS_BAR: "XTC Status Bar" +STR_BOTTOM: "Bottom" +STR_TOP: "Top" STR_UI_THEME: "UI Theme" STR_THEME_CLASSIC: "Classic" STR_THEME_LYRA: "Lyra" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 01d7cc5618..cf5c695df0 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -57,6 +57,12 @@ class CrossPointSettings { STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT }; enum STATUS_BAR_TITLE { BOOK_TITLE = 0, CHAPTER_TITLE = 1, HIDE_TITLE = 2, STATUS_BAR_TITLE_COUNT }; + enum XTC_STATUS_BAR_MODE { + XTC_STATUS_BAR_HIDE = 0, + XTC_STATUS_BAR_BOTTOM = 1, + XTC_STATUS_BAR_TOP = 2, + XTC_STATUS_BAR_MODE_COUNT + }; enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) @@ -161,6 +167,7 @@ class CrossPointSettings { uint8_t statusBarProgressBarThickness = PROGRESS_BAR_NORMAL; uint8_t statusBarTitle = CHAPTER_TITLE; uint8_t statusBarBattery = 1; + uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE; // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; diff --git a/src/SettingsList.h b/src/SettingsList.h index bbe9a77458..cf2d02ed8b 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -131,6 +131,9 @@ inline const std::vector& getSettingsList() { StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Toggle(StrId::STR_BATTERY, &CrossPointSettings::statusBarBattery, "statusBarBattery", StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_XTC_STATUS_BAR, &CrossPointSettings::xtcStatusBarMode, + {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}, "xtcStatusBarMode", + StrId::STR_CUSTOMISE_STATUS_BAR), }; // Only show tilt page turn setting when the QMI8658 IMU is present (X3) if (halTiltSensor.isAvailable()) { diff --git a/src/activities/home/FileBrowserActivity.cpp b/src/activities/home/FileBrowserActivity.cpp index 66a94469ac..5737fdca82 100644 --- a/src/activities/home/FileBrowserActivity.cpp +++ b/src/activities/home/FileBrowserActivity.cpp @@ -142,17 +142,20 @@ void FileBrowserActivity::loop() { return; } - if (mode == Mode::Books && mappedInput.getHeldTime() >= GO_HOME_MS && !isDirectory) { - // --- LONG PRESS ACTION: DELETE FILE --- + if (mode == Mode::Books && mappedInput.getHeldTime() >= GO_HOME_MS) { + // --- LONG PRESS ACTION: DELETE FILE OR DIRECTORY --- std::string cleanBasePath = basepath; if (cleanBasePath.back() != '/') cleanBasePath += "/"; const std::string fullPath = cleanBasePath + entry; - auto handler = [this, fullPath](const ActivityResult& res) { + auto handler = [this, fullPath, isDirectory](const ActivityResult& res) { if (!res.isCancelled) { LOG_DBG("FileBrowser", "Attempting to delete: %s", fullPath.c_str()); - clearFileMetadata(fullPath); - if (Storage.remove(fullPath.c_str())) { + if (!isDirectory) { + clearFileMetadata(fullPath); + } + const bool deleted = isDirectory ? Storage.removeDir(fullPath.c_str()) : Storage.remove(fullPath.c_str()); + if (deleted) { LOG_DBG("FileBrowser", "Deleted successfully"); loadFiles(); if (files.empty()) { @@ -164,7 +167,7 @@ void FileBrowserActivity::loop() { requestUpdate(true); } else { - LOG_ERR("FileBrowser", "Failed to delete file: %s", fullPath.c_str()); + LOG_ERR("FileBrowser", "Failed to delete: %s", fullPath.c_str()); } } else { LOG_DBG("FileBrowser", "Delete cancelled by user"); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 5779fb7897..dc4486f9ae 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "CrossPointSettings.h" #include "CrossPointState.h" #include "MappedInputManager.h" @@ -135,6 +137,86 @@ void XtcReaderActivity::render(RenderLock&&) { saveProgress(); } +void XtcReaderActivity::renderStatusBarOverlayCallback(const GfxRenderer&, const void* raw) { + const auto* activity = static_cast(raw); + activity->renderConfiguredStatusBarOverlay(); +} + +XtcReaderActivity::StatusBarInfo XtcReaderActivity::getStatusBarInfo() const { + const int bookPageCount = static_cast(xtc->getPageCount()); + const int bookPage = static_cast(currentPage) + 1; + std::string title = + SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE ? xtc->getTitle() : ""; + + if (!xtc->hasChapters()) { + return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; + } + + const auto& chapters = xtc->getChapters(); + const auto chapterIt = std::find_if(chapters.begin(), chapters.end(), [this](const xtc::ChapterInfo& chapter) { + return currentPage >= chapter.startPage && currentPage <= chapter.endPage; + }); + + if (chapterIt == chapters.end() || chapterIt->endPage < chapterIt->startPage) { + return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; + } + + if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { + title = chapterIt->name.empty() ? tr(STR_UNNAMED) : chapterIt->name; + } + + return StatusBarInfo{static_cast(currentPage - chapterIt->startPage) + 1, + static_cast(chapterIt->endPage - chapterIt->startPage) + 1, std::move(title)}; +} + +void XtcReaderActivity::renderStatusBarOverlay(const StatusBarOverlayPosition position) const { + const bool drawBottom = SETTINGS.xtcStatusBarMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_BOTTOM && + position == StatusBarOverlayPosition::Bottom; + const bool drawTop = SETTINGS.xtcStatusBarMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_TOP && + position == StatusBarOverlayPosition::Top; + if (!drawBottom && !drawTop) { + return; + } + + const int statusBarHeight = UITheme::getInstance().getStatusBarHeight(); + if (statusBarHeight <= 0) { + return; + } + + int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; + renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, + &orientedMarginLeft); + + int clearY; + int paddingBottom = 0; + if (position == StatusBarOverlayPosition::Bottom) { + clearY = renderer.getScreenHeight() - orientedMarginBottom - statusBarHeight - 4; + if (clearY < 0) { + clearY = 0; + } + } else { + clearY = orientedMarginTop; + paddingBottom = renderer.getScreenHeight() - statusBarHeight - orientedMarginBottom - orientedMarginTop - 4; + } + const int clearHeight = position == StatusBarOverlayPosition::Bottom + ? renderer.getScreenHeight() - orientedMarginBottom - clearY + : statusBarHeight + 4; + if (clearHeight > 0) { + renderer.fillRect(0, clearY, renderer.getScreenWidth(), clearHeight, false); + } + + const int pageCount = static_cast(xtc->getPageCount()); + const int displayPage = static_cast(currentPage) + 1; + const float progress = pageCount > 0 ? (static_cast(displayPage) * 100.0f) / pageCount : 0.0f; + const auto pageInfo = getStatusBarInfo(); + GUI.drawStatusBar(renderer, progress, pageInfo.currentPage, pageInfo.pageCount, pageInfo.title, paddingBottom); +} + +void XtcReaderActivity::renderConfiguredStatusBarOverlay() const { + renderStatusBarOverlay(StatusBarOverlayPosition::Top); + renderStatusBarOverlay(StatusBarOverlayPosition::Bottom); +} + void XtcReaderActivity::renderPage() { const uint16_t pageWidth = xtc->getPageWidth(); const uint16_t pageHeight = xtc->getPageHeight(); @@ -188,7 +270,8 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(HalDisplay::FULL_REFRESH); } - renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight); + renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight, &XtcReaderActivity::renderStatusBarOverlayCallback, + this); free(plane1); free(plane2); @@ -215,8 +298,17 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(); return; } - renderer.displayXtcBwPage(pageBuffer, pageWidth, pageHeight); + const bool doFullRefresh = pagesUntilFullRefresh <= 1; + renderer.displayXtcBwPage(pageBuffer, pageWidth, pageHeight, + doFullRefresh ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH, + &XtcReaderActivity::renderStatusBarOverlayCallback, this); free(pageBuffer); + if (doFullRefresh) { + renderer.cleanupGrayscaleWithFrameBuffer(); + pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); + } else { + pagesUntilFullRefresh--; + } LOG_DBG("XTR", "Rendered page %lu/%lu (1-bit)", currentPage + 1, xtc->getPageCount()); } diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 07d21b4d3f..af2da44fd1 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -9,15 +9,30 @@ #include +#include +#include + #include "activities/Activity.h" class XtcReaderActivity final : public Activity { std::shared_ptr xtc; uint32_t currentPage = 0; + int pagesUntilFullRefresh = 0; uint32_t pagesSinceClean = 0; + enum class StatusBarOverlayPosition { Bottom, Top }; + struct StatusBarInfo { + int currentPage; + int pageCount; + std::string title; + }; + + static void renderStatusBarOverlayCallback(const GfxRenderer& renderer, const void* raw); void renderPage(); + void renderStatusBarOverlay(StatusBarOverlayPosition position) const; + void renderConfiguredStatusBarOverlay() const; + StatusBarInfo getStatusBarInfo() const; void saveProgress() const; void loadProgress(); diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 6ff0b34d37..f62ce4d4b6 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -11,13 +11,14 @@ #include "fontIds.h" namespace { -constexpr int MENU_ITEMS = 6; +constexpr int MENU_ITEMS = 7; const StrId menuNames[MENU_ITEMS] = {StrId::STR_CHAPTER_PAGE_COUNT, StrId::STR_BOOK_PROGRESS_PERCENTAGE, StrId::STR_PROGRESS_BAR, StrId::STR_PROGRESS_BAR_THICKNESS, StrId::STR_TITLE, - StrId::STR_BATTERY}; + StrId::STR_BATTERY, + StrId::STR_XTC_STATUS_BAR}; constexpr int PROGRESS_BAR_ITEMS = 3; const StrId progressBarNames[PROGRESS_BAR_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; @@ -28,6 +29,9 @@ const StrId progressBarThicknessNames[PROGRESS_BAR_THICKNESS_ITEMS] = { constexpr int TITLE_ITEMS = 3; const StrId titleNames[TITLE_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; +constexpr int XTC_STATUS_BAR_ITEMS = 3; +const StrId xtcStatusBarNames[XTC_STATUS_BAR_ITEMS] = {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}; + const int widthMargin = 10; const int verticalPreviewPadding = 50; const int verticalPreviewTextPadding = 40; @@ -51,6 +55,10 @@ void StatusBarSettingsActivity::onEnter() { SETTINGS.statusBarTitle = CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE; } + if (SETTINGS.xtcStatusBarMode >= XTC_STATUS_BAR_ITEMS) { + SETTINGS.xtcStatusBarMode = CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_HIDE; + } + requestUpdate(); } @@ -110,6 +118,9 @@ void StatusBarSettingsActivity::handleSelection() { } else if (selectedIndex == 5) { // Show Battery SETTINGS.statusBarBattery = (SETTINGS.statusBarBattery + 1) % 2; + } else if (selectedIndex == 6) { + // XTC Status Bar + SETTINGS.xtcStatusBarMode = (SETTINGS.xtcStatusBarMode + 1) % XTC_STATUS_BAR_ITEMS; } SETTINGS.saveToFile(); } @@ -143,6 +154,8 @@ void StatusBarSettingsActivity::render(RenderLock&&) { return I18N.get(titleNames[SETTINGS.statusBarTitle]); } else if (index == 5) { return SETTINGS.statusBarBattery ? tr(STR_SHOW) : tr(STR_HIDE); + } else if (index == 6) { + return I18N.get(xtcStatusBarNames[SETTINGS.xtcStatusBarMode]); } else { return tr(STR_HIDE); } diff --git a/src/main.cpp b/src/main.cpp index d6f5533d44..6c540a0433 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -381,7 +381,9 @@ void loop() { } static bool screenshotButtonsReleased = true; + static bool screenshotComboActive = false; if (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.isPressed(HalGPIO::BTN_DOWN)) { + screenshotComboActive = true; if (screenshotButtonsReleased) { screenshotButtonsReleased = false; { @@ -401,8 +403,16 @@ void loop() { } } return; - } else { + } + if (screenshotComboActive) { + if (gpio.isPressed(HalGPIO::BTN_POWER)) return; + if (gpio.wasReleased(HalGPIO::BTN_POWER)) { + screenshotButtonsReleased = true; + screenshotComboActive = false; + return; + } screenshotButtonsReleased = true; + screenshotComboActive = false; } const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs(); From 7ee4f6fe4f2d654228ed43bd32d0919587739030 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 8 May 2026 22:38:10 +0200 Subject: [PATCH 40/93] chore: apply clang-format fix for xtc reader --- src/activities/reader/XtcReaderActivity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 8fe68c0b3a..254195f739 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -268,8 +268,8 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(HalDisplay::FULL_REFRESH); } - renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight, &XtcReaderActivity::renderStatusBarOverlayCallback, - this); + renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight, + &XtcReaderActivity::renderStatusBarOverlayCallback, this); free(plane1); free(plane2); From 9d03cae1106b4781aadd61d019fa69eddbe4c9d6 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sat, 9 May 2026 18:46:05 +0200 Subject: [PATCH 41/93] fix: declare gray cleanup intent + drop redundant RED RAM rebase Two coupled changes for the new SDK contract: - After displayGrayBuffer in GfxRenderer::renderGrayscale and renderGrayscaleSinglePass (both the malloc'd path and the two-pass fallback), call display.clearGrayscaleModeFlag(). Differential mode rendering was leaving inGrayscaleMode=true, which (post-SDK fix) triggered an automatic ~700ms revert refresh on the next BW page turn. The renderer already manages cleanup via cleanupGrayscaleBuffers inside restoreBwBuffer, so the SDK auto-revert is unwanted overhead. - Drop the redundant cleanupGrayscaleWithFrameBuffer call after restoreBwBuffer in EpubReaderActivity (page render path and onScreenshotRequest). restoreBwBuffer already calls cleanupGrayscaleBuffers(frameBuffer), so the explicit second call wrote RED RAM with identical data twice per page. RED RAM is now rebased exactly once per gray-to-BW transition. Bumps SDK submodule to ccfd37b (community-sdk PR #4 head): the clearGrayscaleModeFlag() accessor and idempotent grayscaleRevert contract this code depends on. --- lib/GfxRenderer/GfxRenderer.cpp | 8 ++++++++ lib/hal/HalDisplay.h | 5 +++++ open-x4-sdk | 2 +- src/activities/reader/EpubReaderActivity.cpp | 14 ++++++-------- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index bc858f0ac9..f12b48ff42 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1496,6 +1496,10 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx g_differentialQuantize = false; displayGrayBuffer(lut, factoryMode); + // Suppress the SDK's automatic grayscaleRevert on the next BW page turn. + // Caller is responsible for cleanup (restoreBwBuffer rebases RED RAM and + // the next FAST_REFRESH drives pixels back to clean BW). + display.clearGrayscaleModeFlag(); setRenderMode(BW); } @@ -1532,6 +1536,8 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) copyGrayscaleMsbBuffers(); g_differentialQuantize = false; displayGrayBuffer(lut, factoryMode); + // See note in renderGrayscale(). + display.clearGrayscaleModeFlag(); setRenderMode(BW); return; } @@ -1563,6 +1569,8 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) g_differentialQuantize = false; displayGrayBuffer(lut, factoryMode); + // See note in renderGrayscale(). + display.clearGrayscaleModeFlag(); setRenderMode(BW); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index d12a5c5b8a..b39d6b10e6 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -49,6 +49,11 @@ class HalDisplay { void displayGrayBuffer(bool turnOffScreen = false, const unsigned char* lut = nullptr, bool factoryMode = false); + // Tell the SDK that grayscale state has been cleaned up by the consumer + // (RAM banks rebased + a follow-up FAST_REFRESH will handle pixel cleanup), + // so the next displayBuffer() should not run grayscaleRevert(). + void clearGrayscaleModeFlag() { einkDisplay.clearGrayscaleModeFlag(); } + // Runtime geometry passthrough uint16_t getDisplayWidth() const; uint16_t getDisplayHeight() const; diff --git a/open-x4-sdk b/open-x4-sdk index e9dbce287c..ccfd37be5d 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit e9dbce287c6618220b184cb2849ddc438c5b9fdb +Subproject commit ccfd37be5d0ee1ce8eef1c389db3941b1c1b61b3 diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 54a9fdf257..d020bcce25 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -819,12 +819,10 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or const auto tGrayEnd = millis(); fcm->logStats(useFactoryGray ? "gray_factory_quality" : "gray"); + // restoreBwBuffer() copies the saved BW frame back AND rebases RED RAM + // via cleanupGrayscaleBuffers, which is sufficient cleanup for both + // differential and factory paths — no extra RED RAM write needed. renderer.restoreBwBuffer(); - if (useFactoryGray) { - // Factory LUT leaves RED RAM in gray-encoded state; sync controller to the - // restored BW framebuffer so subsequent BW page turns render cleanly. - renderer.cleanupGrayscaleWithFrameBuffer(); - } const auto tBwRestore = millis(); const auto tEnd = millis(); @@ -852,14 +850,14 @@ void EpubReaderActivity::onScreenshotRequest() { auto p = section->loadPageFromSectionFile(); if (!p) return; - // Preserve the BW page across the gray render so cleanupGrayscaleWithFrameBuffer - // syncs the controller to the actual page, not a cleared framebuffer. + // Preserve the BW page across the gray render so restoreBwBuffer's + // cleanupGrayscaleBuffers call rebases RED RAM to the actual page, not a + // cleared framebuffer. if (!renderer.storeBwBuffer()) return; PageRenderCtx grayCtx{p.get(), SETTINGS.getReaderFontId(), lastFactoryMarginLeft, lastFactoryMarginTop, this}; renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, &renderPageCallback, &grayCtx); renderer.restoreBwBuffer(); - renderer.cleanupGrayscaleWithFrameBuffer(); } void EpubReaderActivity::renderStatusBar() const { From 368b83bbd88c875ce2a1046422cc8ea2ca1b5abd Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sat, 9 May 2026 22:36:09 +0200 Subject: [PATCH 42/93] feat: PXC viewer sibling navigation and set-sleep-cover Mirrors the BMP viewer feature set for .pxc files: Up/Down navigates siblings in the current folder, Confirm copies the current image to /sleep.pxc and switches sleepScreen to CUSTOM. --- src/activities/util/PxcViewerActivity.cpp | 110 +++++++++++++++++++++- src/activities/util/PxcViewerActivity.h | 6 ++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index ccec33f7d7..d397b0cb88 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -1,10 +1,12 @@ #include "PxcViewerActivity.h" +#include #include #include #include #include +#include "CrossPointSettings.h" #include "Epub/converters/DirectPixelWriter.h" #include "components/UITheme.h" #include "fontIds.h" @@ -61,8 +63,49 @@ void pxcLoadingOverlay(const GfxRenderer& r, const void*) { PxcViewerActivity::PxcViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path) : Activity("PxcViewer", renderer, mappedInput), filePath(std::move(path)) {} +void PxcViewerActivity::loadSiblingImages() { + siblingImages.clear(); + currentImageIndex = -1; + + if (filePath.empty()) return; + + std::string dirPath = FsHelpers::extractFolderPath(filePath); + size_t lastSlash = filePath.find_last_of('/'); + std::string fileName = (lastSlash != std::string::npos) ? filePath.substr(lastSlash + 1) : filePath; + + auto dir = Storage.open(dirPath.c_str()); + if (!dir || !dir.isDirectory()) { + if (dir) dir.close(); + return; + } + + char name[500]; + for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { + if (!file.isDirectory()) { + file.getName(name, sizeof(name)); + if (name[0] != '.') { + std::string fname(name); + if (FsHelpers::hasPxcExtension(fname)) { + siblingImages.push_back(fname); + } + } + } + file.close(); + } + dir.close(); + + FsHelpers::sortFileList(siblingImages); + + for (size_t i = 0; i < siblingImages.size(); ++i) { + if (siblingImages[i] == fileName) { + currentImageIndex = static_cast(i); + break; + } + } +} + void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset) { - const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), "", ""); PxcCtx ctx{&file, dataOffset, width, height, labels}; renderer.renderGrayscaleSinglePass(GfxRenderer::GrayscaleMode::FactoryQuality, &pxcRenderCallback, &ctx, &pxcLoadingOverlay, nullptr); @@ -71,6 +114,10 @@ void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uin void PxcViewerActivity::onEnter() { Activity::onEnter(); + if (siblingImages.empty() && !filePath.empty()) { + loadSiblingImages(); + } + const int screenWidth = renderer.getScreenWidth(); const int screenHeight = renderer.getScreenHeight(); FsFile file; @@ -152,6 +199,39 @@ void PxcViewerActivity::onExit() { renderer.displayBuffer(HalDisplay::HALF_REFRESH); } +void PxcViewerActivity::doSetSleepCover() { + GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); + + bool success = false; + FsFile inFile, outFile; + if (Storage.openFileForRead("PXC", filePath, inFile)) { + if (Storage.openFileForWrite("PXC", "/sleep.pxc", outFile)) { + char buffer[2048]; + int bytesRead; + success = true; + while ((bytesRead = inFile.read(buffer, sizeof(buffer))) > 0) { + if (outFile.write(buffer, bytesRead) != bytesRead) { + success = false; + break; + } + } + outFile.close(); + } + inFile.close(); + } + + if (success) { + SETTINGS.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM; + SETTINGS.saveToFile(); + GUI.drawPopup(renderer, tr(STR_DONE)); + } else { + GUI.drawPopup(renderer, tr(STR_FAILED_LOWER)); + } + + delay(1000); + onEnter(); +} + void PxcViewerActivity::loop() { Activity::loop(); @@ -159,4 +239,32 @@ void PxcViewerActivity::loop() { activityManager.goToFileBrowser(filePath); return; } + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + doSetSleepCover(); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Up)) { + if (siblingImages.size() > 1 && currentImageIndex > 0) { + currentImageIndex--; + std::string dirPath = FsHelpers::extractFolderPath(filePath); + if (dirPath.back() != '/') dirPath += "/"; + filePath = dirPath + siblingImages[currentImageIndex]; + onEnter(); + } + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Down)) { + if (siblingImages.size() > 1 && currentImageIndex != -1 && + currentImageIndex < static_cast(siblingImages.size()) - 1) { + currentImageIndex++; + std::string dirPath = FsHelpers::extractFolderPath(filePath); + if (dirPath.back() != '/') dirPath += "/"; + filePath = dirPath + siblingImages[currentImageIndex]; + onEnter(); + } + return; + } } diff --git a/src/activities/util/PxcViewerActivity.h b/src/activities/util/PxcViewerActivity.h index 377a2936b8..c10357ab39 100644 --- a/src/activities/util/PxcViewerActivity.h +++ b/src/activities/util/PxcViewerActivity.h @@ -4,6 +4,7 @@ #include #include +#include #include "../Activity.h" #include "MappedInputManager.h" @@ -19,6 +20,11 @@ class PxcViewerActivity final : public Activity { private: std::string filePath; + std::vector siblingImages; + int currentImageIndex = -1; + + void loadSiblingImages(); + void doSetSleepCover(); void renderGrayscaleImage(); void renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset); }; From 783b3c0c3376ee46f46a0031dc6435bd4bf0cb27 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Tue, 12 May 2026 22:45:02 +0200 Subject: [PATCH 43/93] add 2-bit XTC render quality setting (Speed/Quality) Exposes the SSD1677 factory LUT trade-off for XTC reading: Speed uses lut_factory_fast (default), Quality uses lut_factory_quality. Selectable under Settings -> Reader as "2-bit XTC". Stored as xtcRenderQuality in the JSON settings file. displayXtchPlanes() gains an optional GrayscaleMode parameter (default FactoryFast) and selects the LUT at the displayGrayBuffer call. XtcReaderActivity reads SETTINGS.xtcRenderQuality at the call site to pick FactoryFast or FactoryQuality. --- lib/GfxRenderer/GfxRenderer.cpp | 6 ++++-- lib/GfxRenderer/GfxRenderer.h | 4 +++- lib/I18n/translations/english.yaml | 3 +++ src/CrossPointSettings.h | 2 ++ src/SettingsList.h | 2 ++ src/activities/reader/XtcReaderActivity.cpp | 5 ++++- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index f12b48ff42..17b5fad25a 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1575,7 +1575,8 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) } void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, - const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx) { + const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx, + GrayscaleMode mode) { const size_t colBytes = (pageHeight + 7) / 8; const uint16_t fbStride = panelWidthBytes; @@ -1626,7 +1627,8 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 screenshotHookCtx = nullptr; } - displayGrayBuffer(lut_factory_quality, true); + const unsigned char* lut = (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality : lut_factory_fast; + displayGrayBuffer(lut, true); setRenderMode(BW); } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index e14f61d28c..7820b8c01c 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -229,8 +229,10 @@ class GfxRenderer { // Direct 2-bit XTCH plane blit using factory LUT. Caller supplies the two decoded bit planes // (plane1 = BW RAM / LSB, plane2 = RED RAM / MSB) in column-major order matching XTCH encoding. // Handles pre-flash, both RAM writes, factory LUT fire, and BW controller sync internally. + // mode selects the factory LUT: FactoryFast (default) or FactoryQuality; Differential is invalid here. void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight, - RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr); + RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr, + GrayscaleMode mode = GrayscaleMode::FactoryFast); // 1-bit XTC page via the same grayscale LUT pipeline. Row-major pageBuffer (XTC: 0=black, 1=white). // BW and RED RAM receive identical data since there are no intermediate gray levels. diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 969af61cb5..4e235886ab 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -230,6 +230,9 @@ STR_PREVIEW: "Preview" STR_TITLE: "Title" STR_BATTERY: "Battery" STR_XTC_STATUS_BAR: "XTC Status Bar" +STR_XTC_RENDER_QUALITY: "2-bit XTC" +STR_SPEED: "Speed" +STR_QUALITY: "Quality" STR_BOTTOM: "Bottom" STR_TOP: "Top" STR_UI_THEME: "UI Theme" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index bd6a850209..7751c66672 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -63,6 +63,7 @@ class CrossPointSettings { XTC_STATUS_BAR_TOP = 2, XTC_STATUS_BAR_MODE_COUNT }; + enum XTC_RENDER_QUALITY { XTC_RENDER_FAST = 0, XTC_RENDER_QUALITY_HIGH = 1, XTC_RENDER_QUALITY_COUNT }; enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) @@ -169,6 +170,7 @@ class CrossPointSettings { uint8_t statusBarTitle = CHAPTER_TITLE; uint8_t statusBarBattery = 1; uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE; + uint8_t xtcRenderQuality = XTC_RENDER_FAST; // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; diff --git a/src/SettingsList.h b/src/SettingsList.h index 9af7101eaf..69d0c8d993 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -157,6 +157,8 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering, {StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS}, "imageRendering", StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_XTC_RENDER_QUALITY, &CrossPointSettings::xtcRenderQuality, + {StrId::STR_SPEED, StrId::STR_QUALITY}, "xtcRenderQuality", StrId::STR_CAT_READER), // --- Controls --- SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 254195f739..92649a581a 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -268,8 +268,11 @@ void XtcReaderActivity::renderPage() { renderer.displayBuffer(HalDisplay::FULL_REFRESH); } + const auto xtcGrayMode = SETTINGS.xtcRenderQuality == CrossPointSettings::XTC_RENDER_QUALITY_HIGH + ? GfxRenderer::GrayscaleMode::FactoryQuality + : GfxRenderer::GrayscaleMode::FactoryFast; renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight, - &XtcReaderActivity::renderStatusBarOverlayCallback, this); + &XtcReaderActivity::renderStatusBarOverlayCallback, this, xtcGrayMode); free(plane1); free(plane2); From 4bc9dd2eb1633ee436607d9f639c4d486c7a1b40 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Tue, 12 May 2026 22:45:33 +0200 Subject: [PATCH 44/93] add prev/next labels to BMP/PXC viewers + Left/Right bindings Ports upstream #1852 prev/next arrow labels onto BmpViewer and mirrors the same behavior on PxcViewer for parity with the recently-added sibling navigation. Both viewers now show '<' / '>' next to BACK and SET_SLEEP_COVER when adjacent siblings exist, and accept Left/Right alongside Up/Down for sibling navigation. PxcViewer's renderPxcToFramebuffer() gains hasPrevious/hasNext bools so the interactive onEnter path receives arrow labels while the screenshot path passes false/false (matches BMP screenshot behavior). --- src/activities/util/BmpViewerActivity.cpp | 13 ++++++++++--- src/activities/util/PxcViewerActivity.cpp | 19 +++++++++++++------ src/activities/util/PxcViewerActivity.h | 3 ++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index c131c490d1..fb062fdd6f 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -97,7 +97,12 @@ void BmpViewerActivity::onEnter() { y = (pageHeight - bitmap.getHeight()) / 2; } - const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), "", ""); + bool hasPrevious = (siblingImages.size() > 1 && currentImageIndex > 0); + bool hasNext = (siblingImages.size() > 1 && currentImageIndex != -1 && + currentImageIndex < static_cast(siblingImages.size()) - 1); + + const auto labels = + mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), (hasPrevious ? "<" : ""), (hasNext ? ">" : "")); if (bitmap.hasGreyscale()) { struct BmpGrayCtx { @@ -279,7 +284,8 @@ void BmpViewerActivity::loop() { return; } - if (mappedInput.wasReleased(MappedInputManager::Button::Up)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Left) || + mappedInput.wasReleased(MappedInputManager::Button::Up)) { if (siblingImages.size() > 1 && currentImageIndex > 0) { currentImageIndex--; std::string dirPath = FsHelpers::extractFolderPath(filePath); @@ -290,7 +296,8 @@ void BmpViewerActivity::loop() { return; } - if (mappedInput.wasReleased(MappedInputManager::Button::Down)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Right) || + mappedInput.wasReleased(MappedInputManager::Button::Down)) { if (siblingImages.size() > 1 && currentImageIndex != -1 && currentImageIndex < static_cast(siblingImages.size()) - 1) { currentImageIndex++; diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index d397b0cb88..1d60a502f8 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -104,8 +104,10 @@ void PxcViewerActivity::loadSiblingImages() { } } -void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset) { - const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), "", ""); +void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset, + bool hasPrevious, bool hasNext) { + const auto labels = + mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), (hasPrevious ? "<" : ""), (hasNext ? ">" : "")); PxcCtx ctx{&file, dataOffset, width, height, labels}; renderer.renderGrayscaleSinglePass(GfxRenderer::GrayscaleMode::FactoryQuality, &pxcRenderCallback, &ctx, &pxcLoadingOverlay, nullptr); @@ -154,7 +156,10 @@ void PxcViewerActivity::onEnter() { } const uint32_t dataOffset = file.position(); - renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset); + const bool hasPrevious = (siblingImages.size() > 1 && currentImageIndex > 0); + const bool hasNext = (siblingImages.size() > 1 && currentImageIndex != -1 && + currentImageIndex < static_cast(siblingImages.size()) - 1); + renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset, hasPrevious, hasNext); file.close(); @@ -182,7 +187,7 @@ void PxcViewerActivity::renderGrayscaleImage() { } const uint32_t dataOffset = file.position(); - renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset); + renderPxcToFramebuffer(file, pxcWidth, pxcHeight, dataOffset, false, false); file.close(); } @@ -245,7 +250,8 @@ void PxcViewerActivity::loop() { return; } - if (mappedInput.wasReleased(MappedInputManager::Button::Up)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Left) || + mappedInput.wasReleased(MappedInputManager::Button::Up)) { if (siblingImages.size() > 1 && currentImageIndex > 0) { currentImageIndex--; std::string dirPath = FsHelpers::extractFolderPath(filePath); @@ -256,7 +262,8 @@ void PxcViewerActivity::loop() { return; } - if (mappedInput.wasReleased(MappedInputManager::Button::Down)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Right) || + mappedInput.wasReleased(MappedInputManager::Button::Down)) { if (siblingImages.size() > 1 && currentImageIndex != -1 && currentImageIndex < static_cast(siblingImages.size()) - 1) { currentImageIndex++; diff --git a/src/activities/util/PxcViewerActivity.h b/src/activities/util/PxcViewerActivity.h index c10357ab39..5df19fc509 100644 --- a/src/activities/util/PxcViewerActivity.h +++ b/src/activities/util/PxcViewerActivity.h @@ -26,5 +26,6 @@ class PxcViewerActivity final : public Activity { void loadSiblingImages(); void doSetSleepCover(); void renderGrayscaleImage(); - void renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset); + void renderPxcToFramebuffer(FsFile& file, uint16_t width, uint16_t height, uint32_t dataOffset, bool hasPrevious, + bool hasNext); }; From f22edb4a343d77ae922fe88531d36871f4f33362 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Wed, 13 May 2026 13:48:38 +0200 Subject: [PATCH 45/93] fix: clean sleep and XTC transitions --- src/activities/boot_sleep/SleepActivity.cpp | 21 ++------------------- src/activities/reader/XtcReaderActivity.cpp | 10 ++++++++-- src/activities/reader/XtcReaderActivity.h | 1 + 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index f84b8e28a1..c273ffc5d5 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -13,28 +13,11 @@ #include "CrossPointState.h" #include "Epub/converters/DirectPixelWriter.h" #include "activities/reader/ReaderUtils.h" -#include "components/UITheme.h" #include "fontIds.h" #include "images/Logo120.h" -namespace { -void drawEnteringSleepOverlay(const GfxRenderer& r, const void*) { - constexpr int margin = 15; - const char* msg = tr(STR_ENTERING_SLEEP); - const int y = static_cast(r.getScreenHeight() * 0.075f); - const int textWidth = r.getTextWidth(UI_12_FONT_ID, msg, EpdFontFamily::BOLD); - const int w = textWidth + margin * 2; - const int h = r.getLineHeight(UI_12_FONT_ID) + margin * 2; - const int x = (r.getScreenWidth() - w) / 2; - r.fillRect(x - 2, y - 2, w + 4, h + 4, true); - r.fillRect(x, y, w, h, false); - r.drawText(UI_12_FONT_ID, x + margin, y + margin - 2, msg, true, EpdFontFamily::BOLD); -} -} // namespace - void SleepActivity::onEnter() { Activity::onEnter(); - GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); if (APP_STATE.lastSleepFromReader) { ReaderUtils::applyOrientation(renderer, SETTINGS.orientation); @@ -279,7 +262,7 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { } free(rowBuf); }, - &ctx, &drawEnteringSleepOverlay, nullptr); + &ctx); file.close(); return true; @@ -343,7 +326,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); }, - &grayCtx, &drawEnteringSleepOverlay, nullptr); + &grayCtx); } else { renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 92649a581a..ac8d1d3da9 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -65,6 +65,7 @@ void XtcReaderActivity::loop() { [this](const ActivityResult& result) { if (!result.isCancelled) { currentPage = std::get(result.data).page; + halfRefreshBeforeNextPage = xtc && xtc->getBitDepth() == 2; } }); } @@ -261,8 +262,13 @@ void XtcReaderActivity::renderPage() { return; } - // Periodic FULL_REFRESH resets DC balance; every 32 pages. - if (++pagesSinceClean >= 32) { + if (halfRefreshBeforeNextPage) { + halfRefreshBeforeNextPage = false; + pagesSinceClean = 0; + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } else if (++pagesSinceClean >= 32) { + // Periodic FULL_REFRESH resets DC balance; every 32 pages. pagesSinceClean = 0; renderer.clearScreen(); renderer.displayBuffer(HalDisplay::FULL_REFRESH); diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index af2da44fd1..734d4065e0 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -20,6 +20,7 @@ class XtcReaderActivity final : public Activity { uint32_t currentPage = 0; int pagesUntilFullRefresh = 0; uint32_t pagesSinceClean = 0; + bool halfRefreshBeforeNextPage = false; enum class StatusBarOverlayPosition { Bottom, Top }; struct StatusBarInfo { From 53a86f5929d371b8eb3096ff309bf20680a3bee4 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Wed, 13 May 2026 18:40:03 +0200 Subject: [PATCH 46/93] chore: point SDK to combined grayscale fixes --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index ccfd37be5d..b1c1f01e4c 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit ccfd37be5d0ee1ce8eef1c389db3941b1c1b61b3 +Subproject commit b1c1f01e4c151152d87e4b64d7fbfd958115eecc From f687b036dcfda56dd6f66549c2e418e863297b83 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Wed, 13 May 2026 19:59:58 +0200 Subject: [PATCH 47/93] fix: clean factory grayscale transitions --- lib/GfxRenderer/GfxRenderer.cpp | 16 +++++++++------- lib/hal/HalDisplay.cpp | 2 +- lib/hal/HalDisplay.h | 2 +- open-x4-sdk | 2 +- src/main.cpp | 4 +++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 5f2b930af7..dd21bef310 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1136,11 +1136,13 @@ void GfxRenderer::invertScreen() const { void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { auto elapsed = millis() - start_ms; LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed); - // After a factory LUT render the display already powered down (0xC7 sequence). - // Requesting turnOffScreen=true here would immediately power on then off again, - // adding a full power cycle. Skip the power-down for this one transition. - const bool turnOff = (displayState == DisplayState::FactoryLut) ? false : fadingFix; - display.displayBuffer(refreshMode, turnOff); + // After a factory LUT render, RED RAM still contains the grayscale MSB plane. + // Promote the first normal FAST refresh to HALF so both RAM banks are rebased + // before differential updates resume. + const bool afterFactoryLut = displayState == DisplayState::FactoryLut; + const auto effectiveRefreshMode = + afterFactoryLut && refreshMode == HalDisplay::FAST_REFRESH ? HalDisplay::HALF_REFRESH : refreshMode; + display.displayBuffer(effectiveRefreshMode, fadingFix); displayState = DisplayState::BW; } @@ -1512,8 +1514,8 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx displayGrayBuffer(lut, factoryMode); // Suppress the SDK's automatic grayscaleRevert on the next BW page turn. - // Caller is responsible for cleanup (restoreBwBuffer rebases RED RAM and - // the next FAST_REFRESH drives pixels back to clean BW). + // Caller is responsible for cleanup: restoreBwBuffer rebases RED RAM, and + // displayBuffer promotes the first post-factory FAST refresh to HALF. display.clearGrayscaleModeFlag(); setRenderMode(BW); } diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index 4f1ec764f4..ff479e3cfb 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -66,7 +66,7 @@ void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen); } -void HalDisplay::deepSleep() { einkDisplay.deepSleep(); } +void HalDisplay::deepSleep(const bool powerDownDisplay) { einkDisplay.deepSleep(powerDownDisplay); } uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index b39d6b10e6..9d4f45d320 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -37,7 +37,7 @@ class HalDisplay { void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false); // Power management - void deepSleep(); + void deepSleep(bool powerDownDisplay = true); // Access to frame buffer uint8_t* getFrameBuffer() const; diff --git a/open-x4-sdk b/open-x4-sdk index b1c1f01e4c..9fe173e2cd 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit b1c1f01e4c151152d87e4b64d7fbfd958115eecc +Subproject commit 9fe173e2cd5d646341f15c6419caa4b0cc92a243 diff --git a/src/main.cpp b/src/main.cpp index 4fe44a0035..83382738b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -191,7 +191,9 @@ void enterDeepSleep() { activityManager.goToSleep(); halTiltSensor.deepSleep(); - display.deepSleep(); + const bool preserveFactoryLutSleepScreen = + !gpio.deviceIsX3() && renderer.getDisplayState() == GfxRenderer::DisplayState::FactoryLut; + display.deepSleep(!preserveFactoryLutSleepScreen); LOG_DBG("MAIN", "Entering deep sleep"); powerManager.startDeepSleep(gpio); From 30baf8fb48b707b140459a0b717109cc61f8b83a Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Wed, 13 May 2026 20:13:45 +0200 Subject: [PATCH 48/93] fix: full preflash grayscale sleep screens --- lib/GfxRenderer/GfxRenderer.cpp | 13 ++++++++++--- lib/GfxRenderer/GfxRenderer.h | 12 ++++++++---- src/activities/boot_sleep/SleepActivity.cpp | 11 ++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index dd21bef310..6536cc818b 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1522,11 +1522,12 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), - const void* preFlashCtx) { + const void* preFlashCtx, + const HalDisplay::RefreshMode preFlashRefreshMode) { if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { clearScreen(); if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); - displayBuffer(HalDisplay::HALF_REFRESH); + displayBuffer(preFlashRefreshMode); } const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; @@ -1593,7 +1594,8 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx, - GrayscaleMode mode) { + GrayscaleMode mode, const bool preFlash, + const HalDisplay::RefreshMode preFlashRefreshMode) { const size_t colBytes = (pageHeight + 7) / 8; const uint16_t fbStride = panelWidthBytes; @@ -1609,6 +1611,11 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 return; } + if (preFlash) { + clearScreen(); + displayBuffer(preFlashRefreshMode); + } + // Pass 1: plane1 (MSB) → BW RAM via copyGrayscaleLsbBuffers. clearScreen(0x00); for (uint16_t c = 0; c < pageWidth; c++) { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 7820b8c01c..29ee7257cb 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -221,18 +221,22 @@ class GfxRenderer { const void* preFlashCtx = nullptr); // Single-pass variant: calls renderFn once in GRAY2_LSB mode while simultaneously writing // the MSB plane to a secondary buffer. Cuts SD card reads from 2 to 1 for file-backed renders. - // Falls back to two-pass on secondary buffer allocation failure. + // Falls back to two-pass on secondary buffer allocation failure. Factory modes pre-flash to + // white with HALF_REFRESH by default; sleep screens can request FULL_REFRESH for a stronger + // temperature-aware conditioning pass before the final static image. void renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, - const void* preFlashCtx = nullptr); + const void* preFlashCtx = nullptr, + HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH); // Direct 2-bit XTCH plane blit using factory LUT. Caller supplies the two decoded bit planes // (plane1 = BW RAM / LSB, plane2 = RED RAM / MSB) in column-major order matching XTCH encoding. - // Handles pre-flash, both RAM writes, factory LUT fire, and BW controller sync internally. + // Handles optional pre-flash, both RAM writes, factory LUT fire, and BW controller sync internally. // mode selects the factory LUT: FactoryFast (default) or FactoryQuality; Differential is invalid here. void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight, RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr, - GrayscaleMode mode = GrayscaleMode::FactoryFast); + GrayscaleMode mode = GrayscaleMode::FactoryFast, bool preFlash = false, + HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH); // 1-bit XTC page via the same grayscale LUT pipeline. Row-major pageBuffer (XTC: 0=black, 1=white). // BW and RED RAM receive identical data since there are no intermediate gray levels. diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index c273ffc5d5..7594d06c85 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -262,7 +262,7 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { } free(rowBuf); }, - &ctx); + &ctx, nullptr, nullptr, HalDisplay::FULL_REFRESH); file.close(); return true; @@ -326,7 +326,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); }, - &grayCtx); + &grayCtx, nullptr, nullptr, HalDisplay::FULL_REFRESH); } else { renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); @@ -393,11 +393,8 @@ void SleepActivity::renderCoverSleepScreen() const { } LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); - if (!APP_STATE.lastSleepFromReader) { - renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } - renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight()); + renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight(), nullptr, nullptr, + GfxRenderer::GrayscaleMode::FactoryQuality, true, HalDisplay::FULL_REFRESH); free(plane1); free(plane2); return; From c0a6a2a02372ca1da9c70d186515122dd79219f4 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Thu, 14 May 2026 11:41:29 +0200 Subject: [PATCH 49/93] fix: use SDK factory gray sleep handling --- open-x4-sdk | 2 +- src/main.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/open-x4-sdk b/open-x4-sdk index 9fe173e2cd..af33a42633 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 9fe173e2cd5d646341f15c6419caa4b0cc92a243 +Subproject commit af33a42633404b113405a4be8bc6b56fdc3dfbea diff --git a/src/main.cpp b/src/main.cpp index 83382738b3..4fe44a0035 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -191,9 +191,7 @@ void enterDeepSleep() { activityManager.goToSleep(); halTiltSensor.deepSleep(); - const bool preserveFactoryLutSleepScreen = - !gpio.deviceIsX3() && renderer.getDisplayState() == GfxRenderer::DisplayState::FactoryLut; - display.deepSleep(!preserveFactoryLutSleepScreen); + display.deepSleep(); LOG_DBG("MAIN", "Entering deep sleep"); powerManager.startDeepSleep(gpio); From 38bcba435a708f2a4afe4ef7d530ea8703135160 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Fri, 15 May 2026 22:12:47 +0200 Subject: [PATCH 50/93] fix: stabilize factory gray sleep screens --- lib/GfxRenderer/GfxRenderer.cpp | 24 +++++++++------- lib/GfxRenderer/GfxRenderer.h | 10 ++++--- open-x4-sdk | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 31 +++++++++++++++++++-- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 6536cc818b..d1f7340863 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1522,12 +1522,14 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), - const void* preFlashCtx, - const HalDisplay::RefreshMode preFlashRefreshMode) { - if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { - clearScreen(); - if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); - displayBuffer(preFlashRefreshMode); + const void* preFlashCtx, const HalDisplay::RefreshMode preFlashRefreshMode, + const uint8_t preFlashPasses) { + if ((mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) && preFlashPasses > 0) { + for (uint8_t pass = 0; pass < preFlashPasses; pass++) { + clearScreen(); + if (pass == 0 && preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); + displayBuffer(preFlashRefreshMode); + } } const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; @@ -1595,7 +1597,7 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx, GrayscaleMode mode, const bool preFlash, - const HalDisplay::RefreshMode preFlashRefreshMode) { + const HalDisplay::RefreshMode preFlashRefreshMode, const uint8_t preFlashPasses) { const size_t colBytes = (pageHeight + 7) / 8; const uint16_t fbStride = panelWidthBytes; @@ -1611,9 +1613,11 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 return; } - if (preFlash) { - clearScreen(); - displayBuffer(preFlashRefreshMode); + if (preFlash && preFlashPasses > 0) { + for (uint8_t pass = 0; pass < preFlashPasses; pass++) { + clearScreen(); + displayBuffer(preFlashRefreshMode); + } } // Pass 1: plane1 (MSB) → BW RAM via copyGrayscaleLsbBuffers. diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 29ee7257cb..9ba80751f6 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -222,12 +222,13 @@ class GfxRenderer { // Single-pass variant: calls renderFn once in GRAY2_LSB mode while simultaneously writing // the MSB plane to a secondary buffer. Cuts SD card reads from 2 to 1 for file-backed renders. // Falls back to two-pass on secondary buffer allocation failure. Factory modes pre-flash to - // white with HALF_REFRESH by default; sleep screens can request FULL_REFRESH for a stronger - // temperature-aware conditioning pass before the final static image. + // white with HALF_REFRESH by default. preFlashPasses=0 skips the internal pre-flash so + // callers can run a custom conditioning sequence first. void renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, const void* preFlashCtx = nullptr, - HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH); + HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH, + uint8_t preFlashPasses = 1); // Direct 2-bit XTCH plane blit using factory LUT. Caller supplies the two decoded bit planes // (plane1 = BW RAM / LSB, plane2 = RED RAM / MSB) in column-major order matching XTCH encoding. @@ -236,7 +237,8 @@ class GfxRenderer { void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight, RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr, GrayscaleMode mode = GrayscaleMode::FactoryFast, bool preFlash = false, - HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH); + HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH, + uint8_t preFlashPasses = 1); // 1-bit XTC page via the same grayscale LUT pipeline. Row-major pageBuffer (XTC: 0=black, 1=white). // BW and RED RAM receive identical data since there are no intermediate gray levels. diff --git a/open-x4-sdk b/open-x4-sdk index af33a42633..d3275d2d96 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit af33a42633404b113405a4be8bc6b56fdc3dfbea +Subproject commit d3275d2d961531161bf08bb9c1d6f78708688aa7 diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 7594d06c85..0fcf6222ba 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -16,6 +16,27 @@ #include "fontIds.h" #include "images/Logo120.h" +namespace { +constexpr uint8_t SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES = 0; + +struct FactorySleepPreconditionPass { + uint8_t color; + HalDisplay::RefreshMode refreshMode; +}; + +constexpr FactorySleepPreconditionPass FACTORY_SLEEP_PRECONDITION[] = { + {0x00, HalDisplay::FULL_REFRESH}, + {0xFF, HalDisplay::FULL_REFRESH}, +}; + +void runFactorySleepPrecondition(const GfxRenderer& renderer) { + for (const auto& pass : FACTORY_SLEEP_PRECONDITION) { + renderer.clearScreen(pass.color); + renderer.displayBuffer(pass.refreshMode); + } +} +} + void SleepActivity::onEnter() { Activity::onEnter(); @@ -236,6 +257,7 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { }; PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; + runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { @@ -262,7 +284,7 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { } free(rowBuf); }, - &ctx, nullptr, nullptr, HalDisplay::FULL_REFRESH); + &ctx, nullptr, nullptr, HalDisplay::FULL_REFRESH, SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES); file.close(); return true; @@ -320,13 +342,14 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { float cropX, cropY; }; BitmapGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, cropX, cropY}; + runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( GfxRenderer::GrayscaleMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); }, - &grayCtx, nullptr, nullptr, HalDisplay::FULL_REFRESH); + &grayCtx, nullptr, nullptr, HalDisplay::FULL_REFRESH, SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES); } else { renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); @@ -393,8 +416,10 @@ void SleepActivity::renderCoverSleepScreen() const { } LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); + runFactorySleepPrecondition(renderer); renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight(), nullptr, nullptr, - GfxRenderer::GrayscaleMode::FactoryQuality, true, HalDisplay::FULL_REFRESH); + GfxRenderer::GrayscaleMode::FactoryQuality, false, HalDisplay::FULL_REFRESH, + SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES); free(plane1); free(plane2); return; From 9266211fcd4b5eeb77e0c972052282d6d16ab077 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sat, 16 May 2026 23:16:30 +0200 Subject: [PATCH 51/93] refactor: GrayscaleDriveMode + stock-V5.5.9 factory experiments Refactor: - Rename GfxRenderer::GrayscaleMode -> GrayscaleDriveMode to separate caller intent (which LUT, fast vs quality vs differential) from low-level panel plumbing (RenderMode values, lut pointer, factoryMode flag, g_differentialQuantize). - Add file-private GrayscaleDriveSpec + resolveGrayscaleDrive() in GfxRenderer.cpp. Replaces four duplicated ternary clusters across renderGrayscale, renderGrayscaleSinglePass (main + malloc-failed fallback), and displayXtchPlanes. - Tidy SleepActivity displayXtchPlanes call: drop two inert args (preFlash is false, so passes count and refresh mode were unreachable). Add a one-line comment pointing to the external precondition. Stock-firmware experiments (PXC sleep ghost investigation, see docs/v559-disassembly-findings.md): - renderGrayscaleSinglePass factory path now uses the split SDK API (displayGrayBufferFactorySetup + RAM writes + displayGrayBufferFactoryActivate) to match stock's SPI order: LUT load -> Border Waveform -> RAM writes -> CTRL1/CTRL2/MASTER_ACTIVATION. - Precondition (runFactorySleepPrecondition) commented out in all three SleepActivity render paths (PXC, BMP grayscale, XTCH planes) to test whether the precondition's post-FULL_REFRESH RED RAM sync was contributing. - Bump open-x4-sdk pointer to pick up the matching SDK experiments (FACTORY_GRAY CTRL2 = 0xCC, Border Waveform per render, BOOSTER and BORDER init bytes matching stock, deepSleep factory branch early-return, factory LUT voltages set to stock's byte-exact 0x00/0x00/0x01/0x22/0x22 sequence). --- lib/GfxRenderer/GfxRenderer.cpp | 101 ++++++++++++------- lib/GfxRenderer/GfxRenderer.h | 19 ++-- lib/hal/HalDisplay.cpp | 6 ++ lib/hal/HalDisplay.h | 3 + open-x4-sdk | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 18 ++-- src/activities/reader/EpubReaderActivity.cpp | 6 +- src/activities/reader/XtcReaderActivity.cpp | 4 +- src/activities/util/BmpViewerActivity.cpp | 4 +- src/activities/util/PxcViewerActivity.cpp | 2 +- 10 files changed, 102 insertions(+), 63 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index d1f7340863..58cfe6dad8 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -23,6 +23,28 @@ const char* resolveVisualText(const char* text, std::string& visualBuffer, int p uint8_t resolveSdCardStyle(const SdCardFont& font, const EpdFontFamily::Style style) { return font.resolveStyle(static_cast(style)); } + +// Bundles every panel-level value derived from a caller-facing GrayscaleDriveMode, +// so the three grayscale entry points share one mapping instead of repeating ternaries. +struct GrayscaleDriveSpec { + GfxRenderer::RenderMode lsbMode; + GfxRenderer::RenderMode msbMode; + const unsigned char* lut; // nullptr for Differential + bool factoryMode; + bool differentialQuantize; +}; + +GrayscaleDriveSpec resolveGrayscaleDrive(GfxRenderer::GrayscaleDriveMode mode) { + switch (mode) { + case GfxRenderer::GrayscaleDriveMode::Differential: + return {GfxRenderer::GRAYSCALE_LSB, GfxRenderer::GRAYSCALE_MSB, nullptr, false, true}; + case GfxRenderer::GrayscaleDriveMode::FactoryFast: + return {GfxRenderer::GRAY2_LSB, GfxRenderer::GRAY2_MSB, lut_factory_fast, true, false}; + case GfxRenderer::GrayscaleDriveMode::FactoryQuality: + return {GfxRenderer::GRAY2_LSB, GfxRenderer::GRAY2_MSB, lut_factory_quality, true, false}; + } + return {GfxRenderer::GRAYSCALE_LSB, GfxRenderer::GRAYSCALE_MSB, nullptr, false, true}; +} } // namespace const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const { @@ -1459,30 +1481,25 @@ void GfxRenderer::copyGrayscaleLsbBuffers() const { display.copyGrayscaleLsbBuff void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(frameBuffer); } -void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), +void GfxRenderer::renderGrayscale(GrayscaleDriveMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), const void* preFlashCtx) { - if (mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) { + const auto spec = resolveGrayscaleDrive(mode); + + if (spec.factoryMode) { clearScreen(); if (preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); displayBuffer(HalDisplay::HALF_REFRESH); } - const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; - const RenderMode msbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_MSB : GRAY2_MSB; - const bool factoryMode = (mode != GrayscaleMode::Differential); - const unsigned char* lut = (mode == GrayscaleMode::FactoryFast) ? lut_factory_fast - : (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality - : nullptr; - - g_differentialQuantize = (mode == GrayscaleMode::Differential); + g_differentialQuantize = spec.differentialQuantize; clearScreen(0x00); - setRenderMode(lsbMode); + setRenderMode(spec.lsbMode); renderFn(*this, ctx); uint8_t* lsbCopy = nullptr; - if (screenshotHook && factoryMode) { + if (screenshotHook && spec.factoryMode) { lsbCopy = static_cast(malloc(frameBufferSize)); if (lsbCopy) { memcpy(lsbCopy, frameBuffer, frameBufferSize); @@ -1495,12 +1512,12 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx copyGrayscaleLsbBuffers(); clearScreen(0x00); - setRenderMode(msbMode); + setRenderMode(spec.msbMode); renderFn(*this, ctx); copyGrayscaleMsbBuffers(); // Fire hook: LSB = lsbCopy, MSB = frameBuffer (still holds second-pass data). - if (screenshotHook && factoryMode && lsbCopy) { + if (screenshotHook && spec.factoryMode && lsbCopy) { screenshotHook(lsbCopy, frameBuffer, panelWidth, panelHeight, screenshotHookCtx); screenshotHook = nullptr; screenshotHookCtx = nullptr; @@ -1512,7 +1529,7 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx g_differentialQuantize = false; - displayGrayBuffer(lut, factoryMode); + displayGrayBuffer(spec.lut, spec.factoryMode); // Suppress the SDK's automatic grayscaleRevert on the next BW page turn. // Caller is responsible for cleanup: restoreBwBuffer rebases RED RAM, and // displayBuffer promotes the first post-factory FAST refresh to HALF. @@ -1520,11 +1537,13 @@ void GfxRenderer::renderGrayscale(GrayscaleMode mode, void (*renderFn)(const Gfx setRenderMode(BW); } -void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), +void GfxRenderer::renderGrayscaleSinglePass(GrayscaleDriveMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*), const void* preFlashCtx, const HalDisplay::RefreshMode preFlashRefreshMode, const uint8_t preFlashPasses) { - if ((mode == GrayscaleMode::FactoryFast || mode == GrayscaleMode::FactoryQuality) && preFlashPasses > 0) { + const auto spec = resolveGrayscaleDrive(mode); + + if (spec.factoryMode && preFlashPasses > 0) { for (uint8_t pass = 0; pass < preFlashPasses; pass++) { clearScreen(); if (pass == 0 && preFlashOverlayFn) preFlashOverlayFn(*this, preFlashCtx); @@ -1532,13 +1551,7 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) } } - const RenderMode lsbMode = (mode == GrayscaleMode::Differential) ? GRAYSCALE_LSB : GRAY2_LSB; - const bool factoryMode = (mode != GrayscaleMode::Differential); - const unsigned char* lut = (mode == GrayscaleMode::FactoryFast) ? lut_factory_fast - : (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality - : nullptr; - - g_differentialQuantize = (mode == GrayscaleMode::Differential); + g_differentialQuantize = spec.differentialQuantize; auto* secBuf = static_cast(malloc(frameBufferSize)); if (!secBuf) { @@ -1547,15 +1560,15 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) screenshotHook = nullptr; screenshotHookCtx = nullptr; clearScreen(0x00); - setRenderMode(lsbMode); + setRenderMode(spec.lsbMode); renderFn(*this, ctx); copyGrayscaleLsbBuffers(); clearScreen(0x00); - setRenderMode(mode == GrayscaleMode::Differential ? GRAYSCALE_MSB : GRAY2_MSB); + setRenderMode(spec.msbMode); renderFn(*this, ctx); copyGrayscaleMsbBuffers(); g_differentialQuantize = false; - displayGrayBuffer(lut, factoryMode); + displayGrayBuffer(spec.lut, spec.factoryMode); // See note in renderGrayscale(). display.clearGrayscaleModeFlag(); setRenderMode(BW); @@ -1566,21 +1579,36 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) // Single pass: renderFn writes LSB plane to frameBuffer and MSB plane to secondaryFrameBuffer. clearScreen(0x00); - setRenderMode(lsbMode); + setRenderMode(spec.lsbMode); renderFn(*this, ctx); // One-shot screenshot hook: fired while both planes are still in software, before either is // pushed to the controller. frameBuffer = LSB plane, secBuf = MSB plane. - if (screenshotHook && factoryMode) { + if (screenshotHook && spec.factoryMode) { screenshotHook(frameBuffer, secBuf, panelWidth, panelHeight, screenshotHookCtx); screenshotHook = nullptr; screenshotHookCtx = nullptr; } - // Push LSB plane (frameBuffer) → BW RAM. - copyGrayscaleLsbBuffers(); + // EXPERIMENT: match stock V5.5.9 SPI order: LUT load → RAM writes → activate. + // For factory mode, use the split SDK API so RAM data is written AFTER setCustomLUT + // and Border Waveform, BEFORE the MASTER_ACTIVATION. See docs/v559-disassembly-findings.md. + if (spec.factoryMode) { + display.displayGrayBufferFactorySetup(spec.lut); + copyGrayscaleLsbBuffers(); + memcpy(frameBuffer, secBuf, frameBufferSize); + copyGrayscaleMsbBuffers(); + free(secBuf); + secondaryFrameBuffer = nullptr; + g_differentialQuantize = false; + display.displayGrayBufferFactoryActivate(); + display.clearGrayscaleModeFlag(); + setRenderMode(BW); + return; + } - // Push MSB plane (secondaryFrameBuffer → frameBuffer → RED RAM). + // Differential path: original order (RAM writes then combined displayGrayBuffer). + copyGrayscaleLsbBuffers(); memcpy(frameBuffer, secBuf, frameBufferSize); copyGrayscaleMsbBuffers(); @@ -1588,15 +1616,14 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn) secondaryFrameBuffer = nullptr; g_differentialQuantize = false; - displayGrayBuffer(lut, factoryMode); - // See note in renderGrayscale(). + displayGrayBuffer(spec.lut, spec.factoryMode); display.clearGrayscaleModeFlag(); setRenderMode(BW); } void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, const uint16_t pageWidth, const uint16_t pageHeight, RenderHook overlayFn, const void* overlayCtx, - GrayscaleMode mode, const bool preFlash, + GrayscaleDriveMode mode, const bool preFlash, const HalDisplay::RefreshMode preFlashRefreshMode, const uint8_t preFlashPasses) { const size_t colBytes = (pageHeight + 7) / 8; const uint16_t fbStride = panelWidthBytes; @@ -1655,8 +1682,8 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 screenshotHookCtx = nullptr; } - const unsigned char* lut = (mode == GrayscaleMode::FactoryQuality) ? lut_factory_quality : lut_factory_fast; - displayGrayBuffer(lut, true); + const auto spec = resolveGrayscaleDrive(mode); + displayGrayBuffer(spec.lut, true); setRenderMode(BW); } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 9ba80751f6..01d480bfb4 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -35,11 +35,12 @@ class GfxRenderer { LandscapeCounterClockwise // 800x480 logical coordinates, native panel orientation }; - // Selects LUT, pixel-plane encoding, and pre-flash behavior for renderGrayscale(). - enum class GrayscaleMode { - FactoryFast, // Factory absolute 2-bit (lut_factory_fast); HALF_REFRESH pre-flash to white - FactoryQuality, // Factory absolute 2-bit (lut_factory_quality); HALF_REFRESH pre-flash to white - Differential, // Differential 2-bit overlay (no LUT); no pre-flash, requires prior BW state + // Selects LUT and pixel-plane encoding for renderGrayscale() / renderGrayscaleSinglePass() / + // displayXtchPlanes(). Pre-flash policy is per-call, not part of the drive mode. + enum class GrayscaleDriveMode { + FactoryFast, // Factory absolute 2-bit (lut_factory_fast) + FactoryQuality, // Factory absolute 2-bit (lut_factory_quality) + Differential, // Differential 2-bit overlay (no LUT); requires prior BW state }; // Display state — tracks whether the physical display was last updated via a factory LUT render. @@ -216,7 +217,7 @@ class GfxRenderer { // storeBwBuffer / restoreBwBuffer remain the caller's responsibility. // preFlashOverlayFn (optional): called after clearScreen() but before the pre-flash displayBuffer(), // allowing callers to draw a loading indicator that appears during the pre-flash without an extra refresh. - void renderGrayscale(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, + void renderGrayscale(GrayscaleDriveMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, const void* preFlashCtx = nullptr); // Single-pass variant: calls renderFn once in GRAY2_LSB mode while simultaneously writing @@ -224,8 +225,8 @@ class GfxRenderer { // Falls back to two-pass on secondary buffer allocation failure. Factory modes pre-flash to // white with HALF_REFRESH by default. preFlashPasses=0 skips the internal pre-flash so // callers can run a custom conditioning sequence first. - void renderGrayscaleSinglePass(GrayscaleMode mode, void (*renderFn)(const GfxRenderer&, const void*), const void* ctx, - void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, + void renderGrayscaleSinglePass(GrayscaleDriveMode mode, void (*renderFn)(const GfxRenderer&, const void*), + const void* ctx, void (*preFlashOverlayFn)(const GfxRenderer&, const void*) = nullptr, const void* preFlashCtx = nullptr, HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH, uint8_t preFlashPasses = 1); @@ -236,7 +237,7 @@ class GfxRenderer { // mode selects the factory LUT: FactoryFast (default) or FactoryQuality; Differential is invalid here. void displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2, uint16_t pageWidth, uint16_t pageHeight, RenderHook overlayFn = nullptr, const void* overlayCtx = nullptr, - GrayscaleMode mode = GrayscaleMode::FactoryFast, bool preFlash = false, + GrayscaleDriveMode mode = GrayscaleDriveMode::FactoryFast, bool preFlash = false, HalDisplay::RefreshMode preFlashRefreshMode = HalDisplay::HALF_REFRESH, uint8_t preFlashPasses = 1); diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index ff479e3cfb..1f6979de7f 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -84,6 +84,12 @@ void HalDisplay::displayGrayBuffer(bool turnOffScreen, const unsigned char* lut, einkDisplay.displayGrayBuffer(turnOffScreen, lut, factoryMode); } +void HalDisplay::displayGrayBufferFactorySetup(const unsigned char* lut) { + einkDisplay.displayGrayBufferFactorySetup(lut); +} + +void HalDisplay::displayGrayBufferFactoryActivate() { einkDisplay.displayGrayBufferFactoryActivate(); } + uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); } uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index 9d4f45d320..fc88189dd0 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -48,6 +48,9 @@ class HalDisplay { void cleanupGrayscaleBuffers(const uint8_t* bwBuffer); void displayGrayBuffer(bool turnOffScreen = false, const unsigned char* lut = nullptr, bool factoryMode = false); + // Two-phase factory grayscale render — see EInkDisplay.h. + void displayGrayBufferFactorySetup(const unsigned char* lut); + void displayGrayBufferFactoryActivate(); // Tell the SDK that grayscale state has been cleaned up by the consumer // (RAM banks rebased + a follow-up FAST_REFRESH will handle pixel cleanup), diff --git a/open-x4-sdk b/open-x4-sdk index d3275d2d96..e59ef3a1a5 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit d3275d2d961531161bf08bb9c1d6f78708688aa7 +Subproject commit e59ef3a1a5be6d3beaa93d48995f6896e76e5fd8 diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 0fcf6222ba..fe889700e4 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -35,7 +35,7 @@ void runFactorySleepPrecondition(const GfxRenderer& renderer) { renderer.displayBuffer(pass.refreshMode); } } -} +} // namespace void SleepActivity::onEnter() { Activity::onEnter(); @@ -257,9 +257,10 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { }; PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; - runFactorySleepPrecondition(renderer); + // EXPERIMENT: precondition disabled to test ghost hypothesis. See docs/v559-disassembly-findings.md. + // runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); c->file->seek(c->dataOffset); @@ -342,9 +343,10 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { float cropX, cropY; }; BitmapGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, cropX, cropY}; - runFactorySleepPrecondition(renderer); + // EXPERIMENT: precondition disabled to test ghost hypothesis. + // runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, c->cropX, c->cropY); @@ -416,10 +418,10 @@ void SleepActivity::renderCoverSleepScreen() const { } LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); - runFactorySleepPrecondition(renderer); + // EXPERIMENT: precondition disabled to test ghost hypothesis. + // runFactorySleepPrecondition(renderer); renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight(), nullptr, nullptr, - GfxRenderer::GrayscaleMode::FactoryQuality, false, HalDisplay::FULL_REFRESH, - SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES); + GfxRenderer::GrayscaleDriveMode::FactoryQuality, false); free(plane1); free(plane2); return; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 3349e701fa..d010270527 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -813,8 +813,8 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or PageRenderCtx grayCtx{page.get(), SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop, this}; const auto tGrayStart = millis(); - const auto grayMode = - useFactoryGray ? GfxRenderer::GrayscaleMode::FactoryQuality : GfxRenderer::GrayscaleMode::Differential; + const auto grayMode = useFactoryGray ? GfxRenderer::GrayscaleDriveMode::FactoryQuality + : GfxRenderer::GrayscaleDriveMode::Differential; renderer.renderGrayscale(grayMode, &renderPageCallback, &grayCtx); const auto tGrayEnd = millis(); fcm->logStats(useFactoryGray ? "gray_factory_quality" : "gray"); @@ -856,7 +856,7 @@ void EpubReaderActivity::onScreenshotRequest() { if (!renderer.storeBwBuffer()) return; PageRenderCtx grayCtx{p.get(), SETTINGS.getReaderFontId(), lastFactoryMarginLeft, lastFactoryMarginTop, this}; - renderer.renderGrayscale(GfxRenderer::GrayscaleMode::FactoryQuality, &renderPageCallback, &grayCtx); + renderer.renderGrayscale(GfxRenderer::GrayscaleDriveMode::FactoryQuality, &renderPageCallback, &grayCtx); renderer.restoreBwBuffer(); } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index ac8d1d3da9..0c0a4e1331 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -275,8 +275,8 @@ void XtcReaderActivity::renderPage() { } const auto xtcGrayMode = SETTINGS.xtcRenderQuality == CrossPointSettings::XTC_RENDER_QUALITY_HIGH - ? GfxRenderer::GrayscaleMode::FactoryQuality - : GfxRenderer::GrayscaleMode::FactoryFast; + ? GfxRenderer::GrayscaleDriveMode::FactoryQuality + : GfxRenderer::GrayscaleDriveMode::FactoryFast; renderer.displayXtchPlanes(plane1, plane2, pageWidth, pageHeight, &XtcReaderActivity::renderStatusBarOverlayCallback, this, xtcGrayMode); free(plane1); diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index 8726183a76..29d37d98b6 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -113,7 +113,7 @@ void BmpViewerActivity::onEnter() { }; BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); @@ -201,7 +201,7 @@ void BmpViewerActivity::renderGrayscaleImage() { BmpGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, labels}; renderer.renderGrayscaleSinglePass( - GfxRenderer::GrayscaleMode::FactoryQuality, + GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { const auto* c = static_cast(raw); r.drawBitmap(*c->bitmap, c->x, c->y, c->maxWidth, c->maxHeight, 0, 0); diff --git a/src/activities/util/PxcViewerActivity.cpp b/src/activities/util/PxcViewerActivity.cpp index 1d60a502f8..e3ba8cf340 100644 --- a/src/activities/util/PxcViewerActivity.cpp +++ b/src/activities/util/PxcViewerActivity.cpp @@ -109,7 +109,7 @@ void PxcViewerActivity::renderPxcToFramebuffer(FsFile& file, uint16_t width, uin const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), (hasPrevious ? "<" : ""), (hasNext ? ">" : "")); PxcCtx ctx{&file, dataOffset, width, height, labels}; - renderer.renderGrayscaleSinglePass(GfxRenderer::GrayscaleMode::FactoryQuality, &pxcRenderCallback, &ctx, + renderer.renderGrayscaleSinglePass(GfxRenderer::GrayscaleDriveMode::FactoryQuality, &pxcRenderCallback, &ctx, &pxcLoadingOverlay, nullptr); } From 400b0bc660d6f17fbfc244eec81ec9f12b47272d Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 13:15:49 +0200 Subject: [PATCH 52/93] chore: bump open-x4-sdk to 4e949dc (VCOM restore in factory Activate) Picks up the fix for displayGrayBufferFactoryActivate not restoring VCOM after a non-default-VCOM LUT load. See SDK commit 4e949dc. --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index e59ef3a1a5..4e949dc5d5 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit e59ef3a1a5be6d3beaa93d48995f6896e76e5fd8 +Subproject commit 4e949dc5d5f8ad4d57bb5c8ff13e1e1c36db559b From 40bd133240cfcde736c43c09aff983b51178ad61 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 13:23:25 +0200 Subject: [PATCH 53/93] chore: bump open-x4-sdk to 7c8afd0 (revert VCOM restore for stock-match) Picks up the revert of the VCOM restore in displayGrayBufferFactoryActivate. Preserves byte-exact stock V5.5.9 SPI sequence for the PXC sleep ghost experiment. See SDK commit 7c8afd0. --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index 4e949dc5d5..7c8afd08c9 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 4e949dc5d5f8ad4d57bb5c8ff13e1e1c36db559b +Subproject commit 7c8afd08c9e4dbacaaca04e05a9c6f12cae3b454 From ba725495efad4cdef500a0b6e4d6c6a9ddc77c8b Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 13:50:45 +0200 Subject: [PATCH 54/93] feat: re-enable precondition with stock-match (CTRL2=0xF7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-enable runFactorySleepPrecondition in all three SleepActivity render paths (PXC, BMP grayscale, XTCH planes). The disable was an experiment that didn't move the ghost; restoring it to test against stock's actual pre-LUT sequence. Switch the precondition itself from renderer.clearScreen + renderer.displayBuffer(FULL_REFRESH) to renderer.displayBufferPrecondition() — a new HAL/SDK method that fires CTRL2 = 0xF7 (full power cycle, matches stock V5.5.9) and skips the SINGLE_BUFFER_MODE post-RED-sync. Wrappers added to HalDisplay and GfxRenderer. Bump open-x4-sdk pointer to 4982527 which contains the new EInkDisplay::displayBufferPrecondition implementation. See docs/v559-disassembly-findings.md for the full byte-match rationale. --- lib/GfxRenderer/GfxRenderer.cpp | 5 ++++ lib/GfxRenderer/GfxRenderer.h | 4 +++ lib/hal/HalDisplay.cpp | 2 ++ lib/hal/HalDisplay.h | 2 ++ open-x4-sdk | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 29 ++++++++------------- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 58cfe6dad8..70b29eb923 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1168,6 +1168,11 @@ void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const displayState = DisplayState::BW; } +void GfxRenderer::displayBufferPrecondition(uint8_t color) const { + display.displayBufferPrecondition(color); + displayState = DisplayState::BW; +} + void GfxRenderer::displayGrayBuffer(const unsigned char* lut, const bool factoryMode) const { display.displayGrayBuffer(fadingFix, lut, factoryMode); if (factoryMode) { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 01d480bfb4..c455deb440 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -140,6 +140,10 @@ class GfxRenderer { int getScreenWidth() const; int getScreenHeight() const; void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const; + // Stock-V5.5.9 byte-match precondition flash (black or white). Fires CTRL2=0xF7 + // (full power-cycle, rails off after) and skips SINGLE_BUFFER_MODE post-RED-sync. + // See docs/v559-disassembly-findings.md. + void displayBufferPrecondition(uint8_t color) const; // EXPERIMENTAL: Windowed update - display only a rectangular region // void displayWindow(int x, int y, int width, int height) const; void invertScreen() const; diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index 1f6979de7f..d176c3de02 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -90,6 +90,8 @@ void HalDisplay::displayGrayBufferFactorySetup(const unsigned char* lut) { void HalDisplay::displayGrayBufferFactoryActivate() { einkDisplay.displayGrayBufferFactoryActivate(); } +void HalDisplay::displayBufferPrecondition(uint8_t color) { einkDisplay.displayBufferPrecondition(color); } + uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); } uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index fc88189dd0..fc8cee8c29 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -51,6 +51,8 @@ class HalDisplay { // Two-phase factory grayscale render — see EInkDisplay.h. void displayGrayBufferFactorySetup(const unsigned char* lut); void displayGrayBufferFactoryActivate(); + // Stock-V5.5.9 byte-match precondition (black/white full power-cycle flash). + void displayBufferPrecondition(uint8_t color); // Tell the SDK that grayscale state has been cleaned up by the consumer // (RAM banks rebased + a follow-up FAST_REFRESH will handle pixel cleanup), diff --git a/open-x4-sdk b/open-x4-sdk index 7c8afd08c9..4982527a3c 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 7c8afd08c9e4dbacaaca04e05a9c6f12cae3b454 +Subproject commit 4982527a3c88a06e36ca7ce084fdfca6ef79bea5 diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index fe889700e4..877eaf1e5c 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -19,20 +19,16 @@ namespace { constexpr uint8_t SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES = 0; -struct FactorySleepPreconditionPass { - uint8_t color; - HalDisplay::RefreshMode refreshMode; -}; - -constexpr FactorySleepPreconditionPass FACTORY_SLEEP_PRECONDITION[] = { - {0x00, HalDisplay::FULL_REFRESH}, - {0xFF, HalDisplay::FULL_REFRESH}, -}; +// Stock V5.5.9 byte-match: black flash then white flash, each via the new +// EInkDisplay::displayBufferPrecondition() path which fires CTRL2=0xF7 (full +// power cycle) and skips the SINGLE_BUFFER_MODE post-RED-sync. Matches stock's +// precondition function at firmware addr 0x42010096 — see +// docs/v559-disassembly-findings.md. +constexpr uint8_t FACTORY_SLEEP_PRECONDITION_COLORS[] = {0x00, 0xFF}; void runFactorySleepPrecondition(const GfxRenderer& renderer) { - for (const auto& pass : FACTORY_SLEEP_PRECONDITION) { - renderer.clearScreen(pass.color); - renderer.displayBuffer(pass.refreshMode); + for (const uint8_t color : FACTORY_SLEEP_PRECONDITION_COLORS) { + renderer.displayBufferPrecondition(color); } } } // namespace @@ -257,8 +253,7 @@ bool SleepActivity::renderPxcSleepScreen(const std::string& path) const { }; PxcCtx ctx{&file, dataOffset, pxcWidth, pxcHeight}; - // EXPERIMENT: precondition disabled to test ghost hypothesis. See docs/v559-disassembly-findings.md. - // runFactorySleepPrecondition(renderer); + runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { @@ -343,8 +338,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { float cropX, cropY; }; BitmapGrayCtx grayCtx{&bitmap, x, y, pageWidth, pageHeight, cropX, cropY}; - // EXPERIMENT: precondition disabled to test ghost hypothesis. - // runFactorySleepPrecondition(renderer); + runFactorySleepPrecondition(renderer); renderer.renderGrayscaleSinglePass( GfxRenderer::GrayscaleDriveMode::FactoryQuality, [](const GfxRenderer& r, const void* raw) { @@ -418,8 +412,7 @@ void SleepActivity::renderCoverSleepScreen() const { } LOG_DBG("SLP", "Direct XTCH plane render: %ux%u", lastXtc.getPageWidth(), lastXtc.getPageHeight()); - // EXPERIMENT: precondition disabled to test ghost hypothesis. - // runFactorySleepPrecondition(renderer); + runFactorySleepPrecondition(renderer); renderer.displayXtchPlanes(plane1, plane2, lastXtc.getPageWidth(), lastXtc.getPageHeight(), nullptr, nullptr, GfxRenderer::GrayscaleDriveMode::FactoryQuality, false); free(plane1); From f024256c62a207ec58be9fc8a9db3d364a4b8c2b Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 20:55:40 +0200 Subject: [PATCH 55/93] chore: bump open-x4-sdk to b4b9d39 (revert byte-exact LUT experiment) The stock byte-exact lut_factory_quality swap caused all-white renders on device. Reverted in SDK; bumping pointer to match. The "OTP-preserve for 0x00 voltages" hypothesis from Difference #8 is falsified by this empirical result. See docs/v559-disassembly-findings.md. --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index 4982527a3c..b4b9d39190 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 4982527a3c88a06e36ca7ce084fdfca6ef79bea5 +Subproject commit b4b9d391908f5afdcc74db267cc371f1fb9e2991 From 57b56c6cb8e25325b4bb5116b2029cd6932461d0 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 21:48:49 +0200 Subject: [PATCH 56/93] feat: extend stock-match LUT-before-RAM order to all factory paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the split SDK API (displayGrayBufferFactorySetup + displayGrayBufferFactoryActivate, matching stock V5.5.9's LUT-load-before-RAM-writes SPI order — Difference #4) was applied only to renderGrayscaleSinglePass. PxcViewer and BmpViewer use that singlepass path so they got the patch implicitly, but the other factory render callers did not: - renderGrayscale (two-pass) → used by EpubReaderActivity for inline grayscale images - displayXtchPlanes → used by XtcReaderActivity and SleepActivity's XTCH cover path Refactor both to use the split SDK API for factory mode: - renderGrayscale: insert displayGrayBufferFactorySetup before the first copyGrayscaleLsbBuffers, replace the trailing displayGrayBuffer with displayGrayBufferFactoryActivate. Differential mode keeps the combined displayGrayBuffer (no change). - displayXtchPlanes: resolve drive spec early, call Setup before the plane1 RAM write, call Activate after the plane2 RAM write. Net result: all 6 factory render call sites (PXC viewer, BMP viewer, EPUB inline images, XTC reader, sleep XTCH path, sleep PXC/BMP path) now follow the stock SPI order. See docs/v559-disassembly-findings.md Difference #4. --- lib/GfxRenderer/GfxRenderer.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 70b29eb923..dfd4a339e9 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1514,6 +1514,13 @@ void GfxRenderer::renderGrayscale(GrayscaleDriveMode mode, void (*renderFn)(cons screenshotHookCtx = nullptr; } } + + // Match stock SPI order for factory mode: setCustomLUT + Border BEFORE the + // RAM writes. Differential path skips Setup (handled by displayGrayBuffer at + // the end). See docs/v559-disassembly-findings.md Difference #4. + if (spec.factoryMode) { + display.displayGrayBufferFactorySetup(spec.lut); + } copyGrayscaleLsbBuffers(); clearScreen(0x00); @@ -1534,7 +1541,14 @@ void GfxRenderer::renderGrayscale(GrayscaleDriveMode mode, void (*renderFn)(cons g_differentialQuantize = false; - displayGrayBuffer(spec.lut, spec.factoryMode); + // Factory mode: differential path keeps the original combined call. Factory + // path uses the split SDK API so the LUT load happens BEFORE RAM writes — + // matches stock V5.5.9 SPI order. Same pattern as renderGrayscaleSinglePass. + if (spec.factoryMode) { + display.displayGrayBufferFactoryActivate(); + } else { + displayGrayBuffer(spec.lut, spec.factoryMode); + } // Suppress the SDK's automatic grayscaleRevert on the next BW page turn. // Caller is responsible for cleanup: restoreBwBuffer rebases RED RAM, and // displayBuffer promotes the first post-factory FAST refresh to HALF. @@ -1652,6 +1666,12 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 } } + // Match stock V5.5.9 SPI order: setCustomLUT + Border BEFORE RAM writes. + // Resolve drive spec early so we can use Setup/Activate. See + // docs/v559-disassembly-findings.md Difference #4. + const auto spec = resolveGrayscaleDrive(mode); + display.displayGrayBufferFactorySetup(spec.lut); + // Pass 1: plane1 (MSB) → BW RAM via copyGrayscaleLsbBuffers. clearScreen(0x00); for (uint16_t c = 0; c < pageWidth; c++) { @@ -1687,8 +1707,7 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 screenshotHookCtx = nullptr; } - const auto spec = resolveGrayscaleDrive(mode); - displayGrayBuffer(spec.lut, true); + display.displayGrayBufferFactoryActivate(); setRenderMode(BW); } From 0bd54cca7494b15740158e06d7145273fa589aa1 Mon Sep 17 00:00:00 2001 From: pablohc Date: Mon, 18 May 2026 12:12:23 +0200 Subject: [PATCH 57/93] fix: set displayState=FactoryLut after factory activate in all render paths --- lib/GfxRenderer/GfxRenderer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index dfd4a339e9..32ea4c0759 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1546,6 +1546,7 @@ void GfxRenderer::renderGrayscale(GrayscaleDriveMode mode, void (*renderFn)(cons // matches stock V5.5.9 SPI order. Same pattern as renderGrayscaleSinglePass. if (spec.factoryMode) { display.displayGrayBufferFactoryActivate(); + displayState = DisplayState::FactoryLut; } else { displayGrayBuffer(spec.lut, spec.factoryMode); } @@ -1621,6 +1622,7 @@ void GfxRenderer::renderGrayscaleSinglePass(GrayscaleDriveMode mode, void (*rend secondaryFrameBuffer = nullptr; g_differentialQuantize = false; display.displayGrayBufferFactoryActivate(); + displayState = DisplayState::FactoryLut; display.clearGrayscaleModeFlag(); setRenderMode(BW); return; @@ -1708,6 +1710,7 @@ void GfxRenderer::displayXtchPlanes(const uint8_t* plane1, const uint8_t* plane2 } display.displayGrayBufferFactoryActivate(); + displayState = DisplayState::FactoryLut; setRenderMode(BW); } From 2123fce4e2ca95d218a101c9fdefa1bf3f99855c Mon Sep 17 00:00:00 2001 From: pablohc Date: Mon, 18 May 2026 19:32:57 +0200 Subject: [PATCH 58/93] chore: update SDK to merged-sdk (factory LUT + power button hold time) --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-x4-sdk b/open-x4-sdk index b4b9d39190..a253cba48f 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit b4b9d391908f5afdcc74db267cc371f1fb9e2991 +Subproject commit a253cba48f76980287fe08ef369215d9aed5b7fd From 44b71b7f12a38a5d6fac96d2b734a706e8bd5bce Mon Sep 17 00:00:00 2001 From: KemoNine Date: Mon, 18 May 2026 12:39:50 -0400 Subject: [PATCH 59/93] feat: port crossink 'read book move' feature to crosspoint (#2032) --- lib/I18n/translations/belarusian.yaml | 1 + lib/I18n/translations/catalan.yaml | 1 + lib/I18n/translations/czech.yaml | 1 + lib/I18n/translations/danish.yaml | 1 + lib/I18n/translations/dutch.yaml | 1 + lib/I18n/translations/english.yaml | 1 + lib/I18n/translations/finnish.yaml | 1 + lib/I18n/translations/french.yaml | 1 + lib/I18n/translations/german.yaml | 1 + lib/I18n/translations/hungarian.yaml | 1 + lib/I18n/translations/italian.yaml | 1 + lib/I18n/translations/kazakh.yaml | 1 + lib/I18n/translations/lithuanian.yaml | 1 + lib/I18n/translations/polish.yaml | 1 + lib/I18n/translations/portuguese.yaml | 1 + lib/I18n/translations/romanian.yaml | 1 + lib/I18n/translations/russian.yaml | 1 + lib/I18n/translations/slovenian.yaml | 1 + lib/I18n/translations/spanish.yaml | 1 + lib/I18n/translations/swedish.yaml | 1 + lib/I18n/translations/turkish.yaml | 1 + lib/I18n/translations/ukrainian.yaml | 1 + src/CrossPointSettings.h | 2 + src/RecentBooksStore.cpp | 22 ++++++ src/RecentBooksStore.h | 12 +++ src/SettingsList.h | 2 + src/activities/reader/EpubReaderActivity.cpp | 82 +++++++++++++++++++- src/activities/reader/EpubReaderActivity.h | 3 + 28 files changed, 144 insertions(+), 1 deletion(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index f854cae866..0b820972dd 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -267,6 +267,7 @@ STR_IMAGES_DISPLAY: "Паказваць" STR_IMAGES_PLACEHOLDER: "Запаўняльнік" STR_IMAGES_SUPPRESS: "Хаваць" STR_SHOW_HIDDEN_FILES: "Паказваць схаваныя файлы" +STR_MOVE_FINISHED_TO_READ: "Перамяшчаць прачытаныя кнігі ў тэчку Read" STR_SELECTED: "Выбрана" STR_SHOW: "Паказаць" STR_HIDE: "Схаваць" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 40b02ea04b..8a80307c3a 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector" STR_HYPHENATION: "Partició de mots" STR_TIME_TO_SLEEP: "Temps per entrar en repòs" STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults" +STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read" STR_REFRESH_FREQ: "Freqüència de refresc" STR_KOREADER_SYNC: "Sincronització del KOReader" STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index 379bd69077..eb398556f1 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Okraj obrazovky čtečky" STR_PARA_ALIGNMENT: "Zarovnání odstavců čtečky" STR_HYPHENATION: "Dělení slov" STR_TIME_TO_SLEEP: "Čas do uspání" +STR_MOVE_FINISHED_TO_READ: "Přesunout přečtené knihy do složky Read" STR_REFRESH_FREQ: "Frekvence obnovení" STR_KOREADER_SYNC: "KOReaderu Sync" STR_CHECK_UPDATES: "Zkontrolovat aktualizace" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 9a1b979da8..ebcace01b8 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Afsnitsjustering" STR_HYPHENATION: "Orddeling" STR_TIME_TO_SLEEP: "Tid til hvile" STR_SHOW_HIDDEN_FILES: "Vis skjulte filer" +STR_MOVE_FINISHED_TO_READ: "Flyt læste bøger til Read-mappen" STR_REFRESH_FREQ: "Opdateringsfrekvens" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Søg efter opdateringer" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 12593f0aff..582854c5a0 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Uitlijning alinea lezer" STR_HYPHENATION: "Woordafbreking" STR_TIME_TO_SLEEP: "Tijd tot slaapstand" STR_SHOW_HIDDEN_FILES: "Toon verborgen bestanden" +STR_MOVE_FINISHED_TO_READ: "Voltooide boeken naar de Read-map verplaatsen" STR_REFRESH_FREQ: "Verversingsfrequentie" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Controleren op updates" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index edd5b08cc6..f60679440e 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Reader Paragraph Alignment" STR_HYPHENATION: "Hyphenation" STR_TIME_TO_SLEEP: "Time to Sleep" STR_SHOW_HIDDEN_FILES: "Show Hidden Files" +STR_MOVE_FINISHED_TO_READ: "Move Finished Books to Read Folder" STR_REFRESH_FREQ: "Refresh Frequency" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Check for updates" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index 93672291f0..8c82ad3b3d 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Lukijan näyttömarginaali" STR_PARA_ALIGNMENT: "Lukijan kappaletasaus" STR_HYPHENATION: "Tavutus" STR_TIME_TO_SLEEP: "Aika lepotilaan" +STR_MOVE_FINISHED_TO_READ: "Siirrä luetut kirjat Read-kansioon" STR_REFRESH_FREQ: "Päivitystaajuus" STR_KOREADER_SYNC: "KOReader-synkronointi" STR_CHECK_UPDATES: "Tarkista päivitykset" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 0f43e82b53..bd054a9071 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Alignement du texte" STR_HYPHENATION: "Césure" STR_TIME_TO_SLEEP: "Mise en veille auto" STR_SHOW_HIDDEN_FILES: "Afficher les fichiers cachés" +STR_MOVE_FINISHED_TO_READ: "Déplacer les livres terminés vers le dossier Read" STR_REFRESH_FREQ: "Fréquence rafraîchissement" STR_KOREADER_SYNC: "Synchro KOReader" STR_CHECK_UPDATES: "Mise à jour" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index d2f8d0e66b..ba78219477 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -302,6 +302,7 @@ STR_IMAGES_DISPLAY: "Darstellung" STR_IMAGES_PLACEHOLDER: "Platzhalter" STR_IMAGES_SUPPRESS: "Unterdrücken" STR_SHOW_HIDDEN_FILES: "Versteckte Dateien anzeigen" +STR_MOVE_FINISHED_TO_READ: "Gelesene Bücher in den Read-Ordner verschieben" STR_PAGE_OVERLAY: "Seiten-Overlay" STR_RECENTS: "Zuletzt" STR_MAPPING_REMOTE: "Externe Position zuordnen..." diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 070f2a28c2..3a8c62d186 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Olvasó szövegigazítás" STR_HYPHENATION: "Elválasztás" STR_TIME_TO_SLEEP: "Alvó mód aktiválása" STR_SHOW_HIDDEN_FILES: "Rejtett fájlok megjelenítése" +STR_MOVE_FINISHED_TO_READ: "Befejezett könyvek áthelyezése a Read mappába" STR_REFRESH_FREQ: "Frissítési frekvencia" STR_KOREADER_SYNC: "KOReader szinkronizálás" STR_CHECK_UPDATES: "Frissítések keresése" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 4a180b4f0c..596f69c2f8 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -86,6 +86,7 @@ STR_PARA_ALIGNMENT: "Allineamento paragrafo" STR_HYPHENATION: "Sillabazione" STR_TIME_TO_SLEEP: "Timeout sospensione" STR_SHOW_HIDDEN_FILES: "Mostra file nascosti" +STR_MOVE_FINISHED_TO_READ: "Sposta i libri finiti nella cartella Read" STR_REFRESH_FREQ: "Frequenza di aggiornamento" STR_KOREADER_SYNC: "Sincronizzazione KOReader" STR_CHECK_UPDATES: "Cerca aggiornamenti" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index 5d9c03e95f..05f1f20df7 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -76,6 +76,7 @@ STR_SCREEN_MARGIN: "Оқырман экран жиегі" STR_PARA_ALIGNMENT: "Оқырман абзац туралануы" STR_HYPHENATION: "Буын бөлу" STR_TIME_TO_SLEEP: "Ұйқы уақыты" +STR_MOVE_FINISHED_TO_READ: "Оқылған кітаптарды Read қалтасына жылжыту" STR_REFRESH_FREQ: "Жаңарту жиілігі" STR_KOREADER_SYNC: "KOReader синхронизациясы" STR_CHECK_UPDATES: "Жаңартуларды тексеру" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index d7619a1fbc..996a2c19e3 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Lygiavimas" STR_HYPHENATION: "Skiemenuoti" STR_TIME_TO_SLEEP: "Užmigti po" STR_SHOW_HIDDEN_FILES: "Rod. paslėptus failus" +STR_MOVE_FINISHED_TO_READ: "Perkelti perskaitytas knygas į aplanką Read" STR_REFRESH_FREQ: "Atnaujinimo dažnis" STR_KOREADER_SYNC: "KOReader sinchr." STR_CHECK_UPDATES: "Atnaujinimai" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 384dbac8a9..33886c744d 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Wyjustowanie paragrafów" STR_HYPHENATION: "Dzielenie wyrazów" STR_TIME_TO_SLEEP: "Czas do uśpienia" STR_SHOW_HIDDEN_FILES: "Pokaż ukryte pliki" +STR_MOVE_FINISHED_TO_READ: "Przenoś przeczytane książki do folderu Read" STR_REFRESH_FREQ: "Częstotliwość odświeżania" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Sprawdź uaktualnia" diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index 97b447b9eb..ee2a1cccc4 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Margens da tela" STR_PARA_ALIGNMENT: "Alinhamento parágrafo" STR_HYPHENATION: "Hifenização" STR_TIME_TO_SLEEP: "Tempo para repousar" +STR_MOVE_FINISHED_TO_READ: "Mover livros lidos para a pasta Read" STR_REFRESH_FREQ: "Frequência atualização" STR_KOREADER_SYNC: "Sincronização KOReader" STR_CHECK_UPDATES: "Verificar atualizações" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index 84d7cac9c4..863cc2f897 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Aliniere paragrafe reader" STR_HYPHENATION: "Silabisire" STR_TIME_TO_SLEEP: "Timp până la repaus" STR_SHOW_HIDDEN_FILES: "Afişează fişierele ascunse" +STR_MOVE_FINISHED_TO_READ: "Mută cărţile terminate în folderul Read" STR_REFRESH_FREQ: "Frecvenţă reîmprospătare" STR_KOREADER_SYNC: "Sincronizare KOReader" STR_CHECK_UPDATES: "Căutaţi actualizări" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 005a5eea2c..dcbe26b380 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Выравнивание абзаца" STR_HYPHENATION: "Перенос слов" STR_TIME_TO_SLEEP: "Сон через" STR_SHOW_HIDDEN_FILES: "Показать скрытые файлы" +STR_MOVE_FINISHED_TO_READ: "Перемещать прочитанные книги в папку Read" STR_REFRESH_FREQ: "Частота обновления" STR_KOREADER_SYNC: "Синхронизация KOReader" STR_CHECK_UPDATES: "Проверить обновления" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index 7e506661bc..cda3347a4f 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Poravnava odstavkov" STR_HYPHENATION: "Deljenje besed" STR_TIME_TO_SLEEP: "Čas do spanja" STR_SHOW_HIDDEN_FILES: "Prikaži skrite datoteke" +STR_MOVE_FINISHED_TO_READ: "Premakni prebrane knjige v mapo Read" STR_REFRESH_FREQ: "Pogostost osveževanja" STR_KOREADER_SYNC: "KOReader sinhronizacija" STR_CHECK_UPDATES: "Preveri posodobitve" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 9b4af84300..71012cf781 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Ajuste de párrafo" STR_HYPHENATION: "División de palabras" STR_TIME_TO_SLEEP: "Auto suspensión" STR_SHOW_HIDDEN_FILES: "Mostrar archivos ocultos" +STR_MOVE_FINISHED_TO_READ: "Mover libros terminados a la carpeta Read" STR_REFRESH_FREQ: "Frecuencia de refresco" STR_KOREADER_SYNC: "Sincronización de KOReader" STR_CHECK_UPDATES: "Verificar actualizaciones" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 9a71fd3266..41d23c7207 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Eboksläsarens paragrafjustering" STR_HYPHENATION: "Avstavning" STR_TIME_TO_SLEEP: "Tid för att gå i vila" STR_SHOW_HIDDEN_FILES: "Visa dolda filer" +STR_MOVE_FINISHED_TO_READ: "Flytta lästa böcker till Read-mappen" STR_REFRESH_FREQ: "Uppdateringsfrekvens" STR_KOREADER_SYNC: "KOReader-synkronisering" STR_CHECK_UPDATES: "Kolla efter uppdateringar" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index 6e0784ce82..4d219212e9 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -80,6 +80,7 @@ STR_PARA_ALIGNMENT: "Okuyucu Paragraf Hizalaması" STR_HYPHENATION: "Hecelerden Ayırma" STR_TIME_TO_SLEEP: "Uykuya Geçme Süresi" STR_SHOW_HIDDEN_FILES: "Gizli Dosyaları Göster" +STR_MOVE_FINISHED_TO_READ: "Biten kitapları Read klasörüne taşı" STR_REFRESH_FREQ: "Yenileme Sıklığı" STR_KOREADER_SYNC: "KOReader Senkronizasyonu" STR_CHECK_UPDATES: "Güncellemeleri denetle" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index 2797f93148..3656cf76c0 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Вирівнювання тексту" STR_HYPHENATION: "Перенесення слів" STR_TIME_TO_SLEEP: "Перехід в режим сну" STR_SHOW_HIDDEN_FILES: "Показати приховані файли" +STR_MOVE_FINISHED_TO_READ: "Переміщати прочитані книги до теки Read" STR_REFRESH_FREQ: "Частота оновлення екрану" STR_KOREADER_SYNC: "Синхронізація KOReader" STR_CHECK_UPDATES: "Перевірити оновлення системи" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index d7e178eecf..c537de7bdd 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -221,6 +221,8 @@ class CrossPointSettings { char sdFontFamilyName[32] = ""; // Show hidden files/directories (starting with '.') in the file browser (0 = hidden, 1 = show) uint8_t showHiddenFiles = 0; + // Move epub to /Read/ folder on SD card when finished (0 = disabled, 1 = enabled) + uint8_t moveFinishedToReadFolder = 0; // Image rendering mode in EPUB reader uint8_t imageRendering = IMAGES_DISPLAY; // Tilt-based page turning (X3 only — requires QMI8658 IMU) diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index b34f8523ba..7b95543340 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -53,6 +53,28 @@ void RecentBooksStore::updateBook(const std::string& path, const std::string& ti } } +void RecentBooksStore::updatePath(const std::string& oldPath, const std::string& newPath, + const std::string& oldCachePath, const std::string& newCachePath) { + auto it = std::find_if(recentBooks.begin(), recentBooks.end(), + [&](const RecentBook& book) { return book.path == oldPath; }); + if (it == recentBooks.end()) { + return; + } + it->path = newPath; + if (!oldCachePath.empty() && !it->coverBmpPath.empty() && it->coverBmpPath.rfind(oldCachePath, 0) == 0) { + it->coverBmpPath = newCachePath + it->coverBmpPath.substr(oldCachePath.size()); + } + saveToFile(); +} + +bool RecentBooksStore::isMissing(const RecentBook& book) { return !Storage.exists(book.path.c_str()); } + +bool RecentBooksStore::pruneMissing() { + const size_t before = recentBooks.size(); + recentBooks.erase(std::remove_if(recentBooks.begin(), recentBooks.end(), &isMissing), recentBooks.end()); + return recentBooks.size() != before; +} + bool RecentBooksStore::saveToFile() const { Storage.mkdir("/.crosspoint"); return JsonSettingsIO::saveRecentBooks(*this, RECENT_BOOKS_FILE_JSON); diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 5d98ce833c..86264553a9 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -37,6 +37,18 @@ class RecentBooksStore { void updateBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath); + // Repoint an entry's path (and coverBmpPath, if it lived under the old cache dir) after the + // backing file and cache dir were moved on disk. No-op if no entry matches oldPath. + // Persists on success. Keeps the entry's list position (does not reorder). + void updatePath(const std::string& oldPath, const std::string& newPath, const std::string& oldCachePath, + const std::string& newCachePath); + + // True if the book's backing file is no longer present on the SD card. + static bool isMissing(const RecentBook& book); + + // Remove entries whose backing file is no longer on the SD card. + // Returns true if any entry was removed. Does not persist — caller decides. + bool pruneMissing(); // Get the list of recent books (most recent first) const std::vector& getBooks() const { return recentBooks; } diff --git a/src/SettingsList.h b/src/SettingsList.h index 4ed466138c..83679c2c5a 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -178,6 +178,8 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist "sleepTimeout", StrId::STR_CAT_SYSTEM), SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles", StrId::STR_CAT_SYSTEM), + SettingInfo::Toggle(StrId::STR_MOVE_FINISHED_TO_READ, &CrossPointSettings::moveFinishedToReadFolder, + "moveFinishedToReadFolder", StrId::STR_CAT_SYSTEM), // --- KOReader Sync (web-only, uses KOReaderCredentialStore) --- SettingInfo::DynamicString( diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d010270527..cebdc3229b 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -45,6 +46,68 @@ int clampPercent(int percent) { return percent; } +// SD card folder finished books are moved into. Single source of truth for the path. +// constexpr ⇒ lives in flash .rodata, no DRAM cost. +constexpr char READ_FOLDER[] = "/read"; + +// True if path is inside READ_FOLDER (starts with "/"). Non-allocating so +// it is cheap to call from loop(), and avoids reintroducing a separate "/Read/" literal. +bool isInReadFolder(const std::string& path) { + constexpr size_t n = sizeof(READ_FOLDER) - 1; // length of "/Read" (excludes NUL) + return path.size() > n && path.compare(0, n, READ_FOLDER) == 0 && path[n] == '/'; +} + +// Pick a non-colliding destination path inside /Read/ for a finished book. +// Mirrors the suffixing scheme used elsewhere: "name.epub" -> "name (2).epub", etc. +std::string buildReadFolderDestination(const std::string& srcPath) { + const size_t lastSlash = srcPath.rfind('/'); + const std::string filename = (lastSlash != std::string::npos) ? srcPath.substr(lastSlash + 1) : srcPath; + + Storage.mkdir(READ_FOLDER); + std::string dstPath = std::string(READ_FOLDER) + "/" + filename; + if (!Storage.exists(dstPath.c_str())) { + return dstPath; + } + + const size_t dotPos = filename.rfind('.'); + const std::string base = (dotPos != std::string::npos) ? filename.substr(0, dotPos) : filename; + const std::string ext = (dotPos != std::string::npos) ? filename.substr(dotPos) : ""; + int suffix = 2; + do { + dstPath = std::string(READ_FOLDER) + "/" + base + " (" + std::to_string(suffix) + ")" + ext; + suffix++; + } while (Storage.exists(dstPath.c_str()) && suffix < 100); + return dstPath; +} + +// Relocate a finished book and its cache dir into /read/, keep it in recents by +// repointing its entry to the new path, and repoint the resume pointer too. +// On rename failure: LOG_ERR and leave everything in place (no UI alert subsystem here). +void moveFinishedBookToReadFolder(const std::string& srcPath, const std::string& dstPath, + const std::string& oldCachePath) { + LOG_INF("ERS", "Moving finished epub: %s -> %s", srcPath.c_str(), dstPath.c_str()); + if (!Storage.rename(srcPath.c_str(), dstPath.c_str())) { + LOG_ERR("ERS", "Failed to move finished book to '/Read' folder"); + return; + } + + // Cache dir is keyed by hash of the epub path (see Epub ctor), so it must be re-keyed. + const std::string newCachePath = "/.crosspoint/epub_" + std::to_string(std::hash{}(dstPath)); + if (!oldCachePath.empty() && Storage.exists(oldCachePath.c_str())) { + if (!Storage.rename(oldCachePath.c_str(), newCachePath.c_str())) { + LOG_ERR("ERS", "Failed to rename cache dir %s -> %s (non-fatal)", oldCachePath.c_str(), newCachePath.c_str()); + } + } + + // Keep the book in recents (crossink behavior): repoint the entry to its new + // location instead of dropping it. updatePath persists on success. + RECENT_BOOKS.updatePath(srcPath, dstPath, oldCachePath, newCachePath); + if (APP_STATE.openEpubPath == srcPath) { + APP_STATE.openEpubPath = dstPath; + APP_STATE.saveToFile(); + } +} + } // namespace void EpubReaderActivity::renderPageCallback(const GfxRenderer& r, const void* raw) { @@ -115,7 +178,15 @@ void EpubReaderActivity::onExit() { APP_STATE.readerActivityLoadCount = 0; APP_STATE.saveToFile(); section.reset(); - epub.reset(); + if (pendingReadFolderMove && epub) { + const std::string srcPath = epub->getPath(); + const std::string oldCachePath = epub->getCachePath(); + const std::string dstPath = buildReadFolderDestination(srcPath); + epub.reset(); // release the Epub (and any open handles) before renaming on the SD card + moveFinishedBookToReadFolder(srcPath, dstPath, oldCachePath); + } else { + epub.reset(); + } } void EpubReaderActivity::loop() { @@ -125,6 +196,15 @@ void EpubReaderActivity::loop() { return; } + // Being on the "End of Book" screen (currentSpineIndex == spine count) means the book is + // finished. Arm the move here so ANY exit path (Back, Home, file browser) relocates the + // book in onExit(); paging back off the end screen disarms it (book not actually finished). + if (currentSpineIndex > 0 && currentSpineIndex >= epub->getSpineItemsCount()) { + pendingReadFolderMove = SETTINGS.moveFinishedToReadFolder && !isInReadFolder(epub->getPath()); + } else { + pendingReadFolderMove = false; + } + if (automaticPageTurnActive) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || mappedInput.wasReleased(MappedInputManager::Button::Back)) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index fbe59566cb..be2f51ba30 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -35,6 +35,9 @@ class EpubReaderActivity final : public Activity { bool lastPageWasFactoryGray = false; int lastFactoryMarginTop = 0; int lastFactoryMarginLeft = 0; + // Set when the reader is left at end-of-book and SETTINGS.moveFinishedToReadFolder is on. + // Consumed in onExit() to relocate the finished book into /Read/. + bool pendingReadFolderMove = false; // Footnote support std::vector currentPageFootnotes; From fe308d97268d474a6be45f6845768d51b1ba2eae Mon Sep 17 00:00:00 2001 From: CaptainFrito Date: Mon, 18 May 2026 21:35:27 +0700 Subject: [PATCH 60/93] feat: Themed reader menus (#1072) --- .../network/WifiSelectionActivity.cpp | 115 +++++++++--------- .../network/WifiSelectionActivity.h | 17 +-- .../EpubReaderChapterSelectionActivity.cpp | 68 +++-------- .../reader/EpubReaderMenuActivity.cpp | 85 +++++-------- .../EpubReaderPercentSelectionActivity.cpp | 20 +-- .../reader/KOReaderSyncActivity.cpp | 57 +++++---- src/components/UITheme.cpp | 44 ++++++- src/components/UITheme.h | 6 + src/components/themes/BaseTheme.cpp | 2 +- src/components/themes/lyra/LyraTheme.cpp | 8 +- 10 files changed, 210 insertions(+), 212 deletions(-) diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index e34403e377..d1c6e1d2b9 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -472,64 +472,63 @@ void WifiSelectionActivity::render(RenderLock&&) { renderer.clearScreen(); - const auto& metrics = UITheme::getInstance().getMetrics(); - const auto pageWidth = renderer.getScreenWidth(); - const auto pageHeight = renderer.getScreenHeight(); + auto& theme = UITheme::getInstance(); + auto metrics = theme.getMetrics(); + Rect screen = theme.getScreenSafeArea(renderer, true, false); // Draw header char countStr[32]; snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size()); - GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_WIFI_NETWORKS), - countStr); - GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight}, - cachedMacAddress.c_str()); + GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight}, + tr(STR_WIFI_NETWORKS), countStr); + GUI.drawSubHeader( + renderer, + Rect{screen.x, screen.y + metrics.topPadding + metrics.headerHeight, screen.width, metrics.tabBarHeight}, + cachedMacAddress.c_str()); switch (state) { case WifiSelectionState::AUTO_CONNECTING: - renderConnecting(); + renderConnecting(&screen, &metrics); break; case WifiSelectionState::SCANNING: - renderConnecting(); // Reuse connecting screen with different message + renderConnecting(&screen, &metrics); // Reuse connecting screen with different message break; case WifiSelectionState::NETWORK_LIST: - renderNetworkList(); + renderNetworkList(&screen, &metrics); break; case WifiSelectionState::CONNECTING: - renderConnecting(); + renderConnecting(&screen, &metrics); break; case WifiSelectionState::CONNECTED: - renderConnected(); + renderConnected(&screen, &metrics); break; case WifiSelectionState::SAVE_PROMPT: - renderSavePrompt(); + renderSavePrompt(&screen, &metrics); break; case WifiSelectionState::CONNECTION_FAILED: - renderConnectionFailed(); + renderConnectionFailed(&screen, &metrics); break; case WifiSelectionState::FORGET_PROMPT: - renderForgetPrompt(); + renderForgetPrompt(&screen, &metrics); break; } renderer.displayBuffer(); } -void WifiSelectionActivity::renderNetworkList() const { - const auto& metrics = UITheme::getInstance().getMetrics(); - const auto pageWidth = renderer.getScreenWidth(); - const auto pageHeight = renderer.getScreenHeight(); - +void WifiSelectionActivity::renderNetworkList(const Rect* screen, const ThemeMetrics* metrics) const { if (networks.empty()) { // No networks found or scan failed const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height) / 2; - renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NO_NETWORKS)); - renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, tr(STR_PRESS_OK_SCAN)); + const auto top = screen->y + (screen->height - height) / 2; + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, tr(STR_NO_NETWORKS)); + UITheme::drawCenteredText(renderer, *screen, SMALL_FONT_ID, top + height + 10, tr(STR_PRESS_OK_SCAN)); } else { - int contentTop = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing; - int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2; + int contentTop = + screen->y + metrics->topPadding + metrics->headerHeight + metrics->tabBarHeight + metrics->verticalSpacing; + int contentHeight = screen->height - contentTop - metrics->verticalSpacing * 2; GUI.drawList( - renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast(networks.size()), + renderer, Rect{screen->x, contentTop, screen->width, contentHeight}, static_cast(networks.size()), selectedNetworkIndex, [this](int index) { return networks[index].ssid; }, nullptr, nullptr, [this](int index) { auto network = networks[index]; @@ -539,7 +538,7 @@ void WifiSelectionActivity::renderNetworkList() const { } GUI.drawHelpText(renderer, - Rect{0, pageHeight - metrics.buttonHintsHeight - metrics.contentSidePadding - 15, pageWidth, 20}, + Rect{screen->x, screen->y + screen->height - metrics->contentSidePadding - 15, screen->width, 20}, tr(STR_NETWORK_LEGEND)); const bool hasSavedPassword = !networks.empty() && networks[selectedNetworkIndex].hasSavedPassword; @@ -549,67 +548,64 @@ void WifiSelectionActivity::renderNetworkList() const { GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } -void WifiSelectionActivity::renderConnecting() const { - const auto pageHeight = renderer.getScreenHeight(); +void WifiSelectionActivity::renderConnecting(const Rect* screen, const ThemeMetrics* metrics) const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height) / 2; + const auto top = screen->y + (screen->height - height) / 2; if (state == WifiSelectionState::SCANNING) { - renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_SCANNING)); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, tr(STR_SCANNING)); } else { - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTING), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 40, tr(STR_CONNECTING), true, + EpdFontFamily::BOLD); std::string ssidInfo = std::string(tr(STR_TO_PREFIX)) + selectedSSID; if (ssidInfo.length() > 25) { ssidInfo.replace(22, ssidInfo.length() - 22, "..."); } - renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, ssidInfo.c_str()); } } -void WifiSelectionActivity::renderConnected() const { - const auto pageHeight = renderer.getScreenHeight(); +void WifiSelectionActivity::renderConnected(const Rect* screen, const ThemeMetrics* metrics) const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height * 4) / 2; + const auto top = screen->y + (screen->height - height * 4) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 30, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 30, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } - renderer.drawCenteredText(UI_10_FONT_ID, top + 10, ssidInfo.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top + 10, ssidInfo.c_str()); const std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP; - renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top + 40, ipInfo.c_str()); // Use centralized button hints const auto labels = mappedInput.mapLabels("", tr(STR_DONE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } -void WifiSelectionActivity::renderSavePrompt() const { - const auto pageWidth = renderer.getScreenWidth(); - const auto pageHeight = renderer.getScreenHeight(); +void WifiSelectionActivity::renderSavePrompt(const Rect* screen, const ThemeMetrics* metrics) const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height * 3) / 2; + const auto top = screen->y + (screen->height - height * 3) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 40, tr(STR_CONNECTED), true, EpdFontFamily::BOLD); std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } - renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, ssidInfo.c_str()); - renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_SAVE_PASSWORD)); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top + 40, tr(STR_SAVE_PASSWORD)); // Draw Yes/No buttons const int buttonY = top + 80; constexpr int buttonWidth = 60; constexpr int buttonSpacing = 30; constexpr int totalWidth = buttonWidth * 2 + buttonSpacing; - const int startX = (pageWidth - totalWidth) / 2; + const int startX = screen->x + (screen->width - totalWidth) / 2; // Draw "Yes" button if (savePromptSelection == 0) { @@ -632,41 +628,40 @@ void WifiSelectionActivity::renderSavePrompt() const { GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } -void WifiSelectionActivity::renderConnectionFailed() const { - const auto pageHeight = renderer.getScreenHeight(); +void WifiSelectionActivity::renderConnectionFailed(const Rect* screen, const ThemeMetrics* metrics) const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height * 2) / 2; + const auto top = screen->y + (screen->height - height * 2) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 20, tr(STR_CONNECTION_FAILED), true, + EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top + 20, connectionError.c_str()); // Use centralized button hints const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_DONE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } -void WifiSelectionActivity::renderForgetPrompt() const { - const auto pageWidth = renderer.getScreenWidth(); - const auto pageHeight = renderer.getScreenHeight(); +void WifiSelectionActivity::renderForgetPrompt(const Rect* screen, const ThemeMetrics* metrics) const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); - const auto top = (pageHeight - height * 3) / 2; + const auto top = screen->y + (screen->height - height * 3) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_FORGET_NETWORK), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 40, tr(STR_FORGET_NETWORK), true, + EpdFontFamily::BOLD); std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID; if (ssidInfo.length() > 28) { ssidInfo.replace(25, ssidInfo.length() - 25, "..."); } - renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, ssidInfo.c_str()); - renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_FORGET_AND_REMOVE)); + UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top + 40, tr(STR_FORGET_AND_REMOVE)); // Draw Cancel/Forget network buttons const int buttonY = top + 80; constexpr int buttonWidth = 120; constexpr int buttonSpacing = 30; constexpr int totalWidth = buttonWidth * 2 + buttonSpacing; - const int startX = (pageWidth - totalWidth) / 2; + const int startX = screen->x + (screen->width - totalWidth) / 2; // Draw "Cancel" button if (forgetPromptSelection == 0) { diff --git a/src/activities/network/WifiSelectionActivity.h b/src/activities/network/WifiSelectionActivity.h index 1ea7fc0a51..baacf4da22 100644 --- a/src/activities/network/WifiSelectionActivity.h +++ b/src/activities/network/WifiSelectionActivity.h @@ -9,6 +9,9 @@ #include "activities/Activity.h" #include "util/ButtonNavigator.h" +struct Rect; +struct ThemeMetrics; + // Structure to hold WiFi network information struct WifiNetworkInfo { std::string ssid; @@ -80,13 +83,13 @@ class WifiSelectionActivity final : public Activity { static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000; unsigned long connectionStartTime = 0; - void renderNetworkList() const; - void renderPasswordEntry() const; - void renderConnecting() const; - void renderConnected() const; - void renderSavePrompt() const; - void renderConnectionFailed() const; - void renderForgetPrompt() const; + void renderNetworkList(const Rect* screen, const ThemeMetrics* metrics) const; + void renderPasswordEntry(const Rect* screen, const ThemeMetrics* metrics) const; + void renderConnecting(const Rect* screen, const ThemeMetrics* metrics) const; + void renderConnected(const Rect* screen, const ThemeMetrics* metrics) const; + void renderSavePrompt(const Rect* screen, const ThemeMetrics* metrics) const; + void renderConnectionFailed(const Rect* screen, const ThemeMetrics* metrics) const; + void renderForgetPrompt(const Rect* screen, const ThemeMetrics* metrics) const; void startWifiScan(); void processWifiScanResults(); diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index ea68bd8f5d..0f085dcea6 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -9,22 +9,6 @@ int EpubReaderChapterSelectionActivity::getTotalItems() const { return epub->getTocItemsCount(); } -int EpubReaderChapterSelectionActivity::getPageItems() const { - // Layout constants used in renderScreen - constexpr int lineHeight = 30; - - const int screenHeight = renderer.getScreenHeight(); - const auto orientation = renderer.getOrientation(); - // In inverted portrait, the button hints are drawn near the logical top. - // Reserve vertical space so list items do not collide with the hints. - const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted; - const int hintGutterHeight = isPortraitInverted ? 50 : 0; - const int startY = 60 + hintGutterHeight; - const int availableHeight = screenHeight - startY - lineHeight; - // Clamp to at least one item to avoid division by zero and empty paging. - return std::max(1, availableHeight / lineHeight); -} - void EpubReaderChapterSelectionActivity::onEnter() { Activity::onEnter(); @@ -44,7 +28,7 @@ void EpubReaderChapterSelectionActivity::onEnter() { void EpubReaderChapterSelectionActivity::onExit() { Activity::onExit(); } void EpubReaderChapterSelectionActivity::loop() { - const int pageItems = getPageItems(); + const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); const int totalItems = getTotalItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { @@ -89,46 +73,22 @@ void EpubReaderChapterSelectionActivity::loop() { void EpubReaderChapterSelectionActivity::render(RenderLock&&) { renderer.clearScreen(); - const auto pageWidth = renderer.getScreenWidth(); - const auto orientation = renderer.getOrientation(); - // Landscape orientation: reserve a horizontal gutter for button hints. - const bool isLandscapeCw = orientation == GfxRenderer::Orientation::LandscapeClockwise; - const bool isLandscapeCcw = orientation == GfxRenderer::Orientation::LandscapeCounterClockwise; - // Inverted portrait: reserve vertical space for hints at the top. - const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted; - const int hintGutterWidth = (isLandscapeCw || isLandscapeCcw) ? 30 : 0; - // Landscape CW places hints on the left edge; CCW keeps them on the right. - const int contentX = isLandscapeCw ? hintGutterWidth : 0; - const int contentWidth = pageWidth - hintGutterWidth; - const int hintGutterHeight = isPortraitInverted ? 50 : 0; - const int contentY = hintGutterHeight; - const int pageItems = getPageItems(); - const int totalItems = getTotalItems(); - - // Manual centering to honor content gutters. - const int titleX = - contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_SELECT_CHAPTER), EpdFontFamily::BOLD)) / 2; - renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_SELECT_CHAPTER), true, EpdFontFamily::BOLD); + auto metrics = UITheme::getInstance().getMetrics(); + Rect screen = UITheme::getInstance().getScreenSafeArea(renderer, true, false); - const auto pageStartIndex = selectorIndex / pageItems * pageItems; - // Highlight only the content area, not the hint gutters. - renderer.fillRect(contentX, 60 + contentY + (selectorIndex % pageItems) * 30 - 2, contentWidth - 1, 30); + GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight}, + tr(STR_SELECT_CHAPTER)); - for (int i = 0; i < pageItems; i++) { - int itemIndex = pageStartIndex + i; - if (itemIndex >= totalItems) break; - const int displayY = 60 + contentY + i * 30; - const bool isSelected = (itemIndex == selectorIndex); + const int contentTop = screen.y + metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; + const int contentHeight = screen.height - contentTop - metrics.verticalSpacing; - auto item = epub->getTocItem(itemIndex); - - // Indent per TOC level while keeping content within the gutter-safe region. - const int indentSize = contentX + 20 + (item.level - 1) * 15; - const std::string chapterName = - renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), contentWidth - 40 - indentSize); - - renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected); - } + const int totalItems = getTotalItems(); + GUI.drawList(renderer, Rect{screen.x, contentTop, screen.width, contentHeight}, totalItems, selectorIndex, + [this](int index) { + auto item = epub->getTocItem(index); + std::string indent((item.level - 1) * 2, ' '); + return indent + item.title; + }); const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 1d95d9b7a1..23b3fafe8a 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -86,29 +86,12 @@ void EpubReaderMenuActivity::loop() { void EpubReaderMenuActivity::render(RenderLock&&) { renderer.clearScreen(); - const auto pageWidth = renderer.getScreenWidth(); - const auto orientation = renderer.getOrientation(); - // Landscape orientation: button hints are drawn along a vertical edge, so we - // reserve a horizontal gutter to prevent overlap with menu content. - const bool isLandscapeCw = orientation == GfxRenderer::Orientation::LandscapeClockwise; - const bool isLandscapeCcw = orientation == GfxRenderer::Orientation::LandscapeCounterClockwise; - // Inverted portrait: button hints appear near the logical top, so we reserve - // vertical space to keep the header and list clear. - const bool isPortraitInverted = orientation == GfxRenderer::Orientation::PortraitInverted; - const int hintGutterWidth = (isLandscapeCw || isLandscapeCcw) ? 30 : 0; - // Landscape CW places hints on the left edge; CCW keeps them on the right. - const int contentX = isLandscapeCw ? hintGutterWidth : 0; - const int contentWidth = pageWidth - hintGutterWidth; - const int hintGutterHeight = isPortraitInverted ? 50 : 0; - const int contentY = hintGutterHeight; - - // Title - const std::string truncTitle = - renderer.truncatedText(UI_12_FONT_ID, title.c_str(), contentWidth - 40, EpdFontFamily::BOLD); - // Manual centering so we can respect the content gutter. - const int titleX = - contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, truncTitle.c_str(), EpdFontFamily::BOLD)) / 2; - renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, truncTitle.c_str(), true, EpdFontFamily::BOLD); + + auto metrics = UITheme::getInstance().getMetrics(); + Rect screen = UITheme::getInstance().getScreenSafeArea(renderer, true, false); + + GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight}, + title.c_str()); // Progress summary std::string progressLine; @@ -117,37 +100,31 @@ void EpubReaderMenuActivity::render(RenderLock&&) { std::to_string(totalPages) + std::string(tr(STR_PAGES_SEPARATOR)); } progressLine += std::string(tr(STR_BOOK_PREFIX)) + std::to_string(bookProgressPercent) + "%"; - renderer.drawCenteredText(UI_10_FONT_ID, 45, progressLine.c_str()); - - // Menu Items - const int startY = 75 + contentY; - constexpr int lineHeight = 30; - - for (size_t i = 0; i < menuItems.size(); ++i) { - const int displayY = startY + (i * lineHeight); - const bool isSelected = (static_cast(i) == selectedIndex); - - if (isSelected) { - // Highlight only the content area so we don't paint over hint gutters. - renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true); - } - - renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected); - - if (menuItems[i].action == MenuAction::ROTATE_SCREEN) { - // Render current orientation value on the right edge of the content area. - const char* value = I18N.get(orientationLabels[pendingOrientation]); - const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); - renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); - } - - if (menuItems[i].action == MenuAction::AUTO_PAGE_TURN) { - // Render current page turn value on the right edge of the content area. - const auto value = pageTurnLabels[selectedPageTurnOption]; - const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); - renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); - } - } + GUI.drawSubHeader( + renderer, + Rect{screen.x, screen.y + metrics.topPadding + metrics.headerHeight, screen.width, metrics.tabBarHeight}, + progressLine.c_str()); + + const int contentTop = + screen.y + metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing; + const int contentHeight = screen.height - contentTop - metrics.verticalSpacing; + + GUI.drawList( + renderer, Rect{screen.x, contentTop, screen.width, contentHeight}, menuItems.size(), selectedIndex, + [this](int index) { return I18N.get(menuItems[index].labelId); }, nullptr, nullptr, + [this](int index) { + const auto value = menuItems[index].action; + if (value == MenuAction::ROTATE_SCREEN) { + // Render current orientation value on the right edge of the content area. + return I18N.get(orientationLabels[pendingOrientation]); + } else if (value == MenuAction::AUTO_PAGE_TURN) { + // Render current page turn value on the right edge of the content area. + return pageTurnLabels[selectedPageTurnOption]; + } else { + return ""; + } + }, + true); // Footer / Hints const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); diff --git a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp index db4109f00a..78cb268339 100644 --- a/src/activities/reader/EpubReaderPercentSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderPercentSelectionActivity.cpp @@ -58,18 +58,24 @@ void EpubReaderPercentSelectionActivity::loop() { void EpubReaderPercentSelectionActivity::render(RenderLock&&) { renderer.clearScreen(); - // Title and numeric percent value. - renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_GO_TO_PERCENT), true, EpdFontFamily::BOLD); + auto& theme = UITheme::getInstance(); + auto metrics = theme.getMetrics(); + Rect screen = theme.getScreenSafeArea(renderer, true, false); + + GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight}, + tr(STR_GO_TO_PERCENT)); + + const int contentTop = screen.y + metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing * 4; const std::string percentText = std::to_string(percent) + "%"; - renderer.drawCenteredText(UI_12_FONT_ID, 90, percentText.c_str(), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_12_FONT_ID, contentTop, percentText.c_str(), true, + EpdFontFamily::BOLD); // Draw slider track. - const int screenWidth = renderer.getScreenWidth(); constexpr int barWidth = 360; constexpr int barHeight = 16; - const int barX = (screenWidth - barWidth) / 2; - const int barY = 140; + const int barX = screen.x + (screen.width - barWidth) / 2; + const int barY = contentTop + metrics.verticalSpacing * 2; renderer.drawRect(barX, barY, barWidth, barHeight); @@ -84,7 +90,7 @@ void EpubReaderPercentSelectionActivity::render(RenderLock&&) { renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true); // Hint text for step sizes. - renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, tr(STR_PERCENT_STEP_HINT), true); + UITheme::drawCenteredText(renderer, screen, SMALL_FONT_ID, barY + 30, tr(STR_PERCENT_STEP_HINT), true); // Button hints follow the current front button layout. const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+"); diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index d445103608..9643e14a8a 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -319,14 +319,20 @@ void KOReaderSyncActivity::onExit() { } void KOReaderSyncActivity::render(RenderLock&&) { - const auto pageWidth = renderer.getScreenWidth(); - renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_SYNC), true, EpdFontFamily::BOLD); + auto metrics = UITheme::getInstance().getMetrics(); + Rect screen = UITheme::getInstance().getScreenSafeArea(renderer, true, false); + + GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight}, + tr(STR_KOREADER_SYNC)); + + int top = screen.y + screen.height / 2 - 40; if (state == NO_CREDENTIALS) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_CREDENTIALS_MSG), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_KOREADER_SETUP_HINT)); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top, tr(STR_NO_CREDENTIALS_MSG), true, + EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top + 40, tr(STR_KOREADER_SETUP_HINT), true, + EpdFontFamily::BOLD); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); @@ -335,14 +341,15 @@ void KOReaderSyncActivity::render(RenderLock&&) { } if (state == SYNCING || state == UPLOADING) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top, statusMessage.c_str(), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == SHOWING_RESULT) { // Show comparison - renderer.drawCenteredText(UI_10_FONT_ID, 120, tr(STR_PROGRESS_FOUND), true, EpdFontFamily::BOLD); + top = screen.y + metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; + renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_PROGRESS_FOUND), true, EpdFontFamily::BOLD); // Remote chapter name requires Epub (loaded lazily in performSync before this state). const int remoteTocIndex = epub->getTocIndexForSpineIndex(remotePosition.spineIndex); @@ -355,45 +362,47 @@ void KOReaderSyncActivity::render(RenderLock&&) { : (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(currentSpineIndex + 1)); // Remote progress - chapter and page - renderer.drawText(UI_10_FONT_ID, 20, 160, tr(STR_REMOTE_LABEL), true); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 40, tr(STR_REMOTE_LABEL), true); char remoteChapterStr[128]; snprintf(remoteChapterStr, sizeof(remoteChapterStr), " %s", remoteChapter.c_str()); - renderer.drawText(UI_10_FONT_ID, 20, 185, remoteChapterStr); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 65, remoteChapterStr); char remotePageStr[64]; snprintf(remotePageStr, sizeof(remotePageStr), tr(STR_PAGE_OVERALL_FORMAT), remotePosition.pageNumber + 1, remoteProgress.percentage * 100); - renderer.drawText(UI_10_FONT_ID, 20, 210, remotePageStr); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 90, remotePageStr); if (!remoteProgress.device.empty()) { char deviceStr[64]; snprintf(deviceStr, sizeof(deviceStr), tr(STR_DEVICE_FROM_FORMAT), remoteProgress.device.c_str()); - renderer.drawText(UI_10_FONT_ID, 20, 235, deviceStr); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 115, deviceStr); } // Local progress - chapter and page - renderer.drawText(UI_10_FONT_ID, 20, 270, tr(STR_LOCAL_LABEL), true); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 150, tr(STR_LOCAL_LABEL), true); char localChapterStr[128]; snprintf(localChapterStr, sizeof(localChapterStr), " %s", localChapter.c_str()); - renderer.drawText(UI_10_FONT_ID, 20, 295, localChapterStr); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 175, localChapterStr); char localPageStr[64]; snprintf(localPageStr, sizeof(localPageStr), tr(STR_PAGE_TOTAL_OVERALL_FORMAT), currentPage + 1, totalPagesInSpine, localProgress.percentage * 100); - renderer.drawText(UI_10_FONT_ID, 20, 320, localPageStr); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, top + 200, localPageStr); - const int optionY = 350; + const int optionY = top + 230; const int optionHeight = 30; // Apply option if (selectedOption == 0) { - renderer.fillRect(0, optionY - 2, pageWidth - 1, optionHeight); + renderer.fillRect(screen.x, optionY - 2, screen.width - 1, optionHeight); } - renderer.drawText(UI_10_FONT_ID, 20, optionY, tr(STR_APPLY_REMOTE), selectedOption != 0); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, optionY, tr(STR_APPLY_REMOTE), + selectedOption != 0); // Upload option if (selectedOption == 1) { - renderer.fillRect(0, optionY + optionHeight - 2, pageWidth - 1, optionHeight); + renderer.fillRect(screen.x, optionY + optionHeight - 2, screen.width - 1, optionHeight); } - renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, tr(STR_UPLOAD_LOCAL), selectedOption != 1); + renderer.drawText(UI_10_FONT_ID, screen.x + metrics.contentSidePadding, optionY + optionHeight, + tr(STR_UPLOAD_LOCAL), selectedOption != 1); // Bottom button hints const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); @@ -403,8 +412,8 @@ void KOReaderSyncActivity::render(RenderLock&&) { } if (state == NO_REMOTE_PROGRESS) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_REMOTE_MSG), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_UPLOAD_PROMPT)); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top, tr(STR_NO_REMOTE_MSG), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top + 40, tr(STR_UPLOAD_PROMPT)); const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_UPLOAD), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); @@ -413,7 +422,7 @@ void KOReaderSyncActivity::render(RenderLock&&) { } if (state == UPLOAD_COMPLETE) { - renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPLOAD_SUCCESS), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top, tr(STR_UPLOAD_SUCCESS), true, EpdFontFamily::BOLD); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); @@ -422,8 +431,8 @@ void KOReaderSyncActivity::render(RenderLock&&) { } if (state == SYNC_FAILED) { - renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_SYNC_FAILED_MSG), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, 320, statusMessage.c_str()); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top, tr(STR_SYNC_FAILED_MSG), true, EpdFontFamily::BOLD); + UITheme::drawCenteredText(renderer, screen, UI_10_FONT_ID, top + 40, statusMessage.c_str()); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 32e5300326..0fda24c5af 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -53,6 +53,7 @@ void UITheme::setTheme(CrossPointSettings::UI_THEME type) { int UITheme::getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, bool hasSubtitle, int extraReservedHeight) { const ThemeMetrics& metrics = UITheme::getInstance().getMetrics(); + auto orientation = renderer.getOrientation(); int reservedHeight = metrics.topPadding; if (hasHeader) { reservedHeight += metrics.headerHeight + metrics.verticalSpacing; @@ -60,7 +61,8 @@ int UITheme::getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader if (hasTabBar) { reservedHeight += metrics.tabBarHeight; } - if (hasButtonHints) { + if (hasButtonHints && orientation != GfxRenderer::Orientation::LandscapeClockwise && + orientation != GfxRenderer::Orientation::LandscapeCounterClockwise) { reservedHeight += metrics.verticalSpacing + metrics.buttonHintsHeight; } const int availableHeight = renderer.getScreenHeight() - reservedHeight - extraReservedHeight; @@ -68,6 +70,39 @@ int UITheme::getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader return availableHeight / rowHeight; } +// Screen area excluding the button hints +Rect UITheme::getScreenSafeArea(const GfxRenderer& renderer, bool hasFrontButtonHints, bool hasSideButtonHints) { + auto orientation = renderer.getOrientation(); + const int screenWidth = renderer.getScreenWidth(); + const int screenHeight = renderer.getScreenHeight(); + Rect safeArea = Rect{0, 0, screenWidth, screenHeight}; + switch (orientation) { + case GfxRenderer::Orientation::Portrait: + if (hasFrontButtonHints) { + safeArea.height -= currentMetrics->buttonHintsHeight; + } + break; + case GfxRenderer::Orientation::LandscapeClockwise: + if (hasFrontButtonHints) { + safeArea.x += currentMetrics->buttonHintsHeight; + safeArea.width -= currentMetrics->buttonHintsHeight; + } + break; + case GfxRenderer::Orientation::PortraitInverted: + if (hasFrontButtonHints) { + safeArea.y += currentMetrics->buttonHintsHeight; + safeArea.height -= currentMetrics->buttonHintsHeight; + } + break; + case GfxRenderer::Orientation::LandscapeCounterClockwise: + if (hasFrontButtonHints) { + safeArea.width -= currentMetrics->buttonHintsHeight; + } + break; + } + return safeArea; +} + std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight) { size_t pos = coverBmpPath.find("[HEIGHT]", 0); if (pos != std::string::npos) { @@ -111,3 +146,10 @@ int UITheme::getProgressBarHeight() { SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; return (showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0); } + +// Centered text implementation that takes the safe area into account +void UITheme::drawCenteredText(const GfxRenderer& renderer, Rect screen, int fontId, int y, const char* text, + bool black, EpdFontFamily::Style style) { + const int x = screen.x + (screen.width - renderer.getTextWidth(fontId, text, style)) / 2; + renderer.drawText(fontId, x, y, text, black, style); +} diff --git a/src/components/UITheme.h b/src/components/UITheme.h index c4c37235c0..6660254124 100644 --- a/src/components/UITheme.h +++ b/src/components/UITheme.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -16,6 +18,10 @@ class UITheme { const ThemeMetrics& getMetrics() const { return *currentMetrics; } const BaseTheme& getTheme() const { return *currentTheme; } + Rect getScreenSafeArea(const GfxRenderer& renderer, bool hasFrontButtonHints = false, + bool hasSideButtonHints = false); + static void drawCenteredText(const GfxRenderer& renderer, Rect screen, int fontId, int y, const char* text, + bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR); void reload(); void setTheme(CrossPointSettings::UI_THEME type); static int getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 7fd4ab9eaf..0650948b7a 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -266,7 +266,7 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, // Draw selection int contentWidth = rect.width - 5; if (selectedIndex >= 0) { - renderer.fillRect(0, rect.y + selectedIndex % pageItems * rowHeight - 2, rect.width, rowHeight); + renderer.fillRect(rect.x, rect.y + selectedIndex % pageItems * rowHeight - 2, rect.width, rowHeight); } constexpr int maxValueWidth = 200; constexpr int minValueGap = 10; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 7ee9ad84ae..1e11f2364f 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -233,9 +233,9 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, rect.width - (totalPages > 1 ? (LyraMetrics::values.scrollBarWidth + LyraMetrics::values.scrollBarRightOffset) : 1); if (selectedIndex >= 0) { - renderer.fillRoundedRect(LyraMetrics::values.contentSidePadding, rect.y + selectedIndex % pageItems * rowHeight, - contentWidth - LyraMetrics::values.contentSidePadding * 2, rowHeight, cornerRadius, - Color::LightGray); + renderer.fillRoundedRect( + rect.x + LyraMetrics::values.contentSidePadding, rect.y + selectedIndex % pageItems * rowHeight, + contentWidth - LyraMetrics::values.contentSidePadding * 2, rowHeight, cornerRadius, Color::LightGray); } int textX = rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection; @@ -297,7 +297,7 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, if (!valueText.empty()) { if (i == selectedIndex && highlightValue) { renderer.fillRoundedRect( - contentWidth - LyraMetrics::values.contentSidePadding - hPaddingInSelection - valueWidth, itemY, + rect.x + contentWidth - LyraMetrics::values.contentSidePadding - hPaddingInSelection - valueWidth, itemY, valueWidth + hPaddingInSelection, rowHeight, cornerRadius, Color::Black); } From f8962388877ba98b19f428e87804af26831a4c27 Mon Sep 17 00:00:00 2001 From: WuTofu <5987870+WuTofu@users.noreply.github.com> Date: Mon, 18 May 2026 05:17:24 +0800 Subject: [PATCH 61/93] chore: add 3-minute sleep option (#1948) --- lib/I18n/translations/belarusian.yaml | 1 + lib/I18n/translations/catalan.yaml | 1 + lib/I18n/translations/czech.yaml | 1 + lib/I18n/translations/danish.yaml | 1 + lib/I18n/translations/dutch.yaml | 1 + lib/I18n/translations/english.yaml | 1 + lib/I18n/translations/finnish.yaml | 1 + lib/I18n/translations/french.yaml | 1 + lib/I18n/translations/german.yaml | 1 + lib/I18n/translations/hungarian.yaml | 1 + lib/I18n/translations/italian.yaml | 1 + lib/I18n/translations/kazakh.yaml | 1 + lib/I18n/translations/lithuanian.yaml | 1 + lib/I18n/translations/polish.yaml | 1 + lib/I18n/translations/portuguese.yaml | 1 + lib/I18n/translations/romanian.yaml | 1 + lib/I18n/translations/russian.yaml | 1 + lib/I18n/translations/slovenian.yaml | 1 + lib/I18n/translations/spanish.yaml | 1 + lib/I18n/translations/swedish.yaml | 1 + lib/I18n/translations/turkish.yaml | 1 + lib/I18n/translations/ukrainian.yaml | 1 + src/CrossPointSettings.cpp | 2 ++ src/CrossPointSettings.h | 1 + src/SettingsList.h | 3 ++- 25 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index 0b820972dd..e907950d26 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -143,6 +143,7 @@ STR_ALIGN_LEFT: "Па левым краі" STR_CENTER: "Па цэнтры" STR_ALIGN_RIGHT: "Па правым краі" STR_MIN_1: "1 хв" +STR_MIN_3: "3 хв" STR_MIN_5: "5 хв" STR_MIN_10: "10 хв" STR_MIN_15: "15 хв" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 8a80307c3a..0961626c02 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Esquerra" STR_CENTER: "Centre" STR_ALIGN_RIGHT: "Dreta" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index eb398556f1..e03ea8afc5 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -147,6 +147,7 @@ STR_ALIGN_LEFT: "Vlevo" STR_CENTER: "Na střed" STR_ALIGN_RIGHT: "Vpravo" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index ebcace01b8..56f3a05ff9 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Venstre" STR_CENTER: "Centreret" STR_ALIGN_RIGHT: "Højre" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 582854c5a0..0f0c42ab54 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Links" STR_CENTER: "Centreren" STR_ALIGN_RIGHT: "Rechts" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index f60679440e..4ee88b2dfc 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -153,6 +153,7 @@ STR_ALIGN_LEFT: "Left" STR_CENTER: "Center" STR_ALIGN_RIGHT: "Right" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index 8c82ad3b3d..6d625a07a8 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -147,6 +147,7 @@ STR_ALIGN_LEFT: "Vasen" STR_CENTER: "Keskitetty" STR_ALIGN_RIGHT: "Oikea" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index bd054a9071..8e335f76b3 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Gauche" STR_CENTER: "Centré" STR_ALIGN_RIGHT: "Droite" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index ba78219477..0f98eb6c03 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -151,6 +151,7 @@ STR_ALIGN_LEFT: "Links" STR_CENTER: "Zentriert" STR_ALIGN_RIGHT: "Rechts" STR_MIN_1: "1 Min" +STR_MIN_3: "3 Min" STR_MIN_5: "5 Min" STR_MIN_10: "10 Min" STR_MIN_15: "15 Min" diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 3a8c62d186..b5eb19f71a 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -149,6 +149,7 @@ STR_ALIGN_LEFT: "Bal" STR_CENTER: "Közép" STR_ALIGN_RIGHT: "Jobb" STR_MIN_1: "1 perc" +STR_MIN_3: "3 perc" STR_MIN_5: "5 perc" STR_MIN_10: "10 perc" STR_MIN_15: "15 perc" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 596f69c2f8..45e9f842a5 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -153,6 +153,7 @@ STR_ALIGN_LEFT: "Sinistra" STR_CENTER: "Centrato" STR_ALIGN_RIGHT: "Destra" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index 05f1f20df7..624bf197b5 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -143,6 +143,7 @@ STR_ALIGN_LEFT: "Солға" STR_CENTER: "Ортаға" STR_ALIGN_RIGHT: "Оңға" STR_MIN_1: "1 мин" +STR_MIN_3: "3 мин" STR_MIN_5: "5 мин" STR_MIN_10: "10 мин" STR_MIN_15: "15 мин" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index 996a2c19e3..b73c86e7a8 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -149,6 +149,7 @@ STR_ALIGN_LEFT: "Kairė" STR_CENTER: "Centras" STR_ALIGN_RIGHT: "Dešinė" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 33886c744d..fffdb11453 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -153,6 +153,7 @@ STR_ALIGN_LEFT: "Lewo" STR_CENTER: "Środek" STR_ALIGN_RIGHT: "Prawo" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index ee2a1cccc4..a0efb68c0f 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -147,6 +147,7 @@ STR_ALIGN_LEFT: "Esquerda" STR_CENTER: "Centralizar" STR_ALIGN_RIGHT: "Direita" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index 863cc2f897..d567046721 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Stânga" STR_CENTER: "Centru" STR_ALIGN_RIGHT: "Dreapta" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index dcbe26b380..7815092afc 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "По левому краю" STR_CENTER: "По центру" STR_ALIGN_RIGHT: "По правому краю" STR_MIN_1: "1 мин" +STR_MIN_3: "3 мин" STR_MIN_5: "5 мин" STR_MIN_10: "10 мин" STR_MIN_15: "15 мин" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index cda3347a4f..87d96817fe 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -149,6 +149,7 @@ STR_ALIGN_LEFT: "Levo" STR_CENTER: "Sredinsko" STR_ALIGN_RIGHT: "Desno" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 71012cf781..26f9f1b3a4 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -152,6 +152,7 @@ STR_ALIGN_LEFT: "Izquierda" STR_CENTER: "Centro" STR_ALIGN_RIGHT: "Derecha" STR_MIN_1: "1 min." +STR_MIN_3: "3 min." STR_MIN_5: "5 min." STR_MIN_10: "10 min." STR_MIN_15: "15 min." diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 41d23c7207..8d0957d345 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -153,6 +153,7 @@ STR_ALIGN_LEFT: "Vänster" STR_CENTER: "Mitten" STR_ALIGN_RIGHT: "Höger" STR_MIN_1: "1 min" +STR_MIN_3: "3 min" STR_MIN_5: "5 min" STR_MIN_10: "10 min" STR_MIN_15: "15 min" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index 4d219212e9..64a5828596 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -147,6 +147,7 @@ STR_ALIGN_LEFT: "Sola Yasla" STR_CENTER: "Ortala" STR_ALIGN_RIGHT: "Sağa Yasla" STR_MIN_1: "1 dak" +STR_MIN_3: "3 dak" STR_MIN_5: "5 dak" STR_MIN_10: "10 dak" STR_MIN_15: "15 dak" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index 3656cf76c0..bc2299a960 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -153,6 +153,7 @@ STR_ALIGN_LEFT: "За лівим краєм" STR_CENTER: "По центру" STR_ALIGN_RIGHT: "За правим краєм" STR_MIN_1: "1 хв" +STR_MIN_3: "3 хв" STR_MIN_5: "5 хв" STR_MIN_10: "10 хв" STR_MIN_15: "15 хв" diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 44037690ec..527392f3aa 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -305,6 +305,8 @@ unsigned long CrossPointSettings::getSleepTimeoutMs() const { switch (sleepTimeout) { case SLEEP_1_MIN: return 1UL * 60 * 1000; + case SLEEP_3_MIN: + return 3UL * 60 * 1000; case SLEEP_5_MIN: return 5UL * 60 * 1000; case SLEEP_10_MIN: diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index c537de7bdd..9c67463a81 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -120,6 +120,7 @@ class CrossPointSettings { SLEEP_10_MIN = 2, SLEEP_15_MIN = 3, SLEEP_30_MIN = 4, + SLEEP_3_MIN = 5, SLEEP_TIMEOUT_COUNT }; diff --git a/src/SettingsList.h b/src/SettingsList.h index 83679c2c5a..4ad42206bf 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -174,7 +174,8 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist // --- System --- SettingInfo::Enum(StrId::STR_TIME_TO_SLEEP, &CrossPointSettings::sleepTimeout, - {StrId::STR_MIN_1, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15, StrId::STR_MIN_30}, + {StrId::STR_MIN_1, StrId::STR_MIN_3, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15, + StrId::STR_MIN_30}, "sleepTimeout", StrId::STR_CAT_SYSTEM), SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles", StrId::STR_CAT_SYSTEM), From 576f807ed2d45814808b0777a1fd0bffa56e4dc5 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 00:35:58 +0200 Subject: [PATCH 62/93] feat: add deadline-based cover generation cancellation pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Propagate uint32_t deadline through the full rendering stack: - Epub::generateThumbBmp → readItemContentsToStream → ZipFile::readFileToStream - ZipFile: deadline check in stored (read loop) and deflated (inflate loop) paths - JpegToBmpConverter: deadline check in JPEGDEC bmpDrawCallback (per MCU row) - PngToBmpConverter: deadline check per scanline in decode loop - All functions use deadline=0 default for backward compatibility - Uses millis rollover-safe comparison: static_cast(millis() - deadline) >= 0 --- lib/Epub/Epub.cpp | 16 +++++++++------- lib/Epub/Epub.h | 5 +++-- lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 14 +++++++++++--- lib/JpegToBmpConverter/JpegToBmpConverter.h | 5 +++-- lib/PngToBmpConverter/PngToBmpConverter.cpp | 12 +++++++++--- lib/PngToBmpConverter/PngToBmpConverter.h | 5 +++-- lib/ZipFile/ZipFile.cpp | 11 ++++++++++- lib/ZipFile/ZipFile.h | 2 +- 8 files changed, 49 insertions(+), 21 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index a5befe5b37..2f7e769910 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -616,7 +616,7 @@ bool Epub::generateCoverBmp(bool cropped) const { 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 { +bool Epub::generateThumbBmp(int height, uint32_t deadline) const { // Already generated, return true if (Storage.exists(getThumbBmpPath(height).c_str())) { return true; @@ -638,7 +638,7 @@ bool Epub::generateThumbBmp(int height) const { if (!Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) { return false; } - readItemContentsToStream(coverImageHref, coverJpg, 1024); + readItemContentsToStream(coverImageHref, coverJpg, 1024, deadline); // Explicitly close() file before reopening for reading coverJpg.close(); @@ -655,7 +655,7 @@ bool Epub::generateThumbBmp(int height) const { 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); + THUMB_TARGET_HEIGHT, deadline); // Explicitly close() files before calling Storage.remove() coverJpg.close(); thumbBmp.close(); @@ -675,7 +675,7 @@ bool Epub::generateThumbBmp(int height) const { if (!Storage.openFileForWrite("EBP", coverPngTempPath, coverPng)) { return false; } - readItemContentsToStream(coverImageHref, coverPng, 1024); + readItemContentsToStream(coverImageHref, coverPng, 1024, deadline); // Explicitly close() file before reopening for reading coverPng.close(); @@ -690,7 +690,8 @@ bool Epub::generateThumbBmp(int height) const { 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); + PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH, THUMB_TARGET_HEIGHT, + deadline); // Explicitly close() files before calling Storage.remove() coverPng.close(); thumbBmp.close(); @@ -729,14 +730,15 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size return content; } -bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const { +bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize, + uint32_t deadline) 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); + return ZipFile(filepath).readFileToStream(path.c_str(), out, chunkSize, deadline); } bool Epub::getItemSize(const std::string& itemHref, size_t* size) const { diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index 9ffa8d37c5..0d33bca6dd 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -55,10 +55,11 @@ class Epub { bool generateCoverBmp(bool cropped = false) const; std::string getThumbBmpPath() const; std::string getThumbBmpPath(int height) const; - bool generateThumbBmp(int height) const; + bool generateThumbBmp(int height, uint32_t deadline = 0) 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 readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize, + uint32_t deadline = 0) const; bool getItemSize(const std::string& itemHref, size_t* size) const; BookMetadataCache::SpineEntry getSpineItem(int spineIndex) const; BookMetadataCache::TocEntry getTocItem(int tocIndex) const; diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index 44bd504673..e1709ca9e7 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -226,6 +226,7 @@ struct BmpConvertCtx { Atkinson1BitDitherer* atkinson1BitDitherer; bool error; + uint32_t deadline; // 0 = no limit }; // Write a fully-assembled output row (grayscale bytes, length outWidth) to BMP @@ -329,6 +330,12 @@ int bmpDrawCallback(JPEGDRAW* pDraw) { // Wait for the last MCU column before processing any rows if (blockX + validW < ctx->srcWidth) return 1; + if (ctx->deadline != 0 && static_cast(millis() - ctx->deadline) >= 0) { + LOG_ERR("JPG", "Decode deadline exceeded at MCU row %d", blockY); + ctx->error = true; + return 0; + } + // Process each complete source row in this MCU row const int endRow = blockY + blockH; @@ -376,7 +383,7 @@ int bmpDrawCallback(JPEGDRAW* pDraw) { // Internal implementation with configurable target size and bit depth bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, - bool oneBit, bool crop) { + bool oneBit, bool crop, uint32_t deadline) { LOG_DBG("JPG", "Converting JPEG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight); if (ESP.getFreeHeap() < MIN_FREE_HEAP) { @@ -470,6 +477,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm ctx.scaleX_fp = scaleX_fp; ctx.scaleY_fp = scaleY_fp; ctx.error = false; + ctx.deadline = deadline; // RAII guard: frees all heap resources on any return path struct Cleanup { @@ -552,6 +560,6 @@ bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bm // Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, - int targetMaxHeight) { - return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true); + int targetMaxHeight, uint32_t deadline) { + return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true, deadline); } diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.h b/lib/JpegToBmpConverter/JpegToBmpConverter.h index 66f77f6735..5a86730f97 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.h +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.h @@ -7,12 +7,13 @@ class ZipFile; class JpegToBmpConverter { static bool jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, - bool oneBit, bool crop = true); + bool oneBit, bool crop, uint32_t deadline); public: static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop = true); // Convert with custom target size (for thumbnails) static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight); // Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering - static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight); + static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight, + uint32_t deadline = 0); }; diff --git a/lib/PngToBmpConverter/PngToBmpConverter.cpp b/lib/PngToBmpConverter/PngToBmpConverter.cpp index 5a8c4752a4..ebe606d422 100644 --- a/lib/PngToBmpConverter/PngToBmpConverter.cpp +++ b/lib/PngToBmpConverter/PngToBmpConverter.cpp @@ -396,7 +396,7 @@ static void convertScanlineToGray(const PngDecodeContext& ctx, uint8_t* grayRow) } bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight, - bool oneBit, bool crop) { + bool oneBit, bool crop, uint32_t deadline) { LOG_DBG("PNG", "Converting PNG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight); // Verify PNG signature @@ -666,6 +666,12 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu // Process each scanline for (uint32_t y = 0; y < height; y++) { + if (deadline != 0 && static_cast(millis() - deadline) >= 0) { + LOG_ERR("PNG", "Decode deadline exceeded at scanline %u", y); + success = false; + break; + } + // Decode one scanline if (!decodeScanline(ctx)) { LOG_ERR("PNG", "Failed to decode scanline %u", y); @@ -833,6 +839,6 @@ bool PngToBmpConverter::pngFileToBmpStreamWithSize(FsFile& pngFile, Print& bmpOu } bool PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, - int targetMaxHeight) { - return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true); + int targetMaxHeight, uint32_t deadline) { + return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true, deadline); } diff --git a/lib/PngToBmpConverter/PngToBmpConverter.h b/lib/PngToBmpConverter/PngToBmpConverter.h index bf9d3a2c69..bd7f492f64 100644 --- a/lib/PngToBmpConverter/PngToBmpConverter.h +++ b/lib/PngToBmpConverter/PngToBmpConverter.h @@ -6,10 +6,11 @@ class Print; class PngToBmpConverter { static bool pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight, bool oneBit, - bool crop = true); + bool crop, uint32_t deadline); public: static bool pngFileToBmpStream(FsFile& pngFile, Print& bmpOut, bool crop = true); static bool pngFileToBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight); - static bool pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight); + static bool pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight, + uint32_t deadline = 0); }; diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp index fe59dfaa09..1730296ce8 100644 --- a/lib/ZipFile/ZipFile.cpp +++ b/lib/ZipFile/ZipFile.cpp @@ -441,7 +441,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo return data; } -bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize) { +bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize, uint32_t deadline) { const ScopedOpenClose zip{*this}; if (!zip) return false; @@ -465,6 +465,11 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch size_t remaining = inflatedDataSize; while (remaining > 0) { + if (deadline != 0 && static_cast(millis() - deadline) >= 0) { + LOG_ERR("ZIP", "Read deadline exceeded (stored)"); + free(buffer); + return false; + } const size_t dataRead = file.read(buffer, remaining < chunkSize ? remaining : chunkSize); if (dataRead == 0) { LOG_ERR("ZIP", "Could not read more bytes"); @@ -516,6 +521,10 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch size_t totalProduced = 0; while (true) { + if (deadline != 0 && static_cast(millis() - deadline) >= 0) { + LOG_ERR("ZIP", "Decompress deadline exceeded after %zu bytes", totalProduced); + break; + } size_t produced; const InflateStatus status = ctx.reader.readAtMost(outputBuffer, chunkSize, &produced); diff --git a/lib/ZipFile/ZipFile.h b/lib/ZipFile/ZipFile.h index 60c97a4cf9..a108d49594 100644 --- a/lib/ZipFile/ZipFile.h +++ b/lib/ZipFile/ZipFile.h @@ -68,5 +68,5 @@ class ZipFile { // Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple // These functions will open and close the zip as needed uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false); - bool readFileToStream(const char* filename, Print& out, size_t chunkSize); + bool readFileToStream(const char* filename, Print& out, size_t chunkSize, uint32_t deadline = 0); }; From ebee2a337c5ab10b15a6966edb77b0aa332c91ff Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 24 Apr 2026 00:45:08 +0200 Subject: [PATCH 63/93] feat: add theme support for cover settings with classical book cover and drawBitmap fallback - GfxRenderer: drawBitmap/drawBitmap1Bit now return bool for error detection - UITheme: add getAllCoverHeights() for thumbnail generation - BaseTheme: add BookCoverParams struct, drawClassicalBookCover method, stale buffer clearing, and drawBitmap failure fallback to placeholder - LyraTheme/Lyra3CoversTheme: gate cover rendering on coverDisabled flag - Replace Bookerly font references with Noto Serif (master licensing change) --- lib/GfxRenderer/GfxRenderer.cpp | 13 +- lib/GfxRenderer/GfxRenderer.h | 4 +- src/components/UITheme.cpp | 2 + src/components/UITheme.h | 2 + src/components/themes/BaseTheme.cpp | 277 +++++++++--------- src/components/themes/BaseTheme.h | 7 + .../themes/lyra/Lyra3CoversTheme.cpp | 48 +-- src/components/themes/lyra/LyraTheme.cpp | 13 +- 8 files changed, 189 insertions(+), 177 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 32ea4c0759..6492d5f3ca 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -786,11 +786,10 @@ void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, con void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, const float cropX, const float cropY) const { - if (fontCacheManager_ && fontCacheManager_->isScanning()) return; + if (fontCacheManager_ && fontCacheManager_->isScanning()) return false; // For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit) if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) { - drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight); - return; + return drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight); } float scale = 1.0f; @@ -832,7 +831,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con LOG_ERR("GFX", "!! Failed to allocate BMP row buffers"); free(outputRow); free(rowBytes); - return; + return false; } // --- Pre-compute everything that is constant for the entire render --- @@ -927,7 +926,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con LOG_ERR("GFX", "Failed to read row %d from bitmap", bmpY); free(outputRow); free(rowBytes); - return; + return false; } if (screenY < 0) { @@ -1002,6 +1001,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con free(outputRow); free(rowBytes); + return true; } void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth, @@ -1026,7 +1026,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, LOG_ERR("GFX", "!! Failed to allocate 1-bit BMP row buffers"); free(outputRow); free(rowBytes); - return; + return false; } for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) { @@ -1074,6 +1074,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, free(outputRow); free(rowBytes); + return true; } void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state) const { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index c455deb440..6de6b4adbd 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -168,9 +168,9 @@ class GfxRenderer { bool roundBottomLeft, bool roundBottomRight, Color color) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawIcon(const uint8_t bitmap[], int x, int y, int width, int height) const; - void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, + bool drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0) const; - void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const; + bool drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const; void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const; // Text diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 0fda24c5af..069c422d01 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -111,6 +111,8 @@ std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight return coverBmpPath; } +std::vector UITheme::getAllCoverHeights() { return {226, 400}; } + UIIcon UITheme::getFileIcon(const std::string& filename) { if (filename.back() == '/') { return Folder; diff --git a/src/components/UITheme.h b/src/components/UITheme.h index 6660254124..360b6904fc 100644 --- a/src/components/UITheme.h +++ b/src/components/UITheme.h @@ -4,6 +4,7 @@ #include #include +#include #include "CrossPointSettings.h" #include "components/themes/BaseTheme.h" @@ -27,6 +28,7 @@ class UITheme { static int getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, bool hasSubtitle, int extraReservedHeight = 0); static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight); + static std::vector getAllCoverHeights(); static UIIcon getFileIcon(const std::string& filename); static int getStatusBarHeight(); static int getProgressBarHeight(); diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 0650948b7a..64619c36e8 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -415,16 +415,17 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: const bool hasContinueReading = !recentBooks.empty(); const bool bookSelected = hasContinueReading && selectorIndex == 0; - // --- Top "book" card for the current title (selectorIndex == 0) --- - // When there's no cover image, use fixed size (half screen) - // When there's cover image, adapt width to image aspect ratio, keep height fixed at 400px - const int baseHeight = rect.height; // Fixed height (400px) + LOG_DBG("THEME", "drawRecentBookCover: hasContinue=%d selected=%d coverRendered=%d bufferStored=%d bufferRestored=%d", + hasContinueReading, bookSelected, coverRendered, coverBufferStored, bufferRestored); + + const int baseHeight = rect.height; int bookWidth, bookX; bool hasCoverImage = false; - if (hasContinueReading && !recentBooks[0].coverBmpPath.empty()) { - // Try to get actual image dimensions from BMP header + const bool skipCover = hasContinueReading && recentBooks[0].coverDisabled; + + if (hasContinueReading && !recentBooks[0].coverBmpPath.empty() && !skipCover) { const std::string coverBmpPath = UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, BaseMetrics::values.homeCoverHeight); @@ -436,189 +437,95 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: const int imgWidth = bitmap.getWidth(); const int imgHeight = bitmap.getHeight(); - // Calculate width based on aspect ratio, maintaining baseHeight if (imgWidth > 0 && imgHeight > 0) { const float aspectRatio = static_cast(imgWidth) / static_cast(imgHeight); bookWidth = static_cast(baseHeight * aspectRatio); - // Ensure width doesn't exceed reasonable limits (max 90% of screen width) const int maxWidth = static_cast(rect.width * 0.9f); if (bookWidth > maxWidth) { bookWidth = maxWidth; } } else { - bookWidth = rect.width / 2; // Fallback + bookWidth = baseHeight * 2 / 3; } } + file.close(); } } if (!hasCoverImage) { - // No cover: use half screen size - bookWidth = rect.width / 2; + bookWidth = baseHeight * 2 / 3; + if (bufferRestored) { + LOG_DBG("THEME", "drawRecentBookCover: clearing stale buffer (no cover, bufferRestored)"); + renderer.fillRect(rect.x, rect.y, rect.width, rect.height, false); + bufferRestored = false; + coverRendered = false; + coverBufferStored = false; + } } bookX = rect.x + (rect.width - bookWidth) / 2; const int bookY = rect.y; const int bookHeight = baseHeight; - // Bookmark dimensions (used in multiple places) - const int bookmarkWidth = bookWidth / 8; - const int bookmarkHeight = bookHeight / 5; - const int bookmarkX = bookX + bookWidth - bookmarkWidth - 10; - const int bookmarkY = bookY + 5; - - // Draw book card regardless, fill with message based on `hasContinueReading` { - // Draw cover image as background if available (inside the box) - // Only load from SD on first render, then use stored buffer - - if (hasContinueReading && !recentBooks[0].coverBmpPath.empty() && !coverRendered) { + if (hasContinueReading && !recentBooks[0].coverBmpPath.empty() && !coverRendered && !skipCover) { const std::string coverBmpPath = UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, BaseMetrics::values.homeCoverHeight); - // First time: load cover from SD and render FsFile file; if (Storage.openFileForRead("HOME", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { LOG_DBG("THEME", "Rendering bmp"); - // Draw the cover image (bookWidth and bookHeight already match image aspect ratio) - renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight); - - // Draw border around the card - renderer.drawRect(bookX, bookY, bookWidth, bookHeight); - - // No bookmark ribbon when cover is shown - it would just cover the art - - // Store the buffer with cover image for fast navigation - coverBufferStored = storeCoverBuffer(); - coverRendered = coverBufferStored; // Only consider it rendered if we successfully stored the buffer - - // First render: if selected, draw selection indicators now - if (bookSelected) { - LOG_DBG("THEME", "Drawing selection"); - renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2); - renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4); + if (renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight)) { + renderer.drawRect(bookX, bookY, bookWidth, bookHeight); + coverRendered = true; + coverBufferStored = storeCoverBuffer(); + + if (bookSelected) { + LOG_DBG("THEME", "Drawing selection"); + renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2, true); + renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4, true); + } + } else { + LOG_DBG("THEME", "drawBitmap failed, falling back to placeholder"); + hasCoverImage = false; + bookWidth = baseHeight * 2 / 3; + bookX = rect.x + (rect.width - bookWidth) / 2; } } + file.close(); } } if (!bufferRestored && !coverRendered) { - // No cover image: draw border or fill, plus bookmark as visual flair if (bookSelected) { renderer.fillRect(bookX, bookY, bookWidth, bookHeight); - } else { - renderer.drawRect(bookX, bookY, bookWidth, bookHeight); - } - - // Draw bookmark ribbon when no cover image (visual decoration) - if (hasContinueReading) { - const int notchDepth = bookmarkHeight / 3; - const int centerX = bookmarkX + bookmarkWidth / 2; - - const int xPoints[5] = { - bookmarkX, // top-left - bookmarkX + bookmarkWidth, // top-right - bookmarkX + bookmarkWidth, // bottom-right - centerX, // center notch point - bookmarkX // bottom-left - }; - const int yPoints[5] = { - bookmarkY, // top-left - bookmarkY, // top-right - bookmarkY + bookmarkHeight, // bottom-right - bookmarkY + bookmarkHeight - notchDepth, // center notch point - bookmarkY + bookmarkHeight // bottom-left - }; - - // Draw bookmark ribbon (inverted if selected) - renderer.fillPolygon(xPoints, yPoints, 5, !bookSelected); } } - // If buffer was restored, draw selection indicators if needed if (bufferRestored && bookSelected && coverRendered) { - // Draw selection border (no bookmark inversion needed since cover has no bookmark) - renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2); - renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4); - } else if (!coverRendered && !bufferRestored) { - // Selection border already handled above in the no-cover case + renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2, true); + renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4, true); } } if (hasContinueReading) { - const std::string& lastBookTitle = recentBooks[0].title; - const std::string& lastBookAuthor = recentBooks[0].author; - - // Invert text colors based on selection state: - // - With cover: selected = white text on black box, unselected = black text on white box - // - Without cover: selected = white text on black card, unselected = black text on white card - - auto lines = renderer.wrappedText(UI_12_FONT_ID, lastBookTitle.c_str(), bookWidth - 40, 3); - - // Book title text - int totalTextHeight = renderer.getLineHeight(UI_12_FONT_ID) * static_cast(lines.size()); - if (!lastBookAuthor.empty()) { - totalTextHeight += renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2; - } - - // Vertically center the title block within the card - int titleYStart = bookY + (bookHeight - totalTextHeight) / 2; - - const auto truncatedAuthor = lastBookAuthor.empty() - ? std::string{} - : renderer.truncatedText(UI_10_FONT_ID, lastBookAuthor.c_str(), bookWidth - 40); - - // If cover image was rendered, draw box behind title and author - if (coverRendered) { - constexpr int boxPadding = 8; - // Calculate the max text width for the box - int maxTextWidth = 0; - for (const auto& line : lines) { - const int lineWidth = renderer.getTextWidth(UI_12_FONT_ID, line.c_str()); - if (lineWidth > maxTextWidth) { - maxTextWidth = lineWidth; - } - } - if (!truncatedAuthor.empty()) { - const int authorWidth = renderer.getTextWidth(UI_10_FONT_ID, truncatedAuthor.c_str()); - if (authorWidth > maxTextWidth) { - maxTextWidth = authorWidth; - } - } - - const int boxWidth = maxTextWidth + boxPadding * 2; - const int boxHeight = totalTextHeight + boxPadding * 2; - const int boxX = rect.x + (rect.width - boxWidth) / 2; - const int boxY = titleYStart - boxPadding; - - // Draw box (inverted when selected: black box instead of white) - renderer.fillRect(boxX, boxY, boxWidth, boxHeight, bookSelected); - // Draw border around the box (inverted when selected: white border instead of black) - renderer.drawRect(boxX, boxY, boxWidth, boxHeight, !bookSelected); - } - - for (const auto& line : lines) { - renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected); - titleYStart += renderer.getLineHeight(UI_12_FONT_ID); - } + const char* continueText = tr(STR_CONTINUE_READING); + const int continueTextW = renderer.getTextWidth(UI_10_FONT_ID, continueText); - if (!truncatedAuthor.empty()) { - titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2; - renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, truncatedAuthor.c_str(), !bookSelected); + if (!hasCoverImage) { + BookCoverParams coverParams{recentBooks[0].title, recentBooks[0].author}; + drawClassicalBookCover(renderer, bookX, bookY, bookWidth, bookHeight, coverParams, bookSelected, continueTextW); + renderer.drawRect(bookX, bookY, bookWidth, bookHeight, 3, true); } - // "Continue Reading" label at the bottom - const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2; - if (coverRendered) { - // Draw box behind "Continue Reading" text (inverted when selected: black box instead of white) - const char* continueText = tr(STR_CONTINUE_READING); - const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText); + const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2 - 2; + if (hasCoverImage) { constexpr int continuePadding = 6; - const int continueBoxWidth = continueTextWidth + continuePadding * 2; + const int continueBoxWidth = continueTextW + continuePadding * 2; const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding; const int continueBoxX = rect.x + (rect.width - continueBoxWidth) / 2; const int continueBoxY = continueY - continuePadding / 2; @@ -626,14 +533,12 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected); renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected); } else { - renderer.drawCenteredText(UI_10_FONT_ID, continueY, tr(STR_CONTINUE_READING), !bookSelected); + renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected); } } else { - // No book to continue reading - const int y = - bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - renderer.getLineHeight(UI_10_FONT_ID)) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, y, "No open book"); - renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), "Start reading below"); + renderer.drawRect(bookX, bookY, bookWidth, bookHeight, 3, true); + const int textLineH = renderer.getLineHeight(UI_12_FONT_ID); + renderer.drawCenteredText(UI_12_FONT_ID, bookY + (bookHeight - textLineH) / 2, tr(STR_NO_OPEN_BOOK), true); } } @@ -888,3 +793,87 @@ void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const ch renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert); } } + +void BaseTheme::drawClassicalBookCover(GfxRenderer& renderer, int x, int y, int w, int h, const BookCoverParams& params, + bool inverted, int continueTextWidth) const { + renderer.fillRect(x + 1, y + 1, w - 2, h - 2, inverted); + + renderer.drawRect(x + 10, y + 10, 12, 12, !inverted); + renderer.drawRect(x + w - 22, y + 10, 12, 12, !inverted); + renderer.drawRect(x + 10, y + h - 22, 12, 12, !inverted); + renderer.drawRect(x + w - 22, y + h - 22, 12, 12, !inverted); + + const int innerL = x + 21; + const int innerR = x + w - 22; + const int innerT = y + 21; + const int innerB = y + h - 22; + const int innerCX = x + w / 2; + renderer.drawLine(innerL, innerT, innerR, innerT, !inverted); + renderer.drawLine(innerL, innerT, innerL, innerB, !inverted); + renderer.drawLine(innerR, innerT, innerR, innerB, !inverted); + const int textGapHalf = (continueTextWidth / 2) + 8; + renderer.drawLine(innerL, innerB, innerCX - textGapHalf, innerB, !inverted); + renderer.drawLine(innerCX + textGapHalf, innerB, innerR, innerB, !inverted); + + const int t = 2; + const int s = 30; + const int midY = y + h / 2; + + renderer.fillRect(x + 10, y + s, t, midY - y - s, !inverted); + renderer.fillRect(x + 10, y + s, s - 10 + t, t, !inverted); + renderer.fillRect(x + s, y + 10, t, s - 10, !inverted); + renderer.fillRect(x + s, y + 10, w - 2 * s, t, !inverted); + renderer.fillRect(x + w - 12, y + s, t, h - 2 * s, !inverted); + renderer.fillRect(x + w - 10 - (s - 10 + t), y + s, s - 10 + t, t, !inverted); + renderer.fillRect(x + w - 10 - (s - 10 + t), y + 10, t, s - 10, !inverted); + renderer.fillRect(x + w - 10 - (s - 10 + t), y + h - s - t, s - 10 + t, t, !inverted); + renderer.fillRect(x + w - 10 - (s - 10 + t), y + h - s, t, s - 10, !inverted); + renderer.fillRect(x + s, y + h - 12, w - 2 * s, t, !inverted); + renderer.fillRect(x + s, y + h - s, t, s - 10, !inverted); + renderer.fillRect(x + 10, y + h - s - t, s - 10 + t, t, !inverted); + renderer.fillRect(x + 10, midY, t, y + h - s - midY, !inverted); + + const int titleMaxW = w - 130; + auto titleLines = renderer.wrappedText(NOTOSERIF_12_FONT_ID, params.title.c_str(), titleMaxW, 3); + + const int lineH = renderer.getLineHeight(NOTOSERIF_12_FONT_ID); + const int gap = lineH / 2; + + std::vector authorLines; + int authorLineCount = 0; + if (!params.author.empty()) { + authorLines = renderer.wrappedText(NOTOSERIF_12_FONT_ID, params.author.c_str(), titleMaxW, 2); + authorLineCount = static_cast(authorLines.size()); + } + + int totalBlockH = (static_cast(titleLines.size()) + authorLineCount) * lineH + 4 * gap + 1; + + const int center = y + (innerB - innerT) * 45 / 100; + const int blockY = center - totalBlockH / 2; + + renderer.fillRect(x + 42, blockY, w - 84, totalBlockH + 16, false); + renderer.drawRect(x + 42, blockY, w - 84, totalBlockH + 16, 2, true); + renderer.drawRect(x + 50, blockY + 8, w - 100, totalBlockH, 1, true); + + int centerX = x + w / 2; + int textY = blockY + 8 + gap; + + for (const auto& line : titleLines) { + int tw = renderer.getTextWidth(NOTOSERIF_12_FONT_ID, line.c_str(), EpdFontFamily::BOLD); + renderer.drawText(NOTOSERIF_12_FONT_ID, centerX - tw / 2, textY, line.c_str(), true, EpdFontFamily::BOLD); + textY += lineH; + } + + textY += gap; + if (!authorLines.empty()) { + renderer.drawLine(centerX - 40, textY, centerX + 40, textY, true); + textY += 1; + } + textY += gap; + + for (const auto& line : authorLines) { + int aw = renderer.getTextWidth(NOTOSERIF_12_FONT_ID, line.c_str()); + renderer.drawText(NOTOSERIF_12_FONT_ID, centerX - aw / 2, textY, line.c_str(), true); + textY += lineH; + } +} diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index d21821ecb4..ec4167061d 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -9,6 +9,11 @@ class GfxRenderer; struct RecentBook; +struct BookCoverParams { + std::string title; + std::string author; +}; + struct Rect { int x; int y; @@ -163,6 +168,8 @@ class BaseTheme { const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal, bool inactiveSelection = false) const; virtual bool showsFileIcons() const { return false; } + void drawClassicalBookCover(GfxRenderer& renderer, int x, int y, int w, int h, const BookCoverParams& params, + bool inverted = false, int continueTextWidth = 0) const; // Shared constants and helpers for battery drawing (used by all themes) static constexpr int batteryPercentSpacing = 4; diff --git a/src/components/themes/lyra/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index 68d8b2345f..9f93f3769d 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -38,28 +38,34 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con if (coverPath.empty()) { hasCover = false; } else { - const std::string coverBmpPath = - UITheme::getCoverThumbPath(coverPath, Lyra3CoversMetrics::values.homeCoverHeight); - - // First time: load cover from SD and render - FsFile file; - if (Storage.openFileForRead("HOME", coverBmpPath, file)) { - Bitmap bitmap(file); - if (bitmap.parseHeaders() == BmpReaderError::Ok) { - float coverHeight = static_cast(bitmap.getHeight()); - float coverWidth = static_cast(bitmap.getWidth()); - float ratio = coverWidth / coverHeight; - const float tileRatio = static_cast(tileWidth - 2 * hPaddingInSelection) / - static_cast(Lyra3CoversMetrics::values.homeCoverHeight); - float cropX = 1.0f - (tileRatio / ratio); - - renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, - tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::values.homeCoverHeight, - cropX); - } else { - hasCover = false; + const bool skipCover = recentBooks[i].coverDisabled; + if (skipCover) { + hasCover = false; + } else { + const std::string coverBmpPath = + UITheme::getCoverThumbPath(coverPath, Lyra3CoversMetrics::values.homeCoverHeight); + + FsFile file; + if (Storage.openFileForRead("HOME", coverBmpPath, file)) { + Bitmap bitmap(file); + if (bitmap.parseHeaders() == BmpReaderError::Ok) { + float coverHeight = static_cast(bitmap.getHeight()); + float coverWidth = static_cast(bitmap.getWidth()); + float ratio = coverWidth / coverHeight; + const float tileRatio = static_cast(tileWidth - 2 * hPaddingInSelection) / + static_cast(Lyra3CoversMetrics::values.homeCoverHeight); + float cropX = 1.0f - (tileRatio / ratio); + + if (!renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, + tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::homeCoverHeight, + cropX)) { + hasCover = false; + } + } else { + hasCover = false; + } + file.close(); } - file.close(); } } // Draw either way diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 1e11f2364f..e5cb06afc0 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -419,7 +419,8 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: std::string coverPath = book.coverBmpPath; bool hasCover = true; int tileX = LyraMetrics::values.contentSidePadding; - if (coverPath.empty()) { + const bool skipCover = book.coverDisabled; + if (coverPath.empty() || skipCover) { hasCover = false; } else { const std::string coverBmpPath = UITheme::getCoverThumbPath(coverPath, LyraMetrics::values.homeCoverHeight); @@ -430,12 +431,16 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { coverWidth = bitmap.getWidth(); - renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth, - LyraMetrics::values.homeCoverHeight); + if (!renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth, + LyraMetrics::values.homeCoverHeight)) { + hasCover = false; + } } else { hasCover = false; } file.close(); + } else { + hasCover = false; } } @@ -451,8 +456,8 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); } + coverRendered = true; coverBufferStored = storeCoverBuffer(); - coverRendered = coverBufferStored; // Only consider it rendered if we successfully stored the buffer } bool bookSelected = (selectorIndex == 0); From 1dffa6a7a29e680f72a9e14c7b8b582e7372f5e8 Mon Sep 17 00:00:00 2001 From: pablohc Date: Sun, 26 Apr 2026 01:01:47 +0200 Subject: [PATCH 64/93] fix: compilation errors in deadline pipeline and theme cover rendering - GfxRenderer: drawBitmap/drawBitmap1Bit return bool instead of void - JpegToBmpConverter/PngToBmpConverter: add missing deadline param to internal callers - Lyra3CoversTheme: fix .values. access and handle drawBitmap failure --- lib/GfxRenderer/GfxRenderer.cpp | 6 +++--- lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 4 ++-- lib/PngToBmpConverter/PngToBmpConverter.cpp | 8 ++++---- src/components/themes/lyra/Lyra3CoversTheme.cpp | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 6492d5f3ca..e6f1eadbc2 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -784,7 +784,7 @@ void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, con display.drawImageTransparent(bitmap, y, getScreenWidth() - width - x, height, width); } -void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, +bool GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, const float cropX, const float cropY) const { if (fontCacheManager_ && fontCacheManager_->isScanning()) return false; // For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit) @@ -1004,7 +1004,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con return true; } -void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth, +bool GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight) const { float scale = 1.0f; bool isScaled = false; @@ -1035,7 +1035,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, LOG_ERR("GFX", "Failed to read row %d from 1-bit bitmap", bmpY); free(outputRow); free(rowBytes); - return; + return false; } // Calculate screen Y based on whether BMP is top-down or bottom-up diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index e1709ca9e7..80e2e36efe 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -549,13 +549,13 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bo // Use runtime display dimensions (swapped for portrait cover sizing) const int targetWidth = display.getDisplayHeight(); const int targetHeight = display.getDisplayWidth(); - return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetWidth, targetHeight, false, crop); + return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetWidth, targetHeight, false, crop, 0); } // Convert with custom target size (for thumbnails, 2-bit) bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight) { - return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, false); + return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, false, false, 0); } // Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering diff --git a/lib/PngToBmpConverter/PngToBmpConverter.cpp b/lib/PngToBmpConverter/PngToBmpConverter.cpp index ebe606d422..3a9fc17fa8 100644 --- a/lib/PngToBmpConverter/PngToBmpConverter.cpp +++ b/lib/PngToBmpConverter/PngToBmpConverter.cpp @@ -396,7 +396,7 @@ static void convertScanlineToGray(const PngDecodeContext& ctx, uint8_t* grayRow) } bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight, - bool oneBit, bool crop, uint32_t deadline) { + bool oneBit, bool crop, uint32_t deadline) { LOG_DBG("PNG", "Converting PNG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight); // Verify PNG signature @@ -830,15 +830,15 @@ bool PngToBmpConverter::pngFileToBmpStream(FsFile& pngFile, Print& bmpOut, bool // Use runtime display dimensions (swapped for portrait cover sizing) const int targetWidth = display.getDisplayHeight(); const int targetHeight = display.getDisplayWidth(); - return pngFileToBmpStreamInternal(pngFile, bmpOut, targetWidth, targetHeight, false, crop); + return pngFileToBmpStreamInternal(pngFile, bmpOut, targetWidth, targetHeight, false, crop, 0); } bool PngToBmpConverter::pngFileToBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight) { - return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, false); + return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, false, false, 0); } bool PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, - int targetMaxHeight, uint32_t deadline) { + int targetMaxHeight, uint32_t deadline) { return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true, deadline); } diff --git a/src/components/themes/lyra/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index 9f93f3769d..2e294a4492 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -57,8 +57,8 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con float cropX = 1.0f - (tileRatio / ratio); if (!renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, - tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::homeCoverHeight, - cropX)) { + tileWidth - 2 * hPaddingInSelection, + Lyra3CoversMetrics::values.homeCoverHeight, cropX)) { hasCover = false; } } else { From 6cd4556892ddd5b057b8c88ee87a3876f3a06629 Mon Sep 17 00:00:00 2001 From: pablohc Date: Sun, 26 Apr 2026 01:12:16 +0200 Subject: [PATCH 65/93] style: fix clang-format for LLVM 21 CI --- lib/Epub/Epub.cpp | 5 ++--- lib/Epub/Epub.h | 3 +-- lib/JpegToBmpConverter/JpegToBmpConverter.h | 4 ++-- lib/PngToBmpConverter/PngToBmpConverter.h | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 2f7e769910..6958dfb4e2 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -689,9 +689,8 @@ bool Epub::generateThumbBmp(int height, uint32_t deadline) const { } 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, - deadline); + const bool success = PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH, + THUMB_TARGET_HEIGHT, deadline); // Explicitly close() files before calling Storage.remove() coverPng.close(); thumbBmp.close(); diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index 0d33bca6dd..31508354aa 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -58,8 +58,7 @@ class Epub { bool generateThumbBmp(int height, uint32_t deadline = 0) 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, - uint32_t deadline = 0) const; + bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize, uint32_t deadline = 0) const; bool getItemSize(const std::string& itemHref, size_t* size) const; BookMetadataCache::SpineEntry getSpineItem(int spineIndex) const; BookMetadataCache::TocEntry getTocItem(int tocIndex) const; diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.h b/lib/JpegToBmpConverter/JpegToBmpConverter.h index 5a86730f97..a80a66510d 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.h +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.h @@ -7,7 +7,7 @@ class ZipFile; class JpegToBmpConverter { static bool jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight, - bool oneBit, bool crop, uint32_t deadline); + bool oneBit, bool crop, uint32_t deadline); public: static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop = true); @@ -15,5 +15,5 @@ class JpegToBmpConverter { static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight); // Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight, - uint32_t deadline = 0); + uint32_t deadline = 0); }; diff --git a/lib/PngToBmpConverter/PngToBmpConverter.h b/lib/PngToBmpConverter/PngToBmpConverter.h index bd7f492f64..c723ffa4bf 100644 --- a/lib/PngToBmpConverter/PngToBmpConverter.h +++ b/lib/PngToBmpConverter/PngToBmpConverter.h @@ -6,7 +6,7 @@ class Print; class PngToBmpConverter { static bool pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight, bool oneBit, - bool crop, uint32_t deadline); + bool crop, uint32_t deadline); public: static bool pngFileToBmpStream(FsFile& pngFile, Print& bmpOut, bool crop = true); From 5616d2d71c454aa041a1b203a6adb579c45075f4 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Tue, 12 May 2026 23:37:56 +0200 Subject: [PATCH 66/93] fix: jump page on hold in font family and language selection (#1925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Holding the navigation button in **Settings → Reader → Font family** now advances the selection by a full visible page instead of one item at a time. - Same fix applied to **Settings → Language**, which had the same one-item-only behavior. - Mirrors the pattern already used in font download (ceb3fed) and chapter selection screens. ## Test plan - [ ] Settings → Reader → Font family: tap moves by one item; hold jumps a page (wraps at ends) - [ ] Settings → Language: tap moves by one item; hold jumps a page (wraps at ends) --- .../settings/FontSelectionActivity.cpp | 21 +++++++++++++++---- .../settings/LanguageSelectActivity.cpp | 12 +++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/activities/settings/FontSelectionActivity.cpp b/src/activities/settings/FontSelectionActivity.cpp index 87b3f0f77b..1e5e568cd3 100644 --- a/src/activities/settings/FontSelectionActivity.cpp +++ b/src/activities/settings/FontSelectionActivity.cpp @@ -60,13 +60,26 @@ void FontSelectionActivity::loop() { return; } - buttonNavigator_.onNextRelease([this] { - selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, static_cast(fonts_.size())); + const int listSize = static_cast(fonts_.size()); + const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false); + + buttonNavigator_.onNextRelease([this, listSize] { + selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, listSize); + requestUpdate(); + }); + + buttonNavigator_.onPreviousRelease([this, listSize] { + selectedIndex_ = ButtonNavigator::previousIndex(selectedIndex_, listSize); + requestUpdate(); + }); + + buttonNavigator_.onNextContinuous([this, listSize, pageItems] { + selectedIndex_ = ButtonNavigator::nextPageIndex(selectedIndex_, listSize, pageItems); requestUpdate(); }); - buttonNavigator_.onPreviousRelease([this] { - selectedIndex_ = ButtonNavigator::previousIndex(selectedIndex_, static_cast(fonts_.size())); + buttonNavigator_.onPreviousContinuous([this, listSize, pageItems] { + selectedIndex_ = ButtonNavigator::previousPageIndex(selectedIndex_, listSize, pageItems); requestUpdate(); }); } diff --git a/src/activities/settings/LanguageSelectActivity.cpp b/src/activities/settings/LanguageSelectActivity.cpp index ab5fd02c8b..231cba5216 100644 --- a/src/activities/settings/LanguageSelectActivity.cpp +++ b/src/activities/settings/LanguageSelectActivity.cpp @@ -37,6 +37,8 @@ void LanguageSelectActivity::loop() { return; } + const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false); + // Handle navigation buttonNavigator.onNextRelease([this] { selectedIndex = ButtonNavigator::nextIndex(static_cast(selectedIndex), totalItems); @@ -47,6 +49,16 @@ void LanguageSelectActivity::loop() { selectedIndex = ButtonNavigator::previousIndex(static_cast(selectedIndex), totalItems); requestUpdate(); }); + + buttonNavigator.onNextContinuous([this, pageItems] { + selectedIndex = ButtonNavigator::nextPageIndex(static_cast(selectedIndex), totalItems, pageItems); + requestUpdate(); + }); + + buttonNavigator.onPreviousContinuous([this, pageItems] { + selectedIndex = ButtonNavigator::previousPageIndex(static_cast(selectedIndex), totalItems, pageItems); + requestUpdate(); + }); } void LanguageSelectActivity::handleSelection() { From bf18f4bc6f4d36d3a77d56c8ed900ff477a82747 Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Wed, 13 May 2026 08:26:56 -0500 Subject: [PATCH 67/93] refactor: Removed SdCardFontGlobals.h (#1962) ## Summary First of several changes to decouple and clean up SD card fonts integration. This change eliminates SdCardFontGlobals.h: - Simply declare the `extern SdCardFontSystem sdFontSystem` in SdCardFontSystem.h. - `ActivityManager::goToReader` should not care about loading SD card fonts. Instead do the same work in `ReaderActivity::onEnter`, after the previous activity has exited and after ReaderActivity has validated the file path. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- src/SdCardFontGlobals.h | 12 ------------ src/SdCardFontSystem.h | 3 +++ src/activities/ActivityManager.cpp | 2 -- src/activities/reader/ReaderActivity.cpp | 3 +++ src/activities/settings/FontDownloadActivity.cpp | 2 +- src/activities/settings/SettingsActivity.cpp | 2 +- src/main.cpp | 2 -- src/network/CrossPointWebServer.cpp | 1 - 8 files changed, 8 insertions(+), 19 deletions(-) delete mode 100644 src/SdCardFontGlobals.h diff --git a/src/SdCardFontGlobals.h b/src/SdCardFontGlobals.h deleted file mode 100644 index fec123a081..0000000000 --- a/src/SdCardFontGlobals.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "SdCardFontSystem.h" - -class GfxRenderer; - -// Global SD card font system instance (defined in main.cpp). -extern SdCardFontSystem sdFontSystem; - -// Ensure the correct SD card font family is loaded for current settings. -// Defined in main.cpp; call before entering the reader or after settings change. -extern void ensureSdFontLoaded(); diff --git a/src/SdCardFontSystem.h b/src/SdCardFontSystem.h index f053534389..6b8aef7f10 100644 --- a/src/SdCardFontSystem.h +++ b/src/SdCardFontSystem.h @@ -50,3 +50,6 @@ class SdCardFontSystem { SdCardFontManager manager_; std::atomic registryDirty_{false}; }; + +// Global SD card font system instance (defined in main.cpp). +extern SdCardFontSystem sdFontSystem; diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index c81e6b95b5..96c3c7d0a4 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -5,7 +5,6 @@ #include #include "OpdsServerStore.h" -#include "SdCardFontGlobals.h" #include "boot_sleep/BootActivity.h" #include "boot_sleep/SleepActivity.h" #include "browser/OpdsBookBrowserActivity.h" @@ -194,7 +193,6 @@ void ActivityManager::goToBrowser() { } void ActivityManager::goToReader(std::string path) { - ensureSdFontLoaded(); replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); } diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index aa3c5d03fb..bee81a087d 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -6,6 +6,7 @@ #include "CrossPointSettings.h" #include "Epub.h" #include "EpubReaderActivity.h" +#include "SdCardFontSystem.h" #include "Txt.h" #include "TxtReaderActivity.h" #include "Xtc.h" @@ -110,6 +111,8 @@ void ReaderActivity::onEnter() { return; } + sdFontSystem.ensureLoaded(renderer); + currentBookPath = initialBookPath; if (isBmpFile(initialBookPath)) { onGoToBmpViewer(initialBookPath); diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index 0acba06226..27abc5758e 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -9,7 +9,7 @@ #include #include "MappedInputManager.h" -#include "SdCardFontGlobals.h" +#include "SdCardFontSystem.h" #include "activities/network/WifiSelectionActivity.h" #include "activities/util/ConfirmationActivity.h" #include "components/UITheme.h" diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index adf99acde8..afd707c6bf 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -13,7 +13,7 @@ #include "MappedInputManager.h" #include "OpdsServerListActivity.h" #include "OtaUpdateActivity.h" -#include "SdCardFontGlobals.h" +#include "SdCardFontSystem.h" #include "SdFirmwareUpdateActivity.h" #include "SettingsList.h" #include "StatusBarSettingsActivity.h" diff --git a/src/main.cpp b/src/main.cpp index 4fe44a0035..5f679d6313 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -197,8 +197,6 @@ void enterDeepSleep() { powerManager.startDeepSleep(gpio); } -void ensureSdFontLoaded() { sdFontSystem.ensureLoaded(renderer); } - void setupDisplayAndFonts() { display.begin(); renderer.begin(); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 60e4e9e21a..33a1348ddc 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -13,7 +13,6 @@ #include "CrossPointSettings.h" #include "FontInstaller.h" #include "OpdsServerStore.h" -#include "SdCardFontGlobals.h" #include "SdCardFontSystem.h" #include "SettingsList.h" #include "WebDAVHandler.h" From 70a4ced3ed1855c37e75900f0b8a9dd5b2656de1 Mon Sep 17 00:00:00 2001 From: Fabio Barbon Date: Wed, 13 May 2026 15:32:18 +0200 Subject: [PATCH 68/93] fix: KOSync authentication with Calibre-Web-Automated (#1951) ## Summary * **What is the goal of this PR?** Fix regression of Calibre-Web-Automated authentication * **What changes are included?** Trivial fix using ESP HTTP Client primitives ## Additional Context Calibre-Web-Automated KOSync stopped working after refactoring HTTP client to use `ESP HTTP Client`, resulting in a _Server error (try again later)_ error. --- ### AI Usage Did you use AI tools to help write this code? _**NO**_ --------- Co-authored-by: drbourbon --- lib/KOReaderSync/KOReaderSyncClient.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/KOReaderSync/KOReaderSyncClient.cpp b/lib/KOReaderSync/KOReaderSyncClient.cpp index e0bc410d19..1e4f512d96 100644 --- a/lib/KOReaderSync/KOReaderSyncClient.cpp +++ b/lib/KOReaderSync/KOReaderSyncClient.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -75,6 +74,11 @@ esp_http_client_handle_t createClient(const char* url, ResponseBuffer* buf, config.buffer_size_tx = HTTP_BUF_SIZE; config.crt_bundle_attach = esp_crt_bundle_attach; + // HTTP Basic Auth for Calibre-Web-Automated compatibility + config.username = KOREADER_STORE.getUsername().c_str(); + config.password = KOREADER_STORE.getPassword().c_str(); + config.auth_type = HTTP_AUTH_TYPE_BASIC; + esp_http_client_handle_t client = esp_http_client_init(&config); if (!client) return nullptr; @@ -87,16 +91,6 @@ esp_http_client_handle_t createClient(const char* url, ResponseBuffer* buf, return nullptr; } - // HTTP Basic Auth for Calibre-Web-Automated compatibility - std::string credentials = KOREADER_STORE.getUsername() + ":" + KOREADER_STORE.getPassword(); - String encoded = base64::encode(reinterpret_cast(credentials.data()), credentials.size()); - std::string authHeader = "Basic " + std::string(encoded.c_str()); - if (esp_http_client_set_header(client, "Authorization", authHeader.c_str()) != ESP_OK) { - LOG_ERR("KOSync", "Failed to set Authorization header"); - esp_http_client_cleanup(client); - return nullptr; - } - return client; } } // namespace From 863379923d01e65ab71a5484304978c0e11d31e5 Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Wed, 13 May 2026 21:19:47 -0500 Subject: [PATCH 69/93] refactor: Added utils for non-throwing memory allocation and scoped cleanup (#1832) ## Summary Pared down version of #1418. Following up on b5df6cb2b5f4415ec7ffa49b06c853a78b741f68. Added lib/Memory/Memory.h with: - `makeUniqueNoThrow` a `nothrow` wrapper for `std::make_unique` that return `nullptr` on OOM instead of calling `abort()` (the behavior of bare `new` with `-fno-exceptions`) - `ScopedCleanup` a helper to call a cleanup lambda on scope exit. These utilities help to write code that handles OOM scenarios gracefully, and consistently cleans up resources on scope exit. JpegToBmpConverter.cpp has been converted to use these utilities. Other files can be converted later. This will simplify some of the SD card font resource management in #1327. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- .skills/SKILL.md | 82 ++++++++++++----- lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 92 +++++++++---------- lib/Memory/Memory.h | 54 +++++++++++ 3 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 lib/Memory/Memory.h diff --git a/.skills/SKILL.md b/.skills/SKILL.md index 8e5e8f8f4a..b7b59dc732 100644 --- a/.skills/SKILL.md +++ b/.skills/SKILL.md @@ -58,6 +58,7 @@ find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i 6. `constexpr` First: Compile-time constants and lookup tables must be `constexpr`, not just `static const`. This moves computation to compile time, enables dead-branch elimination, and guarantees flash placement. Use `static constexpr` for class-level constants. 7. `std::vector` Pre-allocation: Always call `.reserve(N)` before any `push_back()` loop. Each growth event allocates a new block (2×), copies all elements, then frees the old one — three heap operations that fragment DRAM. When the final size is unknown, estimate conservatively. 8. SPIFFS Write Throttling: Never write a settings file on every user interaction. Guard all writes with a value-change check (`if (newVal == _current) return;`). Progress saves during reading must be debounced — write on activity exit or every N page turns, not on every turn. SPIFFS sectors have a finite erase cycle limit. +9. `new` is not nothrow on ESP32: With `-fno-exceptions`, bare `new` that fails calls `abort()` — it does NOT return `nullptr`. Always use `new (std::nothrow)` and null-check the result, or use `makeUniqueNoThrow()` from `lib/Memory/Memory.h`. Never write bare `new` for any fallible allocation. --- @@ -266,43 +267,78 @@ When a template is necessary, limit instantiations: use explicit template instan **Rules**: NO exceptions, NO abort(), ALWAYS log before error return -### Acceptable malloc/free Patterns +### Heap Buffer Allocation -**Source**: [src/activities/home/HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp), [lib/GfxRenderer/GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp) +**Prefer `makeUniqueNoThrow` over `malloc`.** Both are nothrow (return `nullptr` on OOM rather than calling `abort()`), but `malloc` requires a manual `free` on every return path — a common source of leaks. `makeUniqueNoThrow(size)` from `lib/Memory/Memory.h` frees automatically when it goes out of scope. -Despite "prefer stack allocation," malloc is acceptable for: -1. **Large temporary buffers** (> 256 bytes, won't fit on stack) -2. **One-time allocations** during activity initialization -3. **Bitmap rendering buffers** (variable size, used briefly) - -**Pattern**: +**Preferred pattern**: ```cpp -// Allocate -auto* buffer = static_cast(malloc(bufferSize)); +#include + +auto buffer = makeUniqueNoThrow(bufferSize); if (!buffer) { - LOG_ERR("MODULE", "malloc failed: %d bytes", bufferSize); - return false; // Handle allocation failure + LOG_ERR("MODULE", "OOM: %d bytes", bufferSize); + return false; } -// Use buffer -processData(buffer, bufferSize); +processData(buffer.get(), bufferSize); +// freed automatically — no manual free needed, no leak on early return +``` -// Free immediately after use -free(buffer); -buffer = nullptr; +**`malloc` or `new (std::nothrow)` are still acceptable** when the buffer must be passed to a C API that takes ownership and frees it itself (e.g., certain SDK callbacks). In that case follow the manual pattern: +```cpp +auto* buffer = static_cast(malloc(bufferSize)); // or new (std::nothrow) uint8_t[bufferSize] +if (!buffer) { + LOG_ERR("MODULE", "OOM: %d bytes", bufferSize); + return false; +} +sdkApiThatTakesOwnership(buffer, bufferSize); // SDK calls free() / delete[] ``` **Rules**: -- **ALWAYS check for nullptr** after malloc -- **Free immediately** after use (don't hold across multiple operations) -- **Set to nullptr** after free (avoid use-after-free) -- **Document size**: Comment why stack allocation was rejected +- **Prefer `makeUniqueNoThrow`** — automatic cleanup eliminates leak risk on error paths +- **ALWAYS check for nullptr** after any allocation and `LOG_ERR` before returning false +- **Raw allocation only** when a C API takes ownership; document why in a comment **Examples in codebase**: +- Memory utilities: [Memory.h](../lib/Memory/Memory.h) (`makeUniqueNoThrow`) - Cover image buffers: [HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp) -- Text chunk buffers: [TxtReaderActivity.cpp:259](../src/activities/reader/TxtReaderActivity.cpp) - Bitmap rendering: [GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp) -- OTA update buffer: [OtaUpdater.cpp:40](../src/network/OtaUpdater.cpp) + +### Heap Allocation with `new`: Always Use `makeUniqueNoThrow` + +**CRITICAL**: With `-fno-exceptions`, bare `new` on OOM calls `abort()` — it does NOT return `nullptr`. Always use `makeUniqueNoThrow` from `lib/Memory/Memory.h`, which wraps `new (std::nothrow)` and returns a `std::unique_ptr` that is null on OOM and automatically frees on scope exit. + +**Preferred pattern**: +```cpp +#include + +auto obj = makeUniqueNoThrow(args); +if (!obj) { LOG_ERR("MOD", "OOM: MyClass"); return false; } + +auto buf = makeUniqueNoThrow(size); +if (!buf) { LOG_ERR("MOD", "OOM: %d bytes", size); return false; } + +// Pass to C APIs via .get(); unique_ptr frees automatically on return +someApi(buf.get(), size); +``` + +**`new (std::nothrow)` directly is acceptable** when the object must be passed to a C API that takes ownership and calls `delete` itself: +```cpp +auto* obj = new (std::nothrow) MyClass(args); +if (!obj) { LOG_ERR("MOD", "OOM: MyClass"); return false; } +sdkApiThatTakesOwnership(obj); // SDK calls delete +``` + +**Rules**: +- **Prefer `makeUniqueNoThrow`** — automatic cleanup eliminates leak risk on error paths +- **NEVER use bare `new`** — always `makeUniqueNoThrow` or `new (std::nothrow)` +- **ALWAYS `LOG_ERR` before returning false** on OOM +- **Use `.get()`** to pass the raw pointer to C-style APIs; ownership stays with the `unique_ptr` +- **`new (std::nothrow)` directly only** when a C API takes ownership; document why in a comment + +**Examples in codebase**: +- Memory utilities: [Memory.h](../lib/Memory/Memory.h) (`makeUniqueNoThrow`) --- diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index 80e2e36efe..38b271b850 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -4,10 +4,10 @@ #include #include #include +#include #include #include -#include #include "BitmapHelpers.h" @@ -211,19 +211,19 @@ struct BmpConvertCtx { // Accumulates one MCU row (up to MAX_MCU_HEIGHT source rows × srcWidth pixels) // Filled column-by-column as JPEGDEC callbacks arrive for the same MCU row - uint8_t* mcuBuf; + std::unique_ptr mcuBuf; // Y-axis area averaging accumulators (needsScaling only) int currentOutY; uint32_t nextOutY_srcStart; // 16.16 fixed-point boundary for the next output row - uint32_t* rowAccum; - uint32_t* rowCount; + std::unique_ptr rowAccum; + std::unique_ptr rowCount; - uint8_t* bmpRow; + std::unique_ptr bmpRow; - AtkinsonDitherer* atkinsonDitherer; - FloydSteinbergDitherer* fsDitherer; - Atkinson1BitDitherer* atkinson1BitDitherer; + std::unique_ptr atkinsonDitherer; + std::unique_ptr fsDitherer; + std::unique_ptr atkinson1BitDitherer; bool error; uint32_t deadline; // 0 = no limit @@ -231,7 +231,7 @@ struct BmpConvertCtx { // Write a fully-assembled output row (grayscale bytes, length outWidth) to BMP static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) { - memset(ctx->bmpRow, 0, ctx->bytesPerRow); + memset(ctx->bmpRow.get(), 0, ctx->bytesPerRow); if (USE_8BIT_OUTPUT && !ctx->oneBit) { for (int x = 0; x < ctx->outWidth; x++) { @@ -262,12 +262,12 @@ static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) ctx->fsDitherer->nextRow(); } - ctx->bmpOut->write(ctx->bmpRow, ctx->bytesPerRow); + ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow); } // Flush one scaled output row from Y-axis accumulators and advance currentOutY static void flushScaledRow(BmpConvertCtx* ctx) { - memset(ctx->bmpRow, 0, ctx->bytesPerRow); + memset(ctx->bmpRow.get(), 0, ctx->bytesPerRow); if (USE_8BIT_OUTPUT && !ctx->oneBit) { for (int x = 0; x < ctx->outWidth; x++) { @@ -301,7 +301,7 @@ static void flushScaledRow(BmpConvertCtx* ctx) { ctx->fsDitherer->nextRow(); } - ctx->bmpOut->write(ctx->bmpRow, ctx->bytesPerRow); + ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow); ctx->currentOutY++; } @@ -324,7 +324,7 @@ int bmpDrawCallback(JPEGDRAW* pDraw) { for (int r = 0; r < blockH && r < MAX_MCU_HEIGHT; r++) { const int copyW = (blockX + validW <= ctx->srcWidth) ? validW : (ctx->srcWidth - blockX); if (copyW <= 0) continue; - memcpy(ctx->mcuBuf + r * ctx->srcWidth + blockX, pixels + r * stride, copyW); + memcpy(ctx->mcuBuf.get() + r * ctx->srcWidth + blockX, pixels + r * stride, copyW); } // Wait for the last MCU column before processing any rows @@ -340,7 +340,7 @@ int bmpDrawCallback(JPEGDRAW* pDraw) { const int endRow = blockY + blockH; for (int y = blockY; y < endRow && y < ctx->srcHeight; y++) { - const uint8_t* srcRow = ctx->mcuBuf + (y - blockY) * ctx->srcWidth; + const uint8_t* srcRow = ctx->mcuBuf.get() + (y - blockY) * ctx->srcWidth; if (!ctx->needsScaling) { // 1:1 — outWidth == srcWidth, write directly @@ -370,8 +370,8 @@ int bmpDrawCallback(JPEGDRAW* pDraw) { flushScaledRow(ctx); ctx->nextOutY_srcStart = static_cast(ctx->currentOutY + 1) * ctx->scaleY_fp; if (srcY_fp >= ctx->nextOutY_srcStart) continue; - memset(ctx->rowAccum, 0, ctx->outWidth * sizeof(uint32_t)); - memset(ctx->rowCount, 0, ctx->outWidth * sizeof(uint32_t)); + memset(ctx->rowAccum.get(), 0, ctx->outWidth * sizeof(uint32_t)); + memset(ctx->rowCount.get(), 0, ctx->outWidth * sizeof(uint32_t)); } } } @@ -393,19 +393,20 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm s_jpegFile = &jpegFile; - JPEGDEC* jpeg = new (std::nothrow) JPEGDEC(); + const auto jpeg = makeUniqueNoThrow(); if (!jpeg) { - LOG_ERR("JPG", "Failed to allocate JPEG decoder"); + LOG_ERR("JPG", "OOM: JPEG decoder"); return false; } int rc = jpeg->open("", bmpJpegOpen, bmpJpegClose, bmpJpegRead, bmpJpegSeek, bmpDrawCallback); if (rc != 1) { LOG_ERR("JPG", "JPEG open failed (err=%d)", jpeg->getLastError()); - delete jpeg; return false; } + const ScopedCleanup cleanup{[&jpeg]() { jpeg->close(); }}; + const int srcWidth = jpeg->getWidth(); const int srcHeight = jpeg->getHeight(); @@ -417,8 +418,6 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm if (srcWidth <= 0 || srcHeight <= 0 || srcWidth > MAX_IMAGE_WIDTH || srcHeight > MAX_IMAGE_HEIGHT) { LOG_DBG("JPG", "Image too large or invalid (%dx%d), max supported: %dx%d", srcWidth, srcHeight, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT); - jpeg->close(); - delete jpeg; return false; } @@ -479,54 +478,49 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm ctx.error = false; ctx.deadline = deadline; - // RAII guard: frees all heap resources on any return path - struct Cleanup { - BmpConvertCtx& ctx; - JPEGDEC* jpeg; - ~Cleanup() { - delete[] ctx.rowAccum; - delete[] ctx.rowCount; - delete ctx.atkinsonDitherer; - delete ctx.fsDitherer; - delete ctx.atkinson1BitDitherer; - free(ctx.mcuBuf); - free(ctx.bmpRow); - jpeg->close(); - delete jpeg; - } - } cleanup{ctx, jpeg}; - // MCU row buffer: MAX_MCU_HEIGHT rows × srcWidth columns of grayscale - ctx.mcuBuf = static_cast(malloc(MAX_MCU_HEIGHT * srcWidth)); + ctx.mcuBuf = makeUniqueNoThrow(MAX_MCU_HEIGHT * srcWidth); if (!ctx.mcuBuf) { - LOG_ERR("JPG", "Failed to allocate MCU buffer (%d bytes)", MAX_MCU_HEIGHT * srcWidth); + LOG_ERR("JPG", "OOM: MCU buffer (%d bytes)", MAX_MCU_HEIGHT * srcWidth); return false; } - memset(ctx.mcuBuf, 0, MAX_MCU_HEIGHT * srcWidth); + memset(ctx.mcuBuf.get(), 0, MAX_MCU_HEIGHT * srcWidth); - ctx.bmpRow = static_cast(malloc(bytesPerRow)); + ctx.bmpRow = makeUniqueNoThrow(bytesPerRow); if (!ctx.bmpRow) { - LOG_ERR("JPG", "Failed to allocate BMP row buffer"); + LOG_ERR("JPG", "OOM: BMP row buffer"); return false; } if (needsScaling) { - ctx.rowAccum = new (std::nothrow) uint32_t[outWidth](); - ctx.rowCount = new (std::nothrow) uint32_t[outWidth](); + ctx.rowAccum = makeUniqueNoThrow(outWidth); + ctx.rowCount = makeUniqueNoThrow(outWidth); if (!ctx.rowAccum || !ctx.rowCount) { - LOG_ERR("JPG", "Failed to allocate scaling buffers"); + LOG_ERR("JPG", "OOM: scaling buffers"); return false; } ctx.nextOutY_srcStart = scaleY_fp; } if (oneBit) { - ctx.atkinson1BitDitherer = new (std::nothrow) Atkinson1BitDitherer(outWidth); + ctx.atkinson1BitDitherer = makeUniqueNoThrow(outWidth); + if (!ctx.atkinson1BitDitherer) { + LOG_ERR("JPG", "OOM: Atkinson1BitDitherer"); + return false; + } } else if (!USE_8BIT_OUTPUT) { if (USE_ATKINSON) { - ctx.atkinsonDitherer = new (std::nothrow) AtkinsonDitherer(outWidth); + ctx.atkinsonDitherer = makeUniqueNoThrow(outWidth); + if (!ctx.atkinsonDitherer) { + LOG_ERR("JPG", "OOM: AtkinsonDitherer"); + return false; + } } else if (USE_FLOYD_STEINBERG) { - ctx.fsDitherer = new (std::nothrow) FloydSteinbergDitherer(outWidth); + ctx.fsDitherer = makeUniqueNoThrow(outWidth); + if (!ctx.fsDitherer) { + LOG_ERR("JPG", "OOM: FloydSteinbergDitherer"); + return false; + } } } diff --git a/lib/Memory/Memory.h b/lib/Memory/Memory.h new file mode 100644 index 0000000000..0093a8526e --- /dev/null +++ b/lib/Memory/Memory.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Nothrow versions of std::make_unique. Return nullptr on allocation failure +// instead of calling abort() (the default when exceptions are disabled on ESP32). +// +// Single object: +// auto obj = makeUniqueNoThrow(); +// if (!obj) { LOG_ERR("TAG", "OOM"); return false; } +// +// Array: +// auto buf = makeUniqueNoThrow(size); +// if (!buf) { LOG_ERR("TAG", "OOM"); return false; } +// buf[0] = 0xFF; +// someApi(buf.get(), size); +// + +template + requires(!std::is_array_v) +std::unique_ptr makeUniqueNoThrow(Args&&... args) { + return std::unique_ptr(new (std::nothrow) T(std::forward(args)...)); +} + +template + requires std::is_unbounded_array_v +std::unique_ptr makeUniqueNoThrow(size_t count) { + using Elem = std::remove_extent_t; + return std::unique_ptr(new (std::nothrow) Elem[count]()); +} + +// Helper struct to call a cleanup function on exit from any scope. +// Use with a lambda to avoid unnecessary allocations from std::function/std::bind: +// Example: +// auto jpeg = makeUniqueNoThrow(); +// ScopedCleanup cleanup{[&jpeg]{ jpeg->close(); }}; +// +template +struct [[nodiscard]] ScopedCleanup final { + const F fn; + explicit ScopedCleanup(F f) : fn{std::move(f)} {} + ScopedCleanup(const ScopedCleanup&) = delete; + ScopedCleanup& operator=(const ScopedCleanup&) = delete; + ScopedCleanup(ScopedCleanup&&) = delete; + ScopedCleanup& operator=(ScopedCleanup&&) = delete; + ~ScopedCleanup() { fn(); } +}; + +template +ScopedCleanup(F) -> ScopedCleanup; From e01632e087265766656a8d17044eb7620d0449e7 Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Thu, 14 May 2026 07:34:35 -0500 Subject: [PATCH 70/93] chore: Removed unused icon header files (#1975) ## Summary Deleted two unused header files containing binary icon data. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- src/components/icons/cog.h | 12 ------------ src/components/icons/settings.h | 12 ------------ 2 files changed, 24 deletions(-) delete mode 100644 src/components/icons/cog.h delete mode 100644 src/components/icons/settings.h diff --git a/src/components/icons/cog.h b/src/components/icons/cog.h deleted file mode 100644 index 899d81a9fb..0000000000 --- a/src/components/icons/cog.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -// size: 32x32 -static const uint8_t CogIcon[] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xBE, 0x7D, 0xFF, 0xFF, 0x1C, 0x38, - 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x82, 0x41, 0xFF, 0xFF, 0x0E, 0x70, 0xFF, 0xF6, 0x3E, 0x7C, 0x6F, 0xE0, 0x7E, - 0x7E, 0x07, 0xF0, 0xFE, 0x7F, 0x0F, 0xF8, 0xFE, 0x7F, 0x1F, 0xF9, 0xFE, 0x7F, 0x9F, 0xF9, 0xF8, 0x1F, 0x9F, 0xF3, - 0xF8, 0x1F, 0xCF, 0xC3, 0xF3, 0xCF, 0xC3, 0xC3, 0xF1, 0x8F, 0xC3, 0xF3, 0xE1, 0x87, 0xCF, 0xF9, 0xC0, 0x03, 0x9F, - 0xF9, 0x0E, 0x70, 0x9F, 0xF8, 0x3F, 0xFC, 0x1F, 0xF0, 0x7F, 0xFE, 0x0F, 0xE0, 0x7F, 0xFE, 0x07, 0xF6, 0x3F, 0xFC, - 0x6F, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0x83, 0xC1, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x1C, 0x38, 0xFF, 0xFF, 0xBE, - 0x7D, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/settings.h b/src/components/icons/settings.h deleted file mode 100644 index 3c924be2d2..0000000000 --- a/src/components/icons/settings.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -// size: 32x32 -static const uint8_t SettingsIcon[] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xE1, 0xFF, 0xFF, 0x03, 0xC0, - 0xFF, 0xFE, 0x30, 0x8C, 0x7F, 0xFE, 0x78, 0x1E, 0x7F, 0xFE, 0x7C, 0x7E, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, - 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x7C, 0x3E, 0x3F, 0xF0, 0xF0, 0x0F, 0x07, 0xC1, 0xF3, 0xCF, 0x83, 0xC7, - 0xE7, 0xE7, 0xE3, 0xCF, 0xE7, 0xE7, 0xF3, 0xCF, 0xE7, 0xE7, 0xF3, 0xC7, 0xE7, 0xE7, 0xE3, 0xC1, 0xF3, 0xCF, 0x83, - 0xE0, 0xF0, 0x0F, 0x0F, 0xFC, 0x7C, 0x3E, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, - 0x7F, 0xFE, 0x7E, 0x3E, 0x7F, 0xFE, 0x78, 0x1E, 0x7F, 0xFE, 0x31, 0x0C, 0x7F, 0xFF, 0x03, 0xC0, 0xFF, 0xFF, 0x87, - 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; From 3ffd6a6d4ce2622f944d2518f848bee1372fe96b Mon Sep 17 00:00:00 2001 From: IjonFryderyk Date: Thu, 14 May 2026 15:32:59 +0200 Subject: [PATCH 71/93] feat: Add Polish hyphenation support (#1590) Co-authored-by: Claude Opus 4.6 --- .../Epub/hyphenation/HyphenationCommon.cpp | 73 +- .../Epub/hyphenation/LanguageRegistry.cpp | 5 +- .../Epub/hyphenation/generated/hyph-pl.trie.h | 987 ++++ scripts/update_hyphenation.sh | 1 + .../HyphenationEvaluationTest.cpp | 1 + .../resources/polish_hyphenation_tests.txt | 5012 +++++++++++++++++ 6 files changed, 6076 insertions(+), 3 deletions(-) create mode 100644 lib/Epub/Epub/hyphenation/generated/hyph-pl.trie.h create mode 100644 test/hyphenation_eval/resources/polish_hyphenation_tests.txt diff --git a/lib/Epub/Epub/hyphenation/HyphenationCommon.cpp b/lib/Epub/Epub/hyphenation/HyphenationCommon.cpp index 4b2ac4a100..13be226387 100644 --- a/lib/Epub/Epub/hyphenation/HyphenationCommon.cpp +++ b/lib/Epub/Epub/hyphenation/HyphenationCommon.cpp @@ -13,9 +13,19 @@ uint32_t toLowerLatinImpl(const uint32_t cp) { 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 0x0152: // Œ - return 0x0153; // œ case 0x0178: // Ÿ return 0x00FF; // ÿ case 0x1E9E: // ẞ @@ -54,6 +64,11 @@ bool isLatinLetter(const uint32_t cp) { 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: // œ @@ -273,6 +288,30 @@ std::vector collectCodepoints(const std::string& word) { 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; } @@ -373,6 +412,18 @@ std::vector collectCodepoints(const std::string& word) { 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: @@ -385,6 +436,24 @@ std::vector collectCodepoints(const std::string& word) { 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; } diff --git a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp index 052d47ee50..541e9a277a 100644 --- a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp +++ b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp @@ -9,6 +9,7 @@ #include "generated/hyph-es.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-uk.trie.h" @@ -22,8 +23,9 @@ LanguageHyphenator russianHyphenator(ru_patterns, isCyrillicLetter, toLowerCyril LanguageHyphenator spanishHyphenator(es_patterns, isLatinLetter, toLowerLatin); LanguageHyphenator italianHyphenator(it_patterns, isLatinLetter, toLowerLatin); LanguageHyphenator ukrainianHyphenator(uk_patterns, isCyrillicLetter, toLowerCyrillic); +LanguageHyphenator polishHyphenator(pl_patterns, isLatinLetter, toLowerLatin); -using EntryArray = std::array; +using EntryArray = std::array; const EntryArray& entries() { static const EntryArray kEntries = {{{"english", "en", &englishHyphenator}, @@ -32,6 +34,7 @@ const EntryArray& entries() { {"russian", "ru", &russianHyphenator}, {"spanish", "es", &spanishHyphenator}, {"italian", "it", &italianHyphenator}, + {"polish", "pl", &polishHyphenator}, {"ukrainian", "uk", &ukrainianHyphenator}}}; return kEntries; } diff --git a/lib/Epub/Epub/hyphenation/generated/hyph-pl.trie.h b/lib/Epub/Epub/hyphenation/generated/hyph-pl.trie.h new file mode 100644 index 0000000000..7e4068c29a --- /dev/null +++ b/lib/Epub/Epub/hyphenation/generated/hyph-pl.trie.h @@ -0,0 +1,987 @@ +#pragma once + +#include +#include + +#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), +}; diff --git a/scripts/update_hyphenation.sh b/scripts/update_hyphenation.sh index ed12aef79b..53706eacc4 100755 --- a/scripts/update_hyphenation.sh +++ b/scripts/update_hyphenation.sh @@ -23,3 +23,4 @@ process es process ru process it process uk +process pl diff --git a/test/hyphenation_eval/HyphenationEvaluationTest.cpp b/test/hyphenation_eval/HyphenationEvaluationTest.cpp index a56467a91e..27b9fff385 100644 --- a/test/hyphenation_eval/HyphenationEvaluationTest.cpp +++ b/test/hyphenation_eval/HyphenationEvaluationTest.cpp @@ -44,6 +44,7 @@ const std::vector kSupportedLanguages = { {"russian", "test/hyphenation_eval/resources/russian_hyphenation_tests.txt", "ru"}, {"spanish", "test/hyphenation_eval/resources/spanish_hyphenation_tests.txt", "es"}, {"italian", "test/hyphenation_eval/resources/italian_hyphenation_tests.txt", "it"}, + {"polish", "test/hyphenation_eval/resources/polish_hyphenation_tests.txt", "pl"}, }; std::vector expectedPositionsFromAnnotatedWord(const std::string& annotated) { diff --git a/test/hyphenation_eval/resources/polish_hyphenation_tests.txt b/test/hyphenation_eval/resources/polish_hyphenation_tests.txt new file mode 100644 index 0000000000..5bc8738f9c --- /dev/null +++ b/test/hyphenation_eval/resources/polish_hyphenation_tests.txt @@ -0,0 +1,5012 @@ +# Hyphenation Test Data +# Source: Władysław Stanisław Reymont, Chlopi.epub +# Language: pl_PL +# Min prefix: 2 +# Min suffix: 2 +# Total words: 5000 +# Format: word | hyphenated_form | frequency_in_source +# +# Hyphenation points are marked with '=' +# Example: Silbentrennung -> Sil=ben=tren=nung +# + +edytorski|edy=tor=ski|3556 +przypis|przy=pis|3556 +jeszcze|jesz=cze|1046 +wszystko|wszyst=ko|411 +któren|któ=ren|292 +będzie|bę=dzie|285 +ledwie|le=d=wie|267 +wszystkie|wszyst=kie|259 +chałupy|cha=łu=py|252 +ludzie|lu=dzie|245 +cięgiem|cię=giem|240 +dzieci|dzie=ci|239 +dobrze|do=brze|228 +siebie|sie=bie|218 +dopiero|do=pie=ro|212 +Mateusz|Ma=te=usz|210 +całkiem|cał=kiem|200 +Boryna|Bo=ry=na|199 +prawie|pra=wie|195 +wszystkich|wszyst=kich|191 +świecie|świe=cie|183 +dzisiaj|dzi=siaj|164 +choćby|choć=by|161 +roboty|ro=bo=ty|161 +chałupie|cha=łu=pie|158 +drzewa|drze=wa|153 +drugie|dru=gie|151 +słońce|słoń=ce|150 +powiedział|po=wie=dział|149 +wszyscy|wszy=scy|148 +Jagustynka|Ja=gu=styn=ka|146 +poszedł|po=szedł|145 +kobiety|ko=bie=ty|143 +ździebko|ździeb=ko|142 +ksiądz|ksiądz|140 +barzej|ba=rzej|139 +jakieś|ja=kieś|139 +między|mię=dzy|139 +niekiedy|nie=kie=dy|138 +oczami|ocza=mi|137 +chciał|chciał|134 +Jagusia|Ja=gu=sia|134 +ziemię|zie=mię|128 +chociaż|cho=ciaż|123 +więcej|wię=cej|122 +gdzieś|gdzieś|120 +ciężko|cięż=ko|117 +drodze|dro=dze|115 +wszystkim|wszyst=kim|115 +bowiem|bo=wiem|114 +stronę|stro=nę|113 +wielce|wiel=ce|113 +poszła|po=szła|111 +kościoła|ko=ścio=ła|109 +przecież|prze=cież|109 +często|czę=sto|107 +wiater|wia=ter|105 +czasem|cza=sem|103 +prosto|pro=sto|101 +rzucił|rzu=cił|101 +szepnął|szep=nął|101 +głowie|gło=wie|99 +jakoby|ja=ko=by|99 +Grzela|Grze=la|98 +potrza|po=trza|97 +zaczął|za=czął|97 +każden|każ=den|96 +Szymek|Szy=mek|96 +gdziesik|gdzie=sik|94 +gdzieniegdzie|gdzie=nie=gdzie|93 +Jambroży|Jam=bro=ży|93 +ściany|ścia=ny|93 +człowiek|czło=wiek|92 +Prawda|Praw=da|91 +ktosik|kto=sik|90 +prędko|pręd=ko|90 +ciebie|cie=bie|89 +karczmy|karcz=my|89 +Dominikowa|Do=mi=ni=ko=wa|88 +kościele|ko=ście=le|88 +pacierz|pa=cierz|88 +szepnęła|szep=nę=ła|88 +rzekła|rze=kła|87 +właśnie|wła=śnie|87 +dziedzic|dzie=dzic|86 +jednak|jed=nak|86 +prawda|praw=da|85 +drogach|dro=gach|84 +siedział|sie=dział|84 +miejsca|miej=sca|83 +drugich|dru=gich|82 +narodu|na=ro=du|82 +Pietrek|Pie=trek|82 +pilnie|pil=nie|80 +przódzi|przó=dzi|80 +zawołał|za=wo=łał|80 +ziemia|zie=mia|80 +głosem|gło=sem|79 +przeciek|prze=ciek|79 +srodze|sro=dze|79 +krzyknął|krzyk=nął|78 +oczyma|oczy=ma|78 +pewnie|pew=nie|78 +rychło|ry=chło|78 +lepiej|le=piej|77 +piersi|pier=si|77 +żałośnie|ża=ło=śnie|76 +niekaj|nie=kaj|75 +wszędy|wszę=dy|75 +trochę|tro=chę|74 +trzeba|trze=ba|74 +chałupach|cha=łu=pach|73 +pierwszy|pierw=szy|73 +wiedział|wie=dział|73 +gorąco|go=rą=co|72 +swojego|swo=je=go|72 +złości|zło=ści|72 +wskroś|wskroś|71 +karczmie|karcz=mie|69 +którym|któ=rym|69 +prędzej|prę=dzej|69 +płaczem|pła=czem|69 +wczoraj|wczo=raj|69 +dziwnie|dziw=nie|68 +Jagusi|Ja=gu=si|68 +zaczęła|za=czę=ła|68 +niezgorzej|nie=zgo=rzej|67 +swoich|swo=ich|67 +ślepiami|śle=pia=mi|67 +krzykiem|krzy=kiem|66 +musiał|mu=siał|66 +przeciech|prze=ciech|66 +przyszedł|przy=szedł|66 +słońcu|słoń=cu|66 +deszcz|deszcz|65 +oczach|oczach|65 +sielnie|siel=nie|65 +wyszedł|wy=szedł|65 +ziemniaki|ziem=nia=ki|64 +Borynów|Bo=ry=nów|63 +cichości|ci=cho=ści|63 +Dopiero|Do=pie=ro|63 +których|któ=rych|63 +podniósł|pod=niósł|63 +polach|po=lach|63 +słychać|sły=chać|63 +twarzy|twa=rzy|63 +chłopaki|chło=pa=ki|62 +dokoła|do=ko=ła|62 +drzewo|drze=wo|62 +Lipcach|Lip=cach|62 +WolneLektury|Wol=ne=Lek=tu=ry|62 +światła|świa=tła|62 +której|któ=rej|61 +spokojnie|spo=koj=nie|61 +starego|sta=re=go|61 +wesoło|we=so=ło|61 +widział|wi=dział|61 +wszystkiego|wszyst=kie=go|61 +głośno|gło=śno|60 +niekiej|nie=kiej|60 +śmierć|śmierć|60 +chciała|chcia=ła|59 +ciągle|cią=gle|59 +kobiet|ko=biet|59 +leżały|le=ża=ły|59 +bacząc|ba=cząc|58 +cichość|ci=chość|58 +samego|sa=me=go|58 +siedziała|sie=dzia=ła|58 +zawsze|za=wsze|58 +gospodarz|go=spo=darz|57 +nieraz|nie=raz|57 +nikogo|ni=ko=go|57 +sposób|spo=sób|57 +Słońce|Słoń=ce|57 +niecoś|nie=coś|56 +radośnie|ra=do=śnie|56 +strony|stro=ny|56 +trzęsły|trzę=sły|56 +wszyćko|wszyć=ko|56 +zapłać|za=płać|56 +chałupę|cha=łu=pę|55 +chłopy|chło=py|55 +niczego|ni=cze=go|55 +opłotkach|opłot=kach|55 +Pewnie|Pew=nie|55 +szeptał|szep=tał|55 +ciszej|ci=szej|54 +gospodarze|go=spo=da=rze|54 +jednego|jed=ne=go|54 +krzywdy|krzyw=dy|54 +niebie|nie=bie|54 +niejeden|nie=je=den|54 +stawem|sta=wem|54 +czerwone|czer=wo=ne|53 +Dominikowej|Do=mi=ni=ko=wej|53 +drugiej|dru=giej|53 +kobieta|ko=bie=ta|53 +opłotki|opłot=ki|53 +przyjdzie|przyj=dzie|53 +siedzieli|sie=dzie=li|53 +stojał|sto=jał|53 +strach|strach|53 +takiego|ta=kie=go|53 +chłopa|chło=pa|52 +ciężkie|cięż=kie|52 +księdza|księ=dza|52 +miasta|mia=sta|52 +chłopak|chło=pak|51 +długie|dłu=gie|51 +któryś|któ=ryś|51 +ruszył|ru=szył|51 +wieczór|wie=czór|51 +zawżdy|za=wż=dy|51 +śmierci|śmier=ci|51 +chałupą|cha=łu=pą|50 +chwili|chwi=li|50 +dobrodziej|do=bro=dziej|50 +powietrzu|po=wie=trzu|50 +pójdzie|pój=dzie|50 +robota|ro=bo=ta|50 +stronie|stro=nie|50 +trudno|trud=no|50 +trwożnie|trwoż=nie|50 +chodził|cho=dził|49 +Dobrze|Do=brze|49 +zacząć|za=cząć|49 +zaczęli|za=czę=li|49 +chłopów|chło=pów|48 +leciały|le=cia=ły|48 +osobna|osob=na|48 +pieniądze|pie=nią=dze|48 +wzięła|wzię=ła|48 +drugiego|dru=gie=go|47 +jeszczek|jesz=czek|47 +którego|któ=re=go|47 +mruknął|mruk=nął|47 +młynarz|mły=narz|47 +naprzeciw|na=prze=ciw|47 +poszli|po=szli|47 +powietrze|po=wie=trze|47 +swojej|swo=jej|47 +wiadomo|wia=do=mo|47 +wreszcie|wresz=cie|47 +zapytał|za=py=tał|47 +święta|świę=ta|47 +chciało|chcia=ło|46 +każdego|każ=de=go|46 +miejscu|miej=scu|46 +myślał|my=ślał|46 +podnosił|pod=no=sił|46 +powiedziała|po=wie=dzia=ła|46 +stajni|staj=ni|46 +wszedł|wszedł|46 +zarówno|za=rów=no|46 +dojrzał|doj=rzał|45 +dziecko|dziec=ko|45 +jakimś|ja=kimś|45 +Jeszcze|Jesz=cze|45 +każdym|każ=dym|45 +przysiadł|przy=siadł|45 +sadach|sa=dach|45 +siedzi|sie=dzi|45 +wesele|we=se=le|45 +drugimi|dru=gi=mi|44 +Kłębów|Kłę=bów|44 +ludźmi|ludź=mi|44 +pacierze|pa=cie=rze|44 +patrzeć|pa=trzeć|44 +poredzić|po=re=dzić|44 +Płoszka|Płosz=ka|44 +radości|ra=do=ści|44 +wiecie|wie=cie|44 +wszystek|wszy=stek|44 +zrobił|zro=bił|44 +złotych|zło=tych|44 +złością|zło=ścią|44 +śmiechem|śmie=chem|44 +bardziej|bar=dziej|43 +dziwota|dzi=wo=ta|43 +Jambroż|Jam=broż|43 +prawdę|praw=dę|43 +sadzie|sa=dzie|43 +zaczęły|za=czę=ły|43 +światem|świa=tem|43 +chodzi|cho=dzi|42 +cichuśko|ci=chuś=ko|42 +gospodarzy|go=spo=da=rzy|42 +każdemu|każ=de=mu|42 +krzyczał|krzy=czał|42 +leciał|le=ciał|42 +Michał|Mi=chał|42 +myślała|my=śla=ła|42 +mówili|mó=wi=li|42 +przyszła|przy=szła|42 +sprawy|spra=wy|42 +swojemu|swo=je=mu|42 +szukać|szu=kać|42 +słowem|sło=wem|42 +wszędzie|wszę=dzie|42 +świata|świa=ta|42 +światło|świa=tło|42 +drugim|dru=gim|41 +krzyczeć|krzy=czeć|41 +krzyki|krzy=ki|41 +leżała|le=ża=ła|41 +patrzył|pa=trzył|41 +patrząc|pa=trząc|41 +pieniędzy|pie=nię=dzy|41 +próżno|próż=no|41 +sprawa|spra=wa|41 +grosza|gro=sza|40 +Kozłowa|Ko=zło=wa|40 +lubością|lu=bo=ścią|40 +Maciej|Ma=ciej|40 +pięściami|pię=ścia=mi|40 +poleciał|po=le=ciał|40 +przyszło|przy=szło|40 +radością|ra=do=ścią|40 +skoczył|sko=czył|40 +ścianą|ścia=ną|40 +bliżej|bli=żej|39 +Jagusię|Ja=gu=się|39 +jestem|je=stem|39 +kominem|ko=mi=nem|39 +komory|ko=mo=ry|39 +krzyknęła|krzyk=nę=ła|39 +któraś|któ=raś|39 +niemało|nie=ma=ło|39 +powiadał|po=wia=dał|39 +radość|ra=dość|39 +szeroko|sze=ro=ko|39 +słońca|słoń=ca|39 +zawołała|za=wo=ła=ła|39 +śniegi|śnie=gi|39 +Boryny|Bo=ry=ny|38 +daleko|da=le=ko|38 +dworze|dwo=rze|38 +dzieje|dzie=je|38 +dziećmi|dzieć=mi|38 +groźnie|groź=nie|38 +jednej|jed=nej|38 +miejsce|miej=sce|38 +milczeniu|mil=cze=niu|38 +niczym|ni=czym|38 +potrzeba|po=trze=ba|38 +rzeczy|rze=czy|38 +strachu|stra=chu|38 +westchnął|wes=tchnął|38 +Wiecie|Wie=cie|38 +wiedząc|wie=dząc|38 +ziemniaków|ziem=nia=ków|38 +zrobić|zro=bić|38 +chałup|cha=łup|37 +chwila|chwi=la|37 +dobrego|do=bre=go|37 +każdej|każ=dej|37 +musiała|mu=sia=ła|37 +Nastka|Na=st=ka|37 +nowego|no=we=go|37 +podwórze|po=dwó=rze|37 +robotę|ro=bo=tę|37 +różnych|róż=nych|37 +stodoły|sto=do=ły|37 +słuchał|słu=chał|37 +zaglądał|za=glą=dał|37 +ziemie|zie=mie|37 +zwiesnę|zwie=snę|37 +bardzo|bar=dzo|36 +całego|ca=łe=go|36 +czasami|cza=sa=mi|36 +dojrzawszy|doj=rzaw=szy|36 +powiada|po=wia=da|36 +pośrodku|po=środ=ku|36 +strasznie|strasz=nie|36 +wszystkimi|wszyst=ki=mi|36 +wywarte|wy=war=te|36 +zajrzał|zaj=rzał|36 +Bogiem|Bo=giem|35 +chwilę|chwi=lę|35 +chłopaków|chło=pa=ków|35 +czarne|czar=ne|35 +cztery|czte=ry|35 +głupie|głu=pie|35 +jednym|jed=nym|35 +Ksiądz|Ksiądz|35 +morgów|mor=gów|35 +ostała|osta=ła|35 +pierwsze|pierw=sze|35 +poradzić|po=ra=dzić|35 +poredzi|po=re=dzi|35 +powiem|po=wiem|35 +powrotem|po=wro=tem|35 +prawił|pra=wił|35 +różności|róż=no=ści|35 +smutnie|smut=nie|35 +strachem|stra=chem|35 +szmaty|szma=ty|35 +słyszał|sły=szał|35 +takich|ta=kich|35 +takiej|ta=kiej|35 +trzęsła|trzę=sła|35 +zrobiło|zro=bi=ło|35 +ścianę|ścia=nę|35 +ślepie|śle=pie|35 +święty|świę=ty|35 +chyłkiem|chył=kiem|34 +czegoś|cze=goś|34 +dzieuchy|dzieu=chy|34 +dziewczyny|dziew=czy=ny|34 +galanto|ga=lan=to|34 +Jagusią|Ja=gu=sią|34 +nikomu|ni=ko=mu|34 +nogami|no=ga=mi|34 +patrzy|pa=trzy|34 +Tereska|Te=re=ska|34 +zawzięcie|za=wzię=cie|34 +zwyczajnie|zwy=czaj=nie|34 +śniegu|śnie=gu|34 +chleba|chle=ba|33 +czasie|cza=sie|33 +Jędrzych|Ję=drzych|33 +kulasy|ku=la=sy|33 +Mateusza|Ma=te=usza|33 +mówiła|mó=wi=ła|33 +niedzielę|nie=dzie=lę|33 +organista|or=ga=ni=sta|33 +parobek|pa=ro=bek|33 +podniesła|pod=nie=sła|33 +poleciała|po=le=cia=ła|33 +puścił|pu=ścił|33 +resztę|resz=tę|33 +rozglądając|roz=glą=da=jąc|33 +Właśnie|Wła=śnie|33 +chałupami|cha=łu=pa=mi|32 +czekał|cze=kał|32 +drzewami|drze=wa=mi|32 +kiejby|kiej=by|32 +kościołem|ko=ścio=łem|32 +muzyka|mu=zy=ka|32 +nieśmiało|nie=śmia=ło|32 +organistów|or=ga=ni=stów|32 +organiścina|or=ga=ni=ści=na|32 +owdzie|owdzie|32 +parobków|pa=rob=ków|32 +patrzał|pa=trzał|32 +powiedzieć|po=wie=dzieć|32 +prosił|pro=sił|32 +Przeciech|Prze=ciech|32 +ramiona|ra=mio=na|32 +rychtyk|rych=tyk|32 +Stacho|Sta=cho|32 +tylachna|ty=lach=na|32 +uciechy|ucie=chy|32 +wysoko|wy=so=ko|32 +wyszła|wy=szła|32 +Zdrowaś|Zdro=waś|32 +ziemią|zie=mią|32 +świętą|świę=tą|32 +gałęzie|ga=łę=zie|31 +głównie|głów=nie|31 +Jankiel|Jan=kiel|31 +kamień|ka=mień|31 +kościół|ko=ściół|31 +Kłębowa|Kłę=bo=wa|31 +miejscami|miej=sca=mi|31 +niesły|nie=sły|31 +ogniem|ogniem|31 +ruchać|ru=chać|31 +spiesznie|spiesz=nie|31 +stanął|sta=nął|31 +szeptała|szep=ta=ła|31 +wcześniej|wcze=śniej|31 +westchnęła|wes=tchnę=ła|31 +wrzask|wrzask|31 +wzdychał|wzdy=chał|31 +akuratnie|aku=rat=nie|30 +chodziła|cho=dzi=ła|30 +chycił|chy=cił|30 +ciepło|cie=pło|30 +garści|gar=ści|30 +gwiazdy|gwiaz=dy|30 +głęboko|głę=bo=ko|30 +kamienie|ka=mie=nie|30 +kominie|ko=mi=nie|30 +młynarza|mły=na=rza|30 +rzuciła|rzu=ci=ła|30 +widząc|wi=dząc|30 +wieczora|wie=czo=ra|30 +wiedziała|wie=dzia=ła|30 +wkrótce|wkrót=ce|30 +wrzaski|wrza=ski|30 +zajadle|za=ja=dle|30 +zarazem|za=ra=zem|30 +zdziebko|zdzieb=ko|30 +zmierzchu|zmierz=chu|30 +święte|świę=te|30 +buchnęła|buch=nę=ła|29 +Bylica|By=li=ca|29 +cienie|cie=nie|29 +ciągnął|cią=gnął|29 +czynił|czy=nił|29 +dobrodzieja|do=bro=dzie=ja|29 +galancie|ga=lan=cie|29 +godzinę|go=dzi=nę|29 +gorzałki|go=rzał=ki|29 +głośniej|gło=śniej|29 +kieliszek|kie=li=szek|29 +kolana|ko=la=na|29 +nasłuchując|na=słu=chu=jąc|29 +pazurami|pa=zu=ra=mi|29 +pięścią|pię=ścią|29 +podwórzu|po=dwó=rzu|29 +pojechał|po=je=chał|29 +poredziła|po=re=dzi=ła|29 +prosiła|pro=si=ła|29 +przystawał|przy=sta=wał|29 +robocie|ro=bo=cie|29 +sołtys|soł=tys|29 +spojrzał|spoj=rzał|29 +spytała|spy=ta=ła|29 +stanie|sta=nie|29 +stanęła|sta=nę=ła|29 +słuchać|słu=chać|29 +urągliwie|urą=gli=wie|29 +weźmie|weź=mie|29 +widziało|wi=dzia=ło|29 +wiedzą|wie=dzą|29 +wielkim|wiel=kim|29 +wrócił|wró=cił|29 +wzięli|wzię=li|29 +wójtowa|wój=to=wa|29 +zapamiętale|za=pa=mię=ta=le|29 +śpiewał|śpie=wał|29 +boleśnie|bo=le=śnie|28 +częściej|czę=ściej|28 +człowieka|czło=wie=ka|28 +drudzy|dru=dzy|28 +drzwiami|drzwia=mi|28 +dziesięć|dzie=sięć|28 +gospodyni|go=spo=dy=ni|28 +jeszczech|jesz=czech|28 +którzy|któ=rzy|28 +Macieju|Ma=cie=ju|28 +odrzekł|od=rzekł|28 +ozwała|ozwa=ła|28 +pierwsza|pierw=sza|28 +pierzynę|pie=rzy=nę|28 +pieski|pie=ski|28 +podnosząc|pod=no=sząc|28 +pokrótce|po=krót=ce|28 +przeszedł|prze=szedł|28 +przyleciał|przy=le=ciał|28 +spokój|spo=kój|28 +spowiedzi|spo=wie=dzi|28 +starszy|star=szy|28 +uciekać|ucie=kać|28 +wicher|wi=cher|28 +zwiesna|zwie=sna|28 +śmiała|śmia=ła|28 +śpiesznie|śpiesz=nie|28 +świnie|świ=nie|28 +dziewczyna|dziew=czy=na|27 +gorzałkę|go=rzał=kę|27 +głupia|głu=pia|27 +Jasiek|Ja=siek|27 +koniec|ko=niec|27 +koszuli|ko=szu=li|27 +Lipiec|Li=piec|27 +ludziach|lu=dziach|27 +naprzód|na=przód|27 +Nastusia|Na=stu=sia|27 +odrzekła|od=rze=kła|27 +pacierza|pa=cie=rza|27 +powiadali|po=wia=da=li|27 +przeciw|prze=ciw|27 +płacze|pła=cze|27 +Płoszkowa|Płosz=ko=wa|27 +sercem|ser=cem|27 +starym|sta=rym|27 +widziała|wi=dzia=ła|27 +wieczorem|wie=czo=rem|27 +Wszystkie|Wszyst=kie|27 +wykrzyknął|wy=krzyk=nął|27 +znowuj|zno=wuj|27 +świętej|świę=tej|27 +białych|bia=łych|26 +brakowało|bra=ko=wa=ło|26 +buchnął|buch=nął|26 +byście|by=ście|26 +będziesz|bę=dziesz|26 +chmury|chmu=ry|26 +dziedzica|dzie=dzi=ca|26 +dzisia|dzi=sia|26 +kapoty|ka=po=ty|26 +krzywda|krzyw=da|26 +obrazy|ob=ra=zy|26 +pomocy|po=mo=cy|26 +porwał|po=rwał|26 +proboszcz|pro=boszcz|26 +rzucał|rzu=cał|26 +sprawę|spra=wę|26 +szybko|szyb=ko|26 +twardo|twar=do|26 +wrzaskiem|wrza=skiem|26 +wychodził|wy=cho=dził|26 +zagony|za=go=ny|26 +zerwał|ze=rwał|26 +śmiechu|śmie=chu|26 +śmiechy|śmie=chy|26 +bezwolnie|bez=wol=nie|25 +cichym|ci=chym|25 +czerwonych|czer=wo=nych|25 +dookoła|do=oko=ła|25 +gotowa|go=to=wa|25 +głowami|gło=wa=mi|25 +komina|ko=mi=na|25 +krzywdę|krzyw=dę|25 +krzyżem|krzy=żem|25 +mrokach|mro=kach|25 +mruczał|mru=czał|25 +najbarzej|naj=ba=rzej|25 +nazajutrz|na=za=jutrz|25 +niespokojnie|nie=spo=koj=nie|25 +ostatnie|ostat=nie|25 +ostatnią|ostat=nią|25 +ostrożnie|ostroż=nie|25 +pamięci|pa=mię=ci|25 +patrzała|pa=trza=ła|25 +pewnikiem|pew=ni=kiem|25 +pierwszą|pierw=szą|25 +Pietrka|Pietr=ka|25 +przeżegnał|prze=że=gnał|25 +później|póź=niej|25 +reszta|resz=ta|25 +rozlegał|roz=le=gał|25 +sieniach|sie=niach|25 +Weronka|We=ron=ka|25 +wszystkiemu|wszyst=kie=mu|25 +wyrwał|wy=rwał|25 +zapaskę|za=pa=skę|25 +śniegiem|śnie=giem|25 +Bartek|Bar=tek|24 +bliska|bli=ska|24 +bronił|bro=nił|24 +buchały|bu=cha=ły|24 +cieniu|cie=niu|24 +cierpliwie|cier=pli=wie|24 +dzieciom|dzie=ciom|24 +dłużej|dłu=żej|24 +grzech|grzech|24 +indziej|in=dziej|24 +kochany|ko=cha=ny|24 +kożuch|ko=żuch|24 +kątach|ką=tach|24 +leciała|le=cia=ła|24 +lękliwie|lę=kli=wie|24 +niechby|niech=by|24 +ogromne|ogrom=ne|24 +poradzi|po=ra=dzi|24 +przystanął|przy=sta=nął|24 +przyszli|przy=szli|24 +robiła|ro=bi=ła|24 +rozumu|ro=zu=mu|24 +trzyma|trzy=ma|24 +trzymał|trzy=mał|24 +wielki|wiel=ki|24 +wisiało|wi=sia=ło|24 +wodzie|wo=dzie|24 +wprost|wprost|24 +wypowiedzieć|wy=po=wie=dzieć|24 +wójtów|wój=tów|24 +zabrał|za=brał|24 +ścianami|ścia=na=mi|24 +środku|środ=ku|24 +bieżyć|bie=żyć|23 +bociek|bo=ciek|23 +błocie|bło=cie|23 +chocia|cho=cia|23 +ciepły|cie=pły|23 +drzewiny|drze=wi=ny|23 +Jagustynki|Ja=gu=styn=ki|23 +jęknęła|jęk=nę=ła|23 +kochane|ko=cha=ne|23 +mocniej|moc=niej|23 +oglądać|oglą=dać|23 +organy|or=ga=ny|23 +ostatni|ostat=ni|23 +pacierzy|pa=cie=rzy|23 +papierosa|pa=pie=ro=sa|23 +pazury|pa=zu=ry|23 +portki|por=t=ki|23 +powrócił|po=wró=cił|23 +południe|po=łu=dnie|23 +przyniósł|przy=niósł|23 +płotem|pło=tem|23 +rzeknę|rzek=nę|23 +siarczyście|siar=czy=ście|23 +siedzą|sie=dzą|23 +stawie|sta=wie|23 +trzech|trzech|23 +twarze|twa=rze|23 +tydzień|ty=dzień|23 +wykrzyknęła|wy=krzyk=nę=ła|23 +wynosił|wy=no=sił|23 +wytrzymać|wy=trzy=mać|23 +zakrzyczał|za=krzy=czał|23 +zdołać|zdo=łać|23 +zdrowie|zdro=wie|23 +świeciło|świe=ci=ło|23 +świtania|świ=ta=nia|23 +Antkowi|Ant=ko=wi|22 +bronić|bro=nić|22 +chcieli|chcie=li|22 +czapkę|czap=kę|22 +dzwony|dzwo=ny|22 +gotowy|go=to=wy|22 +głosów|gło=sów|22 +głucho|głu=cho|22 +Głupiś|Głu=piś|22 +inaczej|ina=czej|22 +innego|in=ne=go|22 +inszego|in=sze=go|22 +kiedyś|kie=dyś|22 +kowala|ko=wa=la|22 +krzyczała|krzy=cza=ła|22 +kurzawy|ku=rza=wy|22 +Ludzie|Lu=dzie|22 +niechta|nie=chta|22 +niejednemu|nie=jed=ne=mu|22 +ojcowej|oj=co=wej|22 +organisty|or=ga=ni=sty|22 +pomiędzy|po=mię=dzy|22 +poredził|po=re=dził|22 +poszły|po=szły|22 +Prawdę|Praw=dę|22 +przodem|przo=dem|22 +przychodzi|przy=cho=dzi|22 +płotów|pło=tów|22 +roznosiły|roz=no=si=ły|22 +rychlej|ry=chlej|22 +również|rów=nież|22 +szkoda|szko=da|22 +topolowej|to=po=lo=wej|22 +wojska|woj=ska|22 +wołała|wo=ła=ła|22 +żadnej|żad=nej|22 +chodziły|cho=dzi=ły|21 +chorego|cho=re=go|21 +chycić|chy=cić|21 +chłopaka|chło=pa=ka|21 +czerwony|czer=wo=ny|21 +dobrym|do=brym|21 +Dominikową|Do=mi=ni=ko=wą|21 +działo|dzia=ło|21 +dziedzicem|dzie=dzi=cem|21 +gromadą|gro=ma=dą|21 +jakiejś|ja=kiejś|21 +jednako|jed=na=ko|21 +komorze|ko=mo=rze|21 +kręcił|krę=cił|21 +latały|la=ta=ły|21 +ludziom|lu=dziom|21 +nabożeństwo|na=bo=żeń=stwo|21 +nasłuchiwał|na=słu=chi=wał|21 +odpowiadał|od=po=wia=dał|21 +ogromny|ogrom=ny|21 +plebanię|ple=ba=nię|21 +począć|po=cząć|21 +poniektóre|po=nie=któ=re|21 +przerwał|prze=rwał|21 +przystając|przy=sta=jąc|21 +rozeznać|ro=ze=znać|21 +rozlegały|roz=le=ga=ły|21 +ruszyli|ru=szy=li|21 +skrzydłami|skrzy=dła=mi|21 +stawał|sta=wał|21 +uważnie|uważ=nie|21 +wargami|war=ga=mi|21 +wniebogłosy|wnie=bo=gło=sy|21 +wracają|wra=ca=ją|21 +wrzasnął|wrza=snął|21 +wrzasnęła|wrza=snę=ła|21 +wyniósł|wy=niósł|21 +wyraźnie|wy=raź=nie|21 +zagonach|za=go=nach|21 +zajrzeć|zaj=rzeć|21 +zapomnieć|za=po=mnieć|21 +śniegów|śnie=gów|21 +śpiewały|śpie=wa=ły|21 +świnia|świ=nia|21 +całował|ca=ło=wał|20 +chodzili|cho=dzi=li|20 +ciężki|cięż=ki|20 +czekać|cze=kać|20 +daleka|da=le=ka|20 +dalekie|da=le=kie|20 +dorzucił|do=rzu=cił|20 +dzieciątko|dzie=ciąt=ko|20 +gałęzi|ga=łę=zi|20 +historie|hi=sto=rie|20 +jakiego|ja=kie=go|20 +jarmarku|jar=mar=ku|20 +jaskółki|ja=skół=ki|20 +kapotę|ka=po=tę|20 +kiełbasy|kieł=ba=sy|20 +kolacji|ko=la=cji|20 +kowalowa|ko=wa=lo=wa|20 +krótko|krót=ko|20 +lecieć|le=cieć|20 +naczelnik|na=czel=nik|20 +nieustannie|nie=ustan=nie|20 +odwrócił|od=wró=cił|20 +oknami|okna=mi|20 +ostało|osta=ło|20 +pamięć|pa=mięć|20 +pijany|pi=ja=ny|20 +podnosiła|pod=no=si=ła|20 +podnosiły|pod=no=si=ły|20 +podwórza|po=dwó=rza|20 +pomagać|po=ma=gać|20 +pomarł|po=marł|20 +powieda|po=wie=da|20 +powstał|po=wstał|20 +południa|po=łu=dnia|20 +rozglądał|roz=glą=dał|20 +rękach|rę=kach|20 +skrzyni|skrzy=ni|20 +smętarz|smę=tarz|20 +sprawiedliwości|spra=wie=dli=wo=ści|20 +starczyło|star=czy=ło|20 +szukał|szu=kał|20 +topoli|to=po=li|20 +trzasnął|trza=snął|20 +trzydzieści|trzy=dzie=ści|20 +ukradkiem|ukrad=kiem|20 +uroczyście|uro=czy=ście|20 +wiedzieć|wie=dzieć|20 +Witkiem|Wit=kiem|20 +wodził|wo=dził|20 +wsiach|wsiach|20 +Wszystko|Wszyst=ko|20 +wyciągnął|wy=cią=gnął|20 +wójtem|wój=tem|20 +zaśmiał|za=śmiał|20 +zielone|zie=lo=ne|20 +Antkiem|Ant=kiem|19 +blisko|bli=sko|19 +borowy|bo=ro=wy|19 +buchał|bu=chał|19 +całować|ca=ło=wać|19 +Cichoj|Ci=choj|19 +cmentarz|cmen=tarz|19 +czterech|czte=rech|19 +czyste|czy=ste|19 +garście|gar=ście|19 +gdzież|gdzież|19 +gniewnie|gniew=nie|19 +godziny|go=dzi=ny|19 +Grzeli|Grze=li|19 +głuche|głu=che|19 +głuchy|głu=chy|19 +Jambroża|Jam=bro=ża|19 +kancelarii|kan=ce=la=rii|19 +Kobiety|Ko=bie=ty|19 +kreminale|kre=mi=na=le|19 +kroków|kro=ków|19 +kużden|kuż=den|19 +lipeckie|li=pec=kie|19 +Mateuszem|Ma=te=uszem|19 +Moiściewy|Mo=iście=wy|19 +młynem|mły=nem|19 +narodem|na=ro=dem|19 +Nastuś|Na=stuś|19 +nogach|no=gach|19 +obejrzał|obej=rzał|19 +obiedzie|obie=dzie|19 +pobiegła|po=bie=gła|19 +poczciwie|po=czci=wie|19 +poczuła|po=czu=ła|19 +polami|po=la=mi|19 +poniesła|po=nie=sła|19 +przebierać|prze=bie=rać|19 +przechodził|prze=cho=dził|19 +przedzie|prze=dzie|19 +przejmował|przej=mo=wał|19 +puszczą|pusz=czą|19 +płynął|pły=nął|19 +rozgłośnie|roz=gło=śnie|19 +rozległ|roz=legł|19 +rozmowy|roz=mo=wy|19 +rozpalone|roz=pa=lo=ne|19 +rękoma|rę=ko=ma|19 +samych|sa=mych|19 +serdecznie|ser=decz=nie|19 +skończyła|skoń=czy=ła|19 +starszych|star=szych|19 +turbacji|tur=ba=cji|19 +urzędu|urzę=du|19 +wichurą|wi=chu=rą|19 +widzieli|wi=dzie=li|19 +wiosna|wio=sna|19 +większy|więk=szy|19 +wymiarkować|wy=miar=ko=wać|19 +wyniesła|wy=nie=sła|19 +zajrzała|zaj=rza=ła|19 +zakrzyczała|za=krzy=cza=ła|19 +znajdzie|znaj=dzie|19 +zrobiła|zro=bi=ła|19 +łacniej|łac=niej|19 +ściskał|ści=skał|19 +śniegach|śnie=gach|19 +bierze|bie=rze|18 +Borynie|Bo=ry=nie|18 +błyskały|bły=ska=ły|18 +chcecie|chce=cie|18 +chłopami|chło=pa=mi|18 +cieniach|cie=niach|18 +ciągnęły|cią=gnę=ły|18 +czytał|czy=tał|18 +Dajcie|Daj=cie|18 +deliberować|de=li=be=ro=wać|18 +dobrodziejowi|do=bro=dzie=jo=wi|18 +dwadzieścia|dwa=dzie=ścia|18 +dworskie|dwor=skie|18 +dzwoniły|dzwo=ni=ły|18 +gorzały|go=rza=ły|18 +gorzej|go=rzej|18 +grontu|gron=tu|18 +Jagnie|Ja=gnie|18 +jakiegoś|ja=kie=goś|18 +Jantoś|Jan=toś|18 +książki|książ=ki|18 +księdzem|księ=dzem|18 +księżyc|księ=życ|18 +mojego|mo=je=go|18 +mruknęła|mruk=nę=ła|18 +musieli|mu=sie=li|18 +młynie|mły=nie|18 +należy|na=le=ży|18 +nieprawda|nie=praw=da|18 +obaczysz|oba=czysz|18 +oczymgnienie|oczym=gnie=nie|18 +odszedł|od=szedł|18 +ogarniał|ogar=niał|18 +otwarte|otwar=te|18 +papier|pa=pier|18 +patrzyła|pa=trzy=ła|18 +pierwszej|pierw=szej|18 +pierzyną|pie=rzy=ną|18 +pieśnie|pie=śnie|18 +pisarz|pi=sarz|18 +plecach|ple=cach|18 +poczuł|po=czuł|18 +Podlesie|Pod=le=sie|18 +Podlesiu|Pod=le=siu|18 +podniosła|pod=nio=sła|18 +pogadywał|po=ga=dy=wał|18 +pokornie|po=kor=nie|18 +powróci|po=wró=ci|18 +południu|po=łu=dniu|18 +progiem|pro=giem|18 +prosić|pro=sić|18 +Przecież|Prze=cież|18 +przejął|prze=jął|18 +przyjść|przyjść|18 +przysiadła|przy=sia=dła|18 +ptaszek|pta=szek|18 +pytała|py=ta=ła|18 +robiło|ro=bi=ło|18 +rzadka|rzad=ka|18 +rękami|rę=ka=mi|18 +skamlał|skam=lał|18 +stworzenie|stwo=rze=nie|18 +swoimi|swo=imi|18 +szczekał|szcze=kał|18 +szepty|szep=ty|18 +słuchali|słu=cha=li|18 +słyszała|sły=sza=ła|18 +trząsł|trząsł|18 +uciekła|ucie=kła|18 +uczynił|uczy=nił|18 +wesela|we=se=la|18 +wełniaki|weł=nia=ki|18 +widziały|wi=dzia=ły|18 +wielgachne|wiel=gach=ne|18 +wielkie|wiel=kie|18 +wlekły|wle=kły|18 +wyszło|wy=szło|18 +wzieni|wzie=ni|18 +zapomniał|za=po=mniał|18 +zapytała|za=py=ta=ła|18 +ziemiach|zie=miach|18 +zrywał|zry=wał|18 +ścierwy|ścier=wy|18 +środkiem|środ=kiem|18 +świece|świe=ce|18 +świętym|świę=tym|18 +żywego|ży=we=go|18 +bezwiednie|bez=wied=nie|17 +Borynowej|Bo=ry=no=wej|17 +Borynę|Bo=ry=nę|17 +Będzie|Bę=dzie|17 +chustkę|chust=kę|17 +chwilami|chwi=la=mi|17 +cisnąc|ci=snąc|17 +czekając|cze=ka=jąc|17 +dalejże|da=lej=że|17 +daruję|da=ru=ję|17 +dodała|do=da=ła|17 +dziecińskie|dzie=ciń=skie|17 +Dziedzic|Dzie=dzic|17 +dziwną|dziw=ną|17 +frasobliwie|fra=so=bli=wie|17 +gardziel|gar=dziel|17 +gardzieli|gar=dzie=li|17 +gospodynie|go=spo=dy=nie|17 +gromadzie|gro=ma=dzie|17 +huczał|hu=czał|17 +jarmark|jar=mark|17 +jesteście|je=ste=ście|17 +kamieniem|ka=mie=niem|17 +karczmę|karcz=mę|17 +kryjomo|kry=jo=mo|17 +krzykał|krzy=kał|17 +krótkie|krót=kie|17 +księży|księ=ży|17 +kłótni|kłót=ni|17 +ludzkie|ludz=kie|17 +mamrotał|mam=ro=tał|17 +myśląc|my=śląc|17 +najgorsze|naj=gor=sze|17 +niecierpliwie|nie=cier=pli=wie|17 +niełacno|nie=łac=no|17 +nijakiej|ni=ja=kiej|17 +ochotnie|ochot=nie|17 +ostanie|osta=nie|17 +ostatku|ostat=ku|17 +ołtarzem|oł=ta=rzem|17 +padały|pa=da=ły|17 +plebanii|ple=ba=nii|17 +podniesły|pod=nie=sły|17 +pognał|po=gnał|17 +poredzę|po=re=dzę|17 +posobnie|po=sob=nie|17 +poważnie|po=waż=nie|17 +powiadają|po=wia=da=ją|17 +Przeciek|Prze=ciek|17 +przeszło|prze=szło|17 +przykład|przy=kład|17 +rozeznał|ro=ze=znał|17 +ruszyć|ru=szyć|17 +skończyli|skoń=czy=li|17 +spoglądał|spo=glą=dał|17 +spozierając|spo=zie=ra=jąc|17 +sprawiedliwość|spra=wie=dli=wość|17 +stanęło|sta=nę=ło|17 +szczęścia|szczę=ścia|17 +słomiane|sło=mia=ne|17 +topolową|to=po=lo=wą|17 +trudem|tru=dem|17 +trwogą|trwo=gą|17 +urodna|urod=na|17 +Wieczór|Wie=czór|17 +wielkiej|wiel=kiej|17 +wisiały|wi=sia=ły|17 +wolniej|wol=niej|17 +wrzeszczała|wrzesz=cza=ła|17 +wszelkie|wszel=kie|17 +wszystkiej|wszyst=kiej|17 +wyciągał|wy=cią=gał|17 +wynosiły|wy=no=si=ły|17 +wyrzekła|wy=rze=kła|17 +wzdłuż|wzdłuż|17 +zaklął|za=klął|17 +zaśmiała|za=śmia=ła|17 +zbierać|zbie=rać|17 +zbierał|zbie=rał|17 +zbrakło|zbra=kło|17 +zielonych|zie=lo=nych|17 +śniadaniu|śnia=da=niu|17 +światłem|świa=tłem|17 +świeciły|świe=ci=ły|17 +żałości|ża=ło=ści|17 +ambony|am=bo=ny|16 +bezradnie|bez=rad=nie|16 +biedota|bie=do=ta|16 +Boryną|Bo=ry=ną|16 +broniła|bro=ni=ła|16 +bywało|by=wa=ło|16 +chłopi|chło=pi|16 +ciągnęli|cią=gnę=li|16 +czerwoną|czer=wo=ną|16 +człowieku|czło=wie=ku|16 +dojrzała|doj=rza=ła|16 +domowi|do=mo=wi|16 +długim|dłu=gim|16 +gospodarstwo|go=spo=dar=stwo|16 +granie|gra=nie|16 +grzechu|grze=chu|16 +gwałtownie|gwał=tow=nie|16 +głębokim|głę=bo=kim|16 +jakiej|ja=kiej|16 +kamieniami|ka=mie=nia=mi|16 +karczma|karcz=ma|16 +kałuże|ka=łu=że|16 +kochanie|ko=cha=nie|16 +kreminału|kre=mi=na=łu|16 +którymi|któ=ry=mi|16 +kwiaty|kwia=ty|16 +markotno|mar=kot=no|16 +Modlicy|Mo=dli=cy|16 +modlitwy|mo=dli=twy|16 +niebądź|nie=bądź|16 +odeszła|ode=szła|16 +opadły|opa=dły|16 +opowiadać|opo=wia=dać|16 +ostanę|osta=nę|16 +ostatniej|ostat=niej|16 +ołtarza|oł=ta=rza|16 +parobka|pa=rob=ka|16 +parobki|pa=rob=ki|16 +pierwszego|pierw=sze=go|16 +pierzyny|pie=rzy=ny|16 +pilnować|pil=no=wać|16 +pięknie|pięk=nie|16 +postawił|po=sta=wił|16 +pościeli|po=ście=li|16 +przeciągał|prze=cią=gał|16 +przyjacielstwa|przy=ja=ciel=stwa|16 +przyjdą|przyj=dą|16 +przyniesła|przy=nie=sła|16 +płachtą|płach=tą|16 +płótno|płót=no|16 +rankiem|ran=kiem|16 +rozmysłem|roz=my=słem|16 +rozumiesz|ro=zu=miesz|16 +ruszać|ru=szać|16 +siedziały|sie=dzia=ły|16 +sielny|siel=ny|16 +spoglądając|spo=glą=da=jąc|16 +stojała|sto=ja=ła|16 +strażników|straż=ni=ków|16 +szkołę|szko=łę|16 +trwogi|trwo=gi|16 +tłumaczył|tłu=ma=czył|16 +wiedli|wie=dli|16 +wodząc|wo=dząc|16 +wrzeszczał|wrzesz=czał|16 +wróble|wró=ble|16 +wychodzili|wy=cho=dzi=li|16 +wzięły|wzię=ły|16 +zachodu|za=cho=du|16 +zagadnął|za=gad=nął|16 +zapachy|za=pa=chy|16 +zatrząsł|za=trząsł|16 +zauważył|za=uwa=żył|16 +znaczenie|zna=cze=nie|16 +zważał|zwa=żał|16 +śpiewy|śpie=wy|16 +świtaniu|świ=ta=niu|16 +bierąc|bie=rąc|15 +boskiej|bo=skiej|15 +chałupa|cha=łu=pa|15 +chmura|chmu=ra|15 +Chodźcie|Chodź=cie|15 +chłopu|chło=pu|15 +ciemności|ciem=no=ści|15 +ciężkim|cięż=kim|15 +cudeńka|cu=deń=ka|15 +czekają|cze=ka=ją|15 +czekała|cze=ka=ła|15 +czysta|czy=sta|15 +dobrej|do=brej|15 +dobrością|do=bro=ścią|15 +dostał|do=stał|15 +drzewem|drze=wem|15 +drzwiach|drzwiach|15 +dzieuch|dzieuch|15 +dzieucha|dzieu=cha|15 +gankiem|gan=kiem|15 +gałęziami|ga=łę=zia=mi|15 +grdykę|grdy=kę|15 +grzechy|grze=chy|15 +Jagustynką|Ja=gu=styn=ką|15 +kapustę|ka=pu=stę|15 +kowalem|ko=wa=lem|15 +kromie|kro=mie|15 +krzepko|krzep=ko|15 +kulasami|ku=la=sa=mi|15 +kwiatów|kwia=tów|15 +Laboga|La=bo=ga|15 +laczego|la=cze=go|15 +maciorę|ma=cio=rę|15 +miarkując|miar=ku=jąc|15 +miedzach|mie=dzach|15 +milczenie|mil=cze=nie|15 +Muzyka|Mu=zy=ka|15 +najlepiej|naj=le=piej|15 +napity|na=pi=ty|15 +niedziele|nie=dzie=le|15 +niedługo|nie=dłu=go|15 +Niemcy|Niem=cy|15 +nigdzie|ni=g=dzie|15 +obejścia|obej=ścia|15 +odpust|od=pust|15 +ozorem|ozo=rem|15 +pacierzem|pa=cie=rzem|15 +palcami|pal=ca=mi|15 +patrzą|pa=trzą|15 +piaskiem|pia=skiem|15 +piersiach|pier=siach|15 +pociągał|po=cią=gał|15 +pogadywali|po=ga=dy=wa=li|15 +pokazał|po=ka=zał|15 +pomagał|po=ma=gał|15 +pomogło|po=mo=gło|15 +pomoże|po=mo=że|15 +porządnie|po=rząd=nie|15 +powiedają|po=wie=da=ją|15 +powiódł|po=wiódł|15 +profit|pro=fit|15 +przeciwić|prze=ciw=ić|15 +przejdzie|przej=dzie|15 +przeszła|prze=szła|15 +przyjdę|przyj=dę|15 +przykazał|przy=ka=zał|15 +przędła|przę=dła|15 +próżnicy|próż=ni=cy|15 +pustych|pu=stych|15 +północka|pół=noc=ka|15 +płaczu|pła=czu|15 +płakać|pła=kać|15 +płakała|pła=ka=ła|15 +płotami|pło=ta=mi|15 +radził|ra=dził|15 +ramionami|ra=mio=na=mi|15 +rwetes|rwe=tes|15 +rzekłem|rze=kłem|15 +sierota|sie=ro=ta|15 +sieroty|sie=ro=ty|15 +skończył|skoń=czył|15 +składowano|skła=do=wa=no|15 +spojrzała|spoj=rza=ła|15 +stając|sta=jąc|15 +staremu|sta=re=mu|15 +starsze|star=sze|15 +stojały|sto=ja=ły|15 +surowo|su=ro=wo|15 +słupach|słu=pach|15 +takiemu|ta=kie=mu|15 +tartaku|tar=ta=ku|15 +uciekł|uciekł|15 +warknęła|wark=nę=ła|15 +wełniak|weł=niak|15 +wiatru|wia=tru|15 +wpatrzony|wpa=trzo=ny|15 +wrzawa|wrza=wa|15 +wychodziła|wy=cho=dzi=ła|15 +wyciągać|wy=cią=gać|15 +wysokie|wy=so=kie|15 +zabawiali|za=ba=wia=li|15 +zabawy|za=ba=wy|15 +zabrała|za=bra=ła|15 +zachodzie|za=cho=dzie|15 +zadaszenie|za=da=sze=nie|15 +zaglądała|za=glą=da=ła|15 +zaprzeszłego|za=prze=szłe=go|15 +zdążył|zdą=żył|15 +zmiłowania|zmi=ło=wa=nia|15 +zwyczaju|zwy=cza=ju|15 +środka|środ=ka|15 +święto|świę=to|15 +alkierza|al=kie=rza|14 +baczenie|ba=cze=nie|14 +baczył|ba=czył|14 +Balcerkowa|Bal=cer=ko=wa|14 +białymi|bia=ły=mi|14 +bojała|bo=ja=ła|14 +brzega|brze=ga|14 +całych|ca=łych|14 +całymi|ca=ły=mi|14 +chodzą|cho=dzą|14 +chwiały|chwia=ły|14 +chłopakami|chło=pa=ka=mi|14 +ciekawie|cie=ka=wie|14 +ciołek|cio=łek|14 +cisnęła|ci=snę=ła|14 +cisnęły|ci=snę=ły|14 +czekali|cze=ka=li|14 +czynić|czy=nić|14 +człowiekowi|czło=wie=ko=wi|14 +dachów|da=chów|14 +dlaczego|dla=cze=go|14 +drogami|dro=ga=mi|14 +dziady|dzia=dy|14 +galanty|ga=lan=ty|14 +godnie|god=nie|14 +gorsze|gor=sze|14 +Gospodarz|Go=spo=darz|14 +gospodynią|go=spo=dy=nią|14 +gotowe|go=to=we|14 +groszy|gro=szy|14 +górnie|gór=nie|14 +gęściej|gę=ściej|14 +głodem|gło=dem|14 +innych|in=nych|14 +Jagusine|Ja=gu=si=ne|14 +jesienią|je=sie=nią|14 +jesteś|je=steś|14 +kapusty|ka=pu=sty|14 +kilkanaście|kil=ka=na=ście|14 +kobietę|ko=bie=tę|14 +krzywd|krzywd|14 +któremu|któ=re=mu|14 +kurzawa|ku=rza=wa|14 +kłótnie|kłót=nie|14 +lepsze|lep=sze|14 +liście|li=ście|14 +Loboga|Lo=bo=ga|14 +Miemcy|Miem=cy|14 +mieście|mie=ście|14 +modlił|mo=dlił|14 +mądrze|mą=drze|14 +młodych|mło=dych|14 +najbardziej|naj=bar=dziej|14 +narodowi|na=ro=do=wi|14 +Nastusi|Na=stu=si|14 +nauczał|na=uczał|14 +nawrotem|na=wro=tem|14 +niejedna|nie=jed=na|14 +niektóre|nie=któ=re|14 +nieprzytomny|nie=przy=tom=ny|14 +niesła|nie=sła|14 +odwieczerzu|od=wie=cze=rzu|14 +ogarnęła|ogar=nę=ła|14 +oglądając|oglą=da=jąc|14 +oglądał|oglą=dał|14 +oparte|opar=te|14 +ostały|osta=ły|14 +otworzył|otwo=rzył|14 +pięście|pię=ście|14 +pociekły|po=cie=kły|14 +podobno|po=dob=no|14 +pomyślał|po=my=ślał|14 +poniósł|po=niósł|14 +poruszył|po=ru=szył|14 +powietrza|po=wie=trza|14 +prawdzie|praw=dzie|14 +przebierał|prze=bie=rał|14 +przednówek|przed=nó=wek|14 +przestał|prze=stał|14 +prześmiechem|prze=śmie=chem|14 +przychodził|przy=cho=dził|14 +przycichło|przy=ci=chło|14 +przyszły|przy=szły|14 +puszczał|pusz=czał|14 +radzili|ra=dzi=li|14 +rozmyślał|roz=my=ślał|14 +rozpowiadać|roz=po=wia=dać|14 +ruszyła|ru=szy=ła|14 +ryknął|ryk=nął|14 +rzetelnie|rze=tel=nie|14 +rzewliwie|rzew=li=wie|14 +rzucić|rzu=cić|14 +senność|sen=ność|14 +siadła|sia=dła|14 +splunął|splu=nął|14 +spojrzenia|spoj=rze=nia|14 +społem|spo=łem|14 +sprzed|sprzed|14 +spytał|spy=tał|14 +srogie|sro=gie|14 +starczy|star=czy|14 +statki|stat=ki|14 +strasznym|strasz=nym|14 +szczęście|szczę=ście|14 +szczęśliwości|szczę=śli=wo=ści|14 +szkody|szko=dy|14 +słodko|słod=ko|14 +tamten|tam=ten|14 +toczyły|to=czy=ły|14 +tygodnie|ty=go=dnie|14 +tłukła|tłu=kła|14 +uciekał|ucie=kał|14 +utrudzenia|utru=dze=nia|14 +wiatrem|wia=trem|14 +wieczorami|wie=czo=ra=mi|14 +wielką|wiel=ką|14 +wlekła|wle=kła|14 +wpadła|wpa=dła|14 +wrychle|wry=chle|14 +wróciła|wró=ci=ła|14 +Wszyscy|Wszy=scy|14 +wszystką|wszyst=ką|14 +wychodzić|wy=cho=dzić|14 +wyjścia|wyj=ścia|14 +wykrzykiwał|wy=krzy=ki=wał|14 +zakręcił|za=krę=cił|14 +zapach|za=pach|14 +zapatrzona|za=pa=trzo=na|14 +zapomniała|za=po=mnia=ła|14 +zapraszał|za=pra=szał|14 +zawrócił|za=wró=cił|14 +zaśpiewały|za=śpie=wa=ły|14 +zbytnio|zbyt=nio|14 +ziarna|ziar=na|14 +zjawił|zja=wił|14 +zmiłowanie|zmi=ło=wa=nie|14 +zrobię|zro=bię|14 +zupełnie|zu=peł=nie|14 +ścierwo|ścier=wo|14 +śmiechów|śmie=chów|14 +śmiejąc|śmie=jąc|14 +święconą|świę=co=ną|14 +żeście|że=ście|14 +baczyć|ba=czyć|13 +bezustannie|bez=u=stan=nie|13 +biorąc|bio=rąc|13 +brzuch|brzuch|13 +buchnęły|buch=nę=ły|13 +bójcie|bój=cie|13 +ciasno|cia=sno|13 +ciemno|ciem=no|13 +cieniów|cie=niów|13 +ciągnęła|cią=gnę=ła|13 +ciągnęło|cią=gnę=ło|13 +ciężej|cię=żej|13 +ciężka|cięż=ka|13 +cofnął|cof=nął|13 +czerniały|czer=nia=ły|13 +czerwieniały|czer=wie=nia=ły|13 +czyniąc|czy=niąc|13 +dobrości|do=bro=ści|13 +drugiemu|dru=gie=mu|13 +dworem|dwo=rem|13 +Dzieci|Dzie=ci|13 +dziesiątkę|dzie=siąt=kę|13 +dziwno|dziw=no|13 +godzina|go=dzi=na|13 +gorzałka|go=rzał=ka|13 +gorzałką|go=rzał=ką|13 +gorące|go=rą=ce|13 +gospodarstwie|go=spo=dar=stwie|13 +grzbiet|grzbiet|13 +gęstwą|gę=stwą|13 +huczały|hu=cza=ły|13 +kijami|ki=ja=mi|13 +kolację|ko=la=cję|13 +koniom|ko=niom|13 +krzyczy|krzy=czy|13 +krótki|krót=ki|13 +książce|książ=ce|13 +kwiatem|kwia=tem|13 +lipeckich|li=pec=kich|13 +miłosierny|mi=ło=sier=ny|13 +mroków|mro=ków|13 +muzyki|mu=zy=ki|13 +najlepsze|naj=lep=sze|13 +namiętnie|na=mięt=nie|13 +nasienie|na=sie=nie|13 +niechętnie|nie=chęt=nie|13 +niedziela|nie=dzie=la|13 +niespodzianie|nie=spo=dzia=nie|13 +niestrudzenie|nie=stru=dze=nie|13 +niewymłócone|nie=wy=młó=co=ne|13 +niezgorszy|nie=zgor=szy|13 +niskie|ni=skie|13 +nocami|no=ca=mi|13 +nowina|no=wi=na|13 +obejściach|obej=ściach|13 +obejściu|obej=ściu|13 +oborze|obo=rze|13 +obrazów|ob=ra=zów|13 +odchodnym|od=chod=nym|13 +oderwać|ode=rwać|13 +odparła|od=par=ła|13 +ogromnym|ogrom=nym|13 +ogromną|ogrom=ną|13 +ojciec|oj=ciec|13 +ojcowe|oj=co=we|13 +oknach|oknach|13 +parobcy|pa=rob=cy|13 +Pańskiego|Pań=skie=go|13 +piekła|pie=kła|13 +piersiami|pier=sia=mi|13 +pochowek|po=cho=wek|13 +poganiał|po=ga=niał|13 +pomyśleć|po=my=śleć|13 +popadło|po=pa=dło|13 +poredzając|po=re=dza=jąc|13 +porwała|po=rwa=ła|13 +porządki|po=rząd=ki|13 +porządku|po=rząd=ku|13 +posypały|po=sy=pa=ły|13 +powlókł|po=wlókł|13 +przejęty|prze=ję=ty|13 +przyjechał|przy=je=chał|13 +przyodziewy|przy=odzie=wy|13 +przypiekało|przy=pie=ka=ło|13 +puścili|pu=ści=li|13 +puściła|pu=ści=ła|13 +płakał|pła=kał|13 +płotach|pło=tach|13 +płynęły|pły=nę=ły|13 +Rochem|Ro=chem|13 +rozeszli|ro=ze=szli|13 +rozlegało|roz=le=ga=ło|13 +rozmyślać|roz=my=ślać|13 +rozumiejąc|ro=zu=mie=jąc|13 +rzekli|rze=kli|13 +różnie|róż=nie|13 +samemu|sa=me=mu|13 +sennie|sen=nie|13 +Siadajcie|Sia=daj=cie|13 +siedzieć|sie=dzieć|13 +silnie|sil=nie|13 +skończy|skoń=czy|13 +smutek|smu=tek|13 +spieka|spie=ka|13 +sposobu|spo=so=bu|13 +spotkał|spo=tkał|13 +stadami|sta=da=mi|13 +strachliwie|stra=chli=wie|13 +straszne|strasz=ne|13 +strugi|stru=gi|13 +szerokie|sze=ro=kie|13 +Szymka|Szym=ka|13 +słoniny|sło=ni=ny|13 +słuchała|słu=cha=ła|13 +tartak|tar=tak|13 +trocha|tro=cha|13 +turkotał|tur=ko=tał|13 +twarzą|twa=rzą|13 +tysiące|ty=sią=ce|13 +tłumaczyła|tłu=ma=czy=ła|13 +waliła|wa=li=ła|13 +weselem|we=se=lem|13 +wiedzieli|wie=dzie=li|13 +wieprzka|wie=prz=ka|13 +wschodu|wscho=du|13 +wybuchnął|wy=buch=nął|13 +wzrokiem|wzro=kiem|13 +zaczęło|za=czę=ło|13 +zapamiętał|za=pa=mię=tał|13 +zauważyła|za=uwa=ży=ła|13 +zazdrość|za=zdrość|13 +ziarno|ziar=no|13 +zwiesny|zwie=sny|13 +zwłaszcza|zwłasz=cza|13 +zębach|zę=bach|13 +zębami|zę=ba=mi|13 +złodziej|zło=dziej|13 +złotówkę|zło=tów=kę|13 +złoście|zło=ście|13 +łakomie|ła=ko=mie|13 +ścierwa|ścier=wa|13 +śpiewać|śpie=wać|13 +światy|świa=ty|13 +świąteczne|świą=tecz=ne|13 +świętych|świę=tych|13 +żelazo|że=la=zo|13 +barłogu|bar=ło=gu|12 +beczki|becz=ki|12 +bełkotał|beł=ko=tał|12 +biedro|bie=dro|12 +będziecie|bę=dzie=cie|12 +będziemy|bę=dzie=my|12 +chcesz|chcesz|12 +chodzić|cho=dzić|12 +chytrze|chy=trze|12 +Chłopi|Chło=pi|12 +ciemnościach|ciem=no=ściach|12 +cisnął|ci=snął|12 +ciężkiej|cięż=kiej|12 +czując|czu=jąc|12 +Częstochowy|Czę=sto=cho=wy|12 +częstował|czę=sto=wał|12 +Człowiek|Czło=wiek|12 +dalekich|da=le=kich|12 +deszczu|desz=czu|12 +Dobrodziej|Do=bro=dziej|12 +domami|do=ma=mi|12 +dworowi|dwo=ro=wi|12 +dworskich|dwor=skich|12 +dziewczyn|dziew=czyn|12 +dziewka|dziew=ka|12 +dziewki|dziew=ki|12 +godzinie|go=dzi=nie|12 +gorąca|go=rą=ca|12 +gospodarzu|go=spo=da=rzu|12 +gwiazdami|gwiaz=da=mi|12 +głodny|głod=ny|12 +głucha|głu=cha|12 +głęboka|głę=bo=ka|12 +Idźcie|Idź=cie|12 +inwentarz|in=wen=tarz|12 +Jagata|Ja=ga=ta|12 +jękiem|ję=kiem|12 +kijaszkiem|ki=jasz=kiem|12 +komornice|ko=mor=ni=ce|12 +komorników|ko=mor=ni=ków|12 +koniczyny|ko=ni=czy=ny|12 +Kozioł|Ko=zioł|12 +krowie|kro=wie|12 +krwawe|krwa=we|12 +krzyczeli|krzy=cze=li|12 +krzywą|krzy=wą|12 +księdzu|księ=dzu|12 +kupować|ku=po=wać|12 +kurzawie|ku=rza=wie|12 +Lektur|Lek=tur|12 +Lektury|Lek=tu=ry|12 +ludzką|ludz=ką|12 +lśniły|lśni=ły|12 +mgnienie|mgnie=nie|12 +miedzami|mie=dza=mi|12 +Miemców|Miem=ców|12 +myśleć|my=śleć|12 +nagrzane|na=grza=ne|12 +najgłośniej|naj=gło=śniej|12 +najpierwsze|naj=pierw=sze|12 +Nastki|Na=st=ki|12 +niekajś|nie=kajś|12 +niezgorsze|nie=zgor=sze|12 +nozdrzach|noz=drzach|12 +obrządków|ob=rząd=ków|12 +oczywiście|oczy=wi=ście|12 +odmianę|od=mia=nę|12 +odpowiadała|od=po=wia=da=ła|12 +odpuście|od=pu=ście|12 +odwracając|od=wra=ca=jąc|12 +ogarniała|ogar=nia=ła|12 +okazji|oka=zji|12 +opłotków|opłot=ków|12 +orzydle|orzy=dle|12 +ostali|osta=li|12 +oszalałe|osza=la=łe|12 +otwarty|otwar=ty|12 +ołtarz|oł=tarz|12 +pacierzów|pa=cie=rzów|12 +paliły|pa=li=ły|12 +parobkami|pa=rob=ka=mi|12 +pierwszym|pierw=szym|12 +piorun|pio=run|12 +plecami|ple=ca=mi|12 +pochylił|po=chy=lił|12 +pochylone|po=chy=lo=ne|12 +pociągnął|po=cią=gnął|12 +poczęły|po=czę=ły|12 +podziwem|po=dzi=wem|12 +podłogę|pod=ło=gę|12 +pojechali|po=je=cha=li|12 +ponosiło|po=no=si=ło|12 +portkach|por=t=kach|12 +porządny|po=rząd=ny|12 +poszło|po=szło|12 +powiał|po=wiał|12 +powiedzą|po=wie=dzą|12 +powoli|po=wo=li|12 +powracał|po=wra=cał|12 +powrotu|po=wro=tu|12 +powstając|po=wsta=jąc|12 +położyła|po=ło=ży=ła|12 +pościele|po=ście=le|12 +prawić|pra=wić|12 +przeciwko|prze=ciw=ko|12 +przejść|przejść|12 +przeróżne|prze=róż=ne|12 +przychodziło|przy=cho=dzi=ło|12 +przychodziły|przy=cho=dzi=ły|12 +przygięte|przy=gię=te|12 +przyleciała|przy=le=cia=ła|12 +przystanęła|przy=sta=nę=ła|12 +ptactwo|ptac=two|12 +rozchodzić|roz=cho=dzić|12 +rozpowiadał|roz=po=wia=dał|12 +rozważał|roz=wa=żał|12 +skowronki|skow=ron=ki|12 +skręcił|skrę=cił|12 +spokoju|spo=ko=ju|12 +społecznie|spo=łecz=nie|12 +sprzedał|sprze=dał|12 +stanęły|sta=nę=ły|12 +stronami|stro=na=mi|12 +sygnaturka|sy=gna=tur=ka|12 +szykować|szy=ko=wać|12 +sąsiadów|są=sia=dów|12 +słodkim|słod=kim|12 +topole|to=po=le|12 +trzaskał|trza=skał|12 +twarzach|twa=rzach|12 +uderzył|ude=rzył|12 +uśmiechem|uśmie=chem|12 +wesołe|we=so=łe|12 +wespół|we=spół|12 +Wiater|Wia=ter|12 +wichry|wi=chry|12 +widzisz|wi=dzisz|12 +Widzisz|Wi=dzisz|12 +wieczerzy|wie=cze=rzy|12 +winowaty|wi=no=wa=ty|12 +większą|więk=szą|12 +Wolnych|Wol=nych|12 +wołali|wo=ła=li|12 +wrzasków|wrza=sków|12 +wrzało|wrza=ło|12 +wstawał|wsta=wał|12 +wstydu|wsty=du|12 +wyglądał|wy=glą=dał|12 +wyjdzie|wyj=dzie|12 +wyniesło|wy=nie=sło|12 +wynieśli|wy=nie=śli|12 +wyrozumieć|wy=ro=zu=mieć|12 +wyrwać|wy=rwać|12 +wyrzekał|wy=rze=kał|12 +wyrzekł|wy=rzekł|12 +wytrzymania|wy=trzy=ma=nia|12 +wywarła|wy=war=ła|12 +wzmagał|wzma=gał|12 +zabierając|za=bie=ra=jąc|12 +zachód|za=chód|12 +zagadywał|za=ga=dy=wał|12 +zagona|za=go=na|12 +zapadał|za=pa=dał|12 +zapaską|za=pa=ską|12 +zawziętość|za=wzię=tość|12 +zgrozy|zgro=zy|12 +zmilkła|zmil=kła|12 +zrobili|zro=bi=li|12 +zrozumieć|zro=zu=mieć|12 +zwalił|zwa=lił|12 +zwaliła|zwa=li=ła|12 +złapać|zła=pać|12 +złodzieje|zło=dzie=je|12 +śniadania|śnia=da=nia|12 +śniadanie|śnia=da=nie|12 +śpiewania|śpie=wa=nia|12 +świateł|świa=teł|12 +żałosne|ża=ło=sne|12 +Antków|Ant=ków|11 +barany|ba=ra=ny|11 +Borynowe|Bo=ry=no=we|11 +Bożego|Bo=że=go|11 +brakło|bra=kło|11 +brogiem|bro=giem|11 +brzaski|brza=ski|11 +bystro|by=stro|11 +całując|ca=łu=jąc|11 +chorągwie|cho=rą=gwie|11 +chwycił|chwy=cił|11 +chwytał|chwy=tał|11 +Chłopy|Chło=py|11 +Cichojta|Ci=choj=ta|11 +ciemna|ciem=na|11 +ciepnąć|ciep=nąć|11 +ciężarem|cię=ża=rem|11 +czerwieniły|czer=wie=ni=ły|11 +czystą|czy=stą|11 +dawała|da=wa=ła|11 +dawnego|daw=ne=go|11 +dobroci|do=bro=ci|11 +dochodził|do=cho=dził|11 +dorzuciła|do=rzu=ci=ła|11 +dygotał|dy=go=tał|11 +dziewucha|dzie=wu=cha|11 +elegancko|ele=ganc=ko|11 +gniewem|gnie=wem|11 +gniewu|gnie=wu|11 +gospodarka|go=spo=dar=ka|11 +gospodarza|go=spo=da=rza|11 +gruchnęła|gruch=nę=ła|11 +gąszczach|gąsz=czach|11 +głosami|gło=sa=mi|11 +głęboką|głę=bo=ką|11 +jajków|jaj=ków|11 +jakich|ja=kich|11 +jarzyły|ja=rzy=ły|11 +jechać|je=chać|11 +jesieni|je=sie=ni|11 +jękliwie|ję=kli=wie|11 +jęknął|jęk=nął|11 +karczmą|karcz=mą|11 +kazała|ka=za=ła|11 +kałdun|kał=dun|11 +kobietom|ko=bie=tom|11 +kochania|ko=cha=nia|11 +korale|ko=ra=le|11 +kożuchem|ko=żu=chem|11 +krzepki|krzep=ki|11 +krzyża|krzy=ża|11 +księże|księ=że|11 +kwardy|kwar=dy|11 +kwiatami|kwia=ta=mi|11 +kładły|kła=dły|11 +Kłębowej|Kłę=bo=wej|11 +ludzki|ludz=ki|11 +Macieja|Ma=cie=ja|11 +martwe|mar=twe|11 +Marysia|Ma=ry=sia|11 +medytował|me=dy=to=wał|11 +miarkować|miar=ko=wać|11 +miejscach|miej=scach|11 +modlitwę|mo=dli=twę|11 +morzył|mo=rzył|11 +możecie|mo=że=cie|11 +musiało|mu=sia=ło|11 +mówicie|mó=wi=cie|11 +młynowi|mły=no=wi|11 +najwięcej|naj=wię=cej|11 +naleźć|na=leźć|11 +naszego|na=sze=go|11 +niechcenia|nie=chce=nia|11 +niedaleko|nie=da=le=ko|11 +niemały|nie=ma=ły|11 +nieprzytomnie|nie=przy=tom=nie|11 +niewiela|nie=wie=la|11 +niewolił|nie=wo=lił|11 +nowinę|no=wi=nę|11 +obchodzić|ob=cho=dzić|11 +obcierając|ob=cie=ra=jąc|11 +obiadu|obia=du|11 +obzierając|obzie=ra=jąc|11 +odpoczywać|od=po=czy=wać|11 +odpowiedzi|od=po=wie=dzi|11 +odrobek|od=ro=bek|11 +odwróciła|od=wró=ci=ła|11 +ogniach|ogniach|11 +opowiadał|opo=wia=dał|11 +opowiadała|opo=wia=da=ła|11 +palcach|pal=cach|11 +pamiętać|pa=mię=tać|11 +pastwisk|pa=stwisk|11 +pazuchę|pa=zu=chę|11 +Pański|Pań=ski|11 +Pewnikiem|Pew=ni=kiem|11 +pilnował|pil=no=wał|11 +pluchy|plu=chy|11 +pobożne|po=boż=ne|11 +pobożnie|po=boż=nie|11 +począł|po=czął|11 +poglądał|po=glą=dał|11 +pogwary|po=gwa=ry|11 +pokoje|po=ko=je|11 +pomiarkował|po=miar=ko=wał|11 +ponieść|po=nieść|11 +poradzę|po=ra=dzę|11 +porządek|po=rzą=dek|11 +posłyszał|po=sły=szał|11 +powiedali|po=wie=da=li|11 +powiedzieli|po=wie=dzie=li|11 +powróciła|po=wró=ci=ła|11 +pozwoli|po=zwo=li|11 +połednie|po=łed=nie|11 +prawdy|praw=dy|11 +Proboszcz|Pro=boszcz|11 +przedzwonili|prze=dzwo=ni=li|11 +przemówić|prze=mó=wić|11 +przepijali|prze=pi=ja=li|11 +przeszłym|prze=szłym|11 +przynaglał|przy=na=glał|11 +przypomnienie|przy=po=mnie=nie|11 +przytajone|przy=ta=jo=ne|11 +przywarła|przy=war=ła|11 +przyzbie|przy=zbie|11 +ptaszki|ptasz=ki|11 +puścić|pu=ścić|11 +pójdziemy|pój=dzie=my|11 +płachty|płach=ty|11 +płachtę|płach=tę|11 +płaczów|pła=czów|11 +radosnym|ra=do=snym|11 +rajcowały|raj=co=wa=ły|11 +raźniej|raź=niej|11 +robotą|ro=bo=tą|11 +rodzone|ro=dzo=ne|11 +rodzony|ro=dzo=ny|11 +rolach|ro=lach|11 +rowach|ro=wach|11 +rozcież|roz=cież|11 +rozlewały|roz=le=wa=ły|11 +rozniesło|roz=nie=sło|11 +Rychtyk|Rych=tyk|11 +rzucając|rzu=ca=jąc|11 +rzucały|rzu=ca=ły|11 +rzędem|rzę=dem|11 +różnoście|róż=no=ście|11 +siedząc|sie=dząc|11 +sinych|si=nych|11 +skarżyła|skar=ży=ła|11 +skrzydła|skrzy=dła|11 +skrzypicy|skrzy=pi=cy|11 +skrzypki|skrzyp=ki|11 +skwapnie|skwap=nie|11 +sołtysem|soł=ty=sem|11 +spiekota|spie=ko=ta|11 +spodoba|spodo=ba|11 +sprawą|spra=wą|11 +starszym|star=szym|11 +stawała|sta=wa=ła|11 +stawiał|sta=wiał|11 +stodołę|sto=do=łę|11 +straszną|strasz=ną|11 +swarów|swa=rów|11 +sypały|sy=pa=ły|11 +Szymon|Szy=mon|11 +szynkwasie|szynk=wa=sie|11 +sądzie|są=dzie|11 +sąsiedzku|są=siedz=ku|11 +słodki|słod=ki|11 +tańcował|tań=co=wał|11 +trzeci|trze=ci|11 +trzymać|trzy=mać|11 +trzymała|trzy=ma=ła|11 +turbacja|tur=ba=cja|11 +turkot|tur=kot|11 +tymczasem|tym=cza=sem|11 +tłukły|tłu=kły|11 +urządzenie|urzą=dze=nie|11 +uspokajać|uspo=ka=jać|11 +wargach|war=gach|11 +wchodząc|wcho=dząc|11 +weselu|we=se=lu|11 +weszła|we=szła|11 +wichura|wi=chu=ra|11 +wielka|wiel=ka|11 +wierzcie|wierz=cie|11 +winien|wi=nien|11 +wrótnie|wrót=nie|11 +wsparłszy|wsparł=szy|11 +wspominki|wspo=min=ki|11 +wstęgi|wstę=gi|11 +wybrał|wy=brał|11 +wygnał|wy=gnał|11 +wyrywał|wy=ry=wał|11 +wyszli|wy=szli|11 +wzdychali|wzdy=cha=li|11 +wzgardliwie|wzgar=dli=wie|11 +Wójtowa|Wój=to=wa|11 +wójtowej|wój=to=wej|11 +zabawa|za=ba=wa|11 +zabrać|za=brać|11 +zaczną|za=czną|11 +zagadnęła|za=gad=nę=ła|11 +zagrały|za=gra=ły|11 +zapatrzony|za=pa=trzo=ny|11 +zapowiedzie|za=po=wie=dzie|11 +zapraszała|za=pra=sza=ła|11 +zasnął|za=snął|11 +zaszło|za=szło|11 +zatrzęsła|za=trzę=sła|11 +zatrzęsły|za=trzę=sły|11 +zebrała|ze=bra=ła|11 +zebrało|ze=bra=ło|11 +zerwała|ze=rwa=ła|11 +zgodzie|zgo=dzie|11 +zjedzą|zje=dzą|11 +zmroku|zmro=ku|11 +znajomków|zna=jom=ków|11 +znaleźć|zna=leźć|11 +zwrócił|zwró=cił|11 +ściana|ścia=na|11 +świętego|świę=te=go|11 +żałował|ża=ło=wał|11 +żałość|ża=łość|11 +Balcerek|Bal=ce=rek|10 +baldach|bal=dach|10 +biedzie|bie=dzie|10 +bokach|bo=kach|10 +bokiem|bo=kiem|10 +bratem|bra=tem|10 +bruzdach|bruz=dach|10 +brzęczały|brzę=cza=ły|10 +brzękiem|brzę=kiem|10 +chciały|chcia=ły|10 +chrzciny|chrzci=ny|10 +chórem|chó=rem|10 +Chłopaki|Chło=pa=ki|10 +chłopcy|chłop=cy|10 +chłopem|chło=pem|10 +Cichocie|Ci=cho=cie|10 +cichuśkim|ci=chuś=kim|10 +ciekły|cie=kły|10 +ciemnicy|ciem=ni=cy|10 +cienia|cie=nia|10 +ciepłe|cie=płe|10 +czarnych|czar=nych|10 +czarną|czar=ną|10 +Częstochowskiej|Czę=sto=chow=skiej|10 +dajcie|daj=cie|10 +daleki|da=le=ki|10 +deszcze|desz=cze|10 +dobrodziejem|do=bro=dzie=jem|10 +dobrych|do=brych|10 +dróżkę|dróż=kę|10 +dwojaków|dwo=ja=ków|10 +dzieuchami|dzieu=cha=mi|10 +długiej|dłu=giej|10 +Filipka|Fi=lip=ka|10 +Franek|Fra=nek|10 +gardło|gar=dło|10 +gospodarkę|go=spo=dar=kę|10 +gospodarski|go=spo=dar=ski|10 +gospodarstwa|go=spo=dar=stwa|10 +gotowego|go=to=we=go|10 +gromada|gro=ma=da|10 +gromady|gro=ma=dy|10 +grzechów|grze=chów|10 +Gulbasiak|Gul=ba=siak|10 +gąszcz|gąszcz|10 +gęstwę|gę=stwę|10 +głosować|gło=so=wać|10 +Głupia|Głu=pia|10 +huknął|huk=nął|10 +idźcie|idź=cie|10 +inszych|in=szych|10 +izbach|izbach|10 +jarmarek|jar=ma=rek|10 +jedzenie|je=dze=nie|10 +Jezusem|Je=zu=sem|10 +jąkała|ją=ka=ła|10 +jęczał|ję=czał|10 +Jędrek|Ję=drek|10 +kartofle|kar=to=fle|10 +karwas|kar=was|10 +kobietami|ko=bie=ta=mi|10 +kobietą|ko=bie=tą|10 +kokosz|ko=kosz|10 +kolebały|ko=le=ba=ły|10 +koszula|ko=szu=la|10 +koszulę|ko=szu=lę|10 +kowalowie|ko=wa=lo=wie|10 +Kozłową|Ko=zło=wą|10 +kredyt|kre=dyt|10 +krokiem|kro=kiem|10 +krzyczały|krzy=cza=ły|10 +krzycząc|krzy=cząc|10 +krzyże|krzy=że|10 +księża|księ=ża|10 +księżą|księ=żą|10 +któregoś|któ=re=goś|10 +kurzyło|ku=rzy=ło|10 +kądzielą|ką=dzie=lą|10 +leżało|le=ża=ło|10 +ludzkich|ludz=kich|10 +majaczyły|ma=ja=czy=ły|10 +mdlały|mdla=ły|10 +miałem|mia=łem|10 +miedzy|mie=dzy|10 +milczał|mil=czał|10 +morgach|mor=gach|10 +moście|mo=ście|10 +muzykę|mu=zy=kę|10 +Mówili|Mó=wi=li|10 +mądrala|mą=dra=la|10 +Młynarz|Mły=narz|10 +nadeszła|na=de=szła|10 +nadszedł|nad=szedł|10 +najlepszy|naj=lep=szy|10 +Nastką|Na=st=ką|10 +naszczekiwały|na=szcze=ki=wa=ły|10 +niemałym|nie=ma=łym|10 +nieobjęte|nie=ob=ję=te|10 +Nieprawda|Nie=praw=da|10 +nieszpory|nie=szpo=ry|10 +nowych|no=wych|10 +obcierał|ob=cie=rał|10 +Obejrzał|Obej=rzał|10 +obraza|ob=ra=za|10 +obrazami|ob=ra=za=mi|10 +odebrać|ode=brać|10 +odezwał|ode=zwał|10 +odezwała|ode=zwa=ła|10 +odpocząć|od=po=cząć|10 +odzywał|od=zy=wał|10 +ogarniając|ogar=nia=jąc|10 +ogonem|ogo=nem|10 +opadały|opa=da=ły|10 +organów|or=ga=nów|10 +osobno|osob=no|10 +pachnące|pach=ną=ce|10 +pachnący|pach=ną=cy|10 +paciorki|pa=cior=ki|10 +piasek|pia=sek|10 +pierwszych|pierw=szych|10 +Pietrkowi|Pietr=ko=wi|10 +Pietruś|Pie=truś|10 +Pietrze|Pie=trze|10 +piętnaście|pięt=na=ście|10 +pięści|pię=ści|10 +poczęli|po=czę=li|10 +poczęła|po=czę=ła|10 +podnieść|pod=nieść|10 +podobne|po=dob=ne|10 +podłodze|pod=ło=dze|10 +pogasły|po=ga=sły|10 +poglądając|po=glą=da=jąc|10 +pojedynkę|po=je=dyn=kę|10 +pokazywał|po=ka=zy=wał|10 +pokaże|po=ka=że|10 +pomiarkować|po=miar=ko=wać|10 +pomocą|po=mo=cą|10 +pomyślała|po=my=śla=ła|10 +ponuro|po=nu=ro|10 +porozchodzili|po=roz=cho=dzi=li|10 +Poszedł|Po=szedł|10 +potrzebuje|po=trze=bu=je|10 +Powiadali|Po=wia=da=li|10 +powiedz|po=wiedz|10 +powinien|po=wi=nien|10 +powstrzymać|po=wstrzy=mać|10 +procesji|pro=ce=sji|10 +przejmowała|przej=mo=wa=ła|10 +przekpinki|prze=kpin=ki|10 +przenikliwie|prze=ni=kli=wie|10 +przerażenia|prze=ra=że=nia|10 +przerwy|prze=rwy|10 +przesiaduje|prze=sia=du=je|10 +przychodziła|przy=cho=dzi=ła|10 +przyjdź|przyjdź|10 +przypołudnie|przy=po=łu=dnie|10 +przywieźli|przy=wieź=li|10 +psiachmać|psiach=mać|10 +płacić|pła=cić|10 +płakaniem|pła=ka=niem|10 +płomieniami|pło=mie=nia=mi|10 +radosne|ra=do=sne|10 +radosny|ra=do=sny|10 +radoście|ra=do=ście|10 +rozpowiadając|roz=po=wia=da=jąc|10 +rozpowiadała|roz=po=wia=da=ła|10 +rozważać|roz=wa=żać|10 +rośnie|ro=śnie|10 +rykiem|ry=kiem|10 +rymnął|rym=nął|10 +rzucili|rzu=ci=li|10 +różańca|ró=żań=ca|10 +schował|scho=wał|10 +siedziało|sie=dzia=ło|10 +Sikora|Si=ko=ra|10 +skoczyła|sko=czy=ła|10 +skończyło|skoń=czy=ło|10 +skądciś|skąd=ciś|10 +sobotę|so=bo=tę|10 +spadały|spa=da=ły|10 +spojrzeć|spoj=rzeć|10 +spostrzegł|spo=strzegł|10 +sprzeciw|sprze=ciw|10 +sprzedać|sprze=dać|10 +sprzęt|sprzęt|10 +spływały|spły=wa=ły|10 +stanęli|sta=nę=li|10 +starej|sta=rej|10 +starsi|star=si|10 +stodole|sto=do=le|10 +szarych|sza=rych|10 +szczerze|szcze=rze|10 +szeroką|sze=ro=ką|10 +szkoły|szko=ły|10 +szpitala|szpi=ta=la|10 +szumem|szu=mem|10 +słodką|słod=ką|10 +służył|słu=żył|10 +tańcowali|tań=co=wa=li|10 +tańcować|tań=co=wać|10 +Tereskę|Te=re=skę|10 +trumnę|trum=nę|10 +trzaskały|trza=ska=ły|10 +trzymają|trzy=ma=ją|10 +trzymając|trzy=ma=jąc|10 +trzymali|trzy=ma=li|10 +trzęsło|trzę=sło|10 +tuliła|tu=li=ła|10 +twarde|twar=de|10 +twardy|twar=dy|10 +uniesła|unie=sła|10 +urzędzie|urzę=dzie|10 +usiadł|usiadł|10 +uważać|uwa=żać|10 +uwierzyć|uwie=rzyć|10 +Wachnik|Wach=nik|10 +walili|wa=li=li|10 +waliły|wa=li=ły|10 +weszli|we=szli|10 +weźcie|weź=cie|10 +widniał|wid=niał|10 +widocznie|wi=docz=nie|10 +wisiał|wi=siał|10 +wnetki|wnet=ki|10 +wolność|wol=ność|10 +wozach|wo=zach|10 +wołania|wo=ła=nia|10 +wrzątku|wrząt=ku|10 +wschodzie|wscho=dzie|10 +wsparł|wsparł|10 +wstrzymał|wstrzy=mał|10 +wszelki|wszel=ki|10 +wybrany|wy=bra=ny|10 +wydzierał|wy=dzie=rał|10 +wydzierały|wy=dzie=ra=ły|10 +wyglądała|wy=glą=da=ła|10 +wyjrzała|wyj=rza=ła|10 +wyprawia|wy=pra=wia|10 +wyrabia|wy=ra=bia|10 +wyrwało|wy=rwa=ło|10 +wysoki|wy=so=ki|10 +wysunął|wy=su=nął|10 +wywarty|wy=war=ty|10 +zagadał|za=ga=dał|10 +zagonów|za=go=nów|10 +zagrała|za=gra=ła|10 +zalały|za=la=ły|10 +zapalił|za=pa=lił|10 +zapowietrzone|za=po=wie=trzo=ne|10 +zapłaci|za=pła=ci|10 +zaraza|za=ra=za|10 +zawracał|za=wra=cał|10 +zbożach|zbo=żach|10 +zbraknie|zbrak=nie|10 +zebrać|ze=brać|10 +zeźlony|ze=źlo=ny|10 +zgodnie|zgod=nie|10 +zielenią|zie=le=nią|10 +ziemiami|zie=mia=mi|10 +zważając|zwa=ża=jąc|10 +zwijała|zwi=ja=ła|10 +ścianie|ścia=nie|10 +ściągali|ścią=ga=li|10 +ślepiów|śle=piów|10 +śliczności|ślicz=no=ści|10 +śmiertelny|śmier=tel=ny|10 +śpiewali|śpie=wa=li|10 +śpiewań|śpie=wań|10 +świecił|świe=cił|10 +żałosnym|ża=ło=snym|10 +żałośliwie|ża=ło=śli=wie|10 +żegnał|że=gnał|10 +Bartka|Bart=ka|9 +białym|bia=łym|9 +bieliły|bie=li=ły|9 +bolały|bo=la=ły|9 +boleści|bo=le=ści|9 +Borynowa|Bo=ry=no=wa|9 +Borynową|Bo=ry=no=wą|9 +boskie|bo=skie|9 +bracie|bra=cie|9 +buchała|bu=cha=ła|9 +budzić|bu=dzić|9 +buzował|bu=zo=wał|9 +byłoby|by=ło=by|9 +Chciała|Chcia=ła|9 +chciałem|chcia=łem|9 +chciwie|chci=wie|9 +chmurzyska|chmu=rzy=ska|9 +Chodził|Cho=dził|9 +chusty|chu=s=ty|9 +chętliwie|chę=tli=wie|9 +chętnie|chęt=nie|9 +Chłopak|Chło=pak|9 +Cichość|Ci=chość|9 +cichych|ci=chych|9 +ciemię|cie=mię|9 +cieniami|cie=nia=mi|9 +cisnęli|ci=snę=li|9 +ciągnie|cią=gnie|9 +ciągną|cią=gną|9 +ciągotki|cią=got=ki|9 +ciężką|cięż=ką|9 +cudzego|cu=dze=go|9 +Czasem|Cza=sem|9 +Czemuż|Cze=muż|9 +czerwono|czer=wo=no|9 +czystym|czy=stym|9 +czytać|czy=tać|9 +człowiekowa|czło=wie=ko=wa|9 +dalekości|da=le=ko=ści|9 +dawnemu|daw=ne=mu|9 +dobrotliwie|do=bro=tli=wie|9 +dobrość|do=brość|9 +dojrzeć|doj=rzeć|9 +dostać|do=stać|9 +dosłyszał|do=sły=szał|9 +drewniane|drew=nia=ne|9 +drobny|drob=ny|9 +drużbowie|druż=bo=wie|9 +drzewach|drze=wach|9 +drzewinami|drze=wi=na=mi|9 +duszno|dusz=no|9 +dychać|dy=chać|9 +dziadów|dzia=dów|9 +dziękować|dzię=ko=wać|9 +długich|dłu=gich|9 +dźwigać|dźwi=gać|9 +figlów|fi=glów|9 +figury|fi=gu=ry|9 +figurą|fi=gu=rą|9 +garnki|garn=ki|9 +godzić|go=dzić|9 +gorący|go=rą=cy|9 +gospodarskie|go=spo=dar=skie|9 +gospodarsku|go=spo=dar=sku|9 +Gospodyni|Go=spo=dy=ni|9 +gotowi|go=to=wi|9 +grania|gra=nia|9 +gronta|gron=ta|9 +gruchnął|gruch=nął|9 +grzmoty|grzmo=ty|9 +głownie|głow=nie|9 +głupawy|głu=pa=wy|9 +Głupie|Głu=pie|9 +głębokie|głę=bo=kie|9 +herbatę|her=ba=tę|9 +jakbyś|jak=byś|9 +jastrząb|ja=strząb|9 +Jastrząb|Ja=strząb|9 +jednych|jed=nych|9 +jeździł|jeź=dził|9 +kapusta|ka=pu=sta|9 +klepisko|kle=pi=sko|9 +kobiece|ko=bie=ce|9 +koguty|ko=gu=ty|9 +kompanii|kom=pa=nii|9 +koniczynę|ko=ni=czy=nę|9 +kopyta|ko=py=ta|9 +kruchcie|kruch=cie|9 +krzyku|krzy=ku|9 +krzyków|krzy=ków|9 +krzywdą|krzyw=dą|9 +kudłów|ku=dłów|9 +kładła|kła=dła|9 +kłosami|kło=sa=mi|9 +Kłębiak|Kłę=biak|9 +Kłębową|Kłę=bo=wą|9 +lamenty|la=men=ty|9 +Ledwie|Le=d=wie|9 +Lipczaki|Lip=cza=ki|9 +lubości|lu=bo=ści|9 +Mateuszowi|Ma=te=uszo=wi|9 +miedzę|mie=dzę|9 +miejsc|miejsc|9 +miesiąc|mie=siąc|9 +minuty|mi=nu=ty|9 +miętko|mięt=ko|9 +miłowania|mi=ło=wa=nia|9 +modrym|mo=drym|9 +mrozie|mro=zie|9 +musiały|mu=sia=ły|9 +myślach|my=ślach|9 +myślałam|my=śla=łam|9 +mówcie|mów=cie|9 +młodzi|mło=dzi|9 +Naczelnik|Na=czel=nik|9 +naczelnika|na=czel=ni=ka|9 +naczynie|na=czy=nie|9 +nadział|na=dział|9 +nagich|na=gich|9 +nastał|na=stał|9 +Nastusią|Na=stu=sią|9 +natknął|na=tknął|9 +niechcący|nie=chcą=cy|9 +niejednego|nie=jed=ne=go|9 +niejedną|nie=jed=ną|9 +nieszczęście|nie=szczę=ście|9 +nieustępliwa|nie=ustę=pli=wa|9 +niewiele|nie=wie=le|9 +nosiła|no=si=ła|9 +nowiną|no=wi=ną|9 +obaczy|oba=czy|9 +obaczycie|oba=czy=cie|9 +obchodził|ob=cho=dził|9 +obejmując|obej=mu=jąc|9 +obejście|obej=ście|9 +obiecał|obie=cał|9 +odpowiedział|od=po=wie=dział|9 +ogarnął|ogar=nął|9 +ogrodach|ogro=dach|9 +ojcową|oj=co=wą|9 +okapem|oka=pem|9 +opowiedział|opo=wie=dział|9 +ostatka|ostat=ka|9 +ostatnich|ostat=nich|9 +otworzyć|otwo=rzyć|9 +pacierzu|pa=cie=rzu|9 +padało|pa=da=ło|9 +paskudne|pa=skud=ne|9 +Pańskie|Pań=skie|9 +piechty|piech=ty|9 +piekło|pie=kło|9 +pierwej|pier=wej|9 +piesek|pie=sek|9 +pieska|pie=ska|9 +pilnuj|pil=nuj|9 +pochwalił|po=chwa=lił|9 +pochylonych|po=chy=lo=nych|9 +pociechy|po=cie=chy|9 +poczciwy|po=czci=wy|9 +Poczekaj|Po=cze=kaj|9 +początku|po=cząt=ku|9 +podatki|po=dat=ki|9 +podała|po=da=ła|9 +podczas|pod=czas|9 +Podniesienia|Pod=nie=sie=nia|9 +podnosiło|pod=no=si=ło|9 +podobien|po=do=bien|9 +podziwu|po=dzi=wu|9 +pogadał|po=ga=dał|9 +poglądała|po=glą=da=ła|9 +pojadę|po=ja=dę|9 +pokazała|po=ka=za=ła|9 +pokoju|po=ko=ju|9 +pokryty|po=kry=ty|9 +poletę|po=le=tę|9 +pomstował|po=msto=wał|9 +popatrzeć|po=pa=trzeć|9 +popatrzył|po=pa=trzył|9 +poprzek|po=przek|9 +popłakując|po=pła=ku=jąc|9 +poradził|po=ra=dził|9 +poruszyć|po=ru=szyć|9 +postronku|po=stron=ku|9 +poszukać|po=szu=kać|9 +posłyszawszy|po=sły=szaw=szy|9 +potrzebny|po=trzeb=ny|9 +powiadam|po=wia=dam|9 +powtórzył|po=wtó=rzył|9 +poznał|po=znał|9 +prażył|pra=żył|9 +proboszcza|pro=bosz=cza|9 +procesją|pro=ce=sją|9 +prosiak|pro=siak|9 +przechodziły|prze=cho=dzi=ły|9 +przecierał|prze=cie=rał|9 +przecknęła|prze=ck=nę=ła|9 +przede|przede|9 +przegarniał|prze=gar=niał|9 +przejęte|prze=ję=te|9 +przeleciał|prze=le=ciał|9 +przemian|prze=mian|9 +przepijał|prze=pi=jał|9 +przepił|prze=pił|9 +przestając|prze=sta=jąc|9 +przestrzał|prze=strzał|9 +przeszły|prze=szły|9 +przełaj|prze=łaj|9 +przełazu|prze=ła=zu|9 +przygarnął|przy=gar=nął|9 +przyjacielstwo|przy=ja=ciel=stwo|9 +przykry|przy=kry|9 +przypomniał|przy=po=mniał|9 +przystawali|przy=sta=wa=li|9 +Próżno|Próż=no|9 +półgłosem|pół=gło=sem|9 +półkwaterki|pół=kwa=ter=ki|9 +płakania|pła=ka=nia|9 +płótna|płót=na|9 +radzić|ra=dzić|9 +rejwach|rej=wach|9 +rodzona|ro=dzo=na|9 +rosami|ro=sa=mi|9 +rozgłośniej|roz=gło=śniej|9 +rozległy|roz=le=gły|9 +rozmyślała|roz=my=śla=ła|9 +rozstajach|roz=sta=jach|9 +rychtować|rych=to=wać|9 +Rychło|Ry=chło|9 +ryczała|ry=cza=ła|9 +rzeźwe|rzeź=we|9 +rzęsisty|rzę=si=sty|9 +sercach|ser=cach|9 +sielna|siel=na|9 +siostry|sio=stry|9 +siostrą|sio=strą|9 +sięgnąć|się=gnąć|9 +skargi|skar=gi|9 +skargą|skar=gą|9 +skargę|skar=gę|9 +skwapliwie|skwa=pli=wie|9 +smutne|smut=ne|9 +smętarza|smę=ta=rza|9 +spadnie|spad=nie|9 +sparło|spar=ło|9 +spaśny|spa=śny|9 +spotkała|spo=tka=ła|9 +sprawie|spra=wie|9 +stamtąd|stam=tąd|9 +starych|sta=rych|9 +starzy|sta=rzy|9 +stawać|sta=wać|9 +stojący|sto=ją=cy|9 +Strach|Strach|9 +stracił|stra=cił|9 +straszna|strasz=na|9 +strasznego|strasz=ne=go|9 +straszny|strasz=ny|9 +stworzył|stwo=rzył|9 +stójka|stój=ka|9 +syknęła|syk=nę=ła|9 +szczekać|szcze=kać|9 +szczęściem|szczę=ściem|9 +Szczęściem|Szczę=ściem|9 +szybki|szyb=ki|9 +słowami|sło=wa=mi|9 +słońcem|słoń=cem|9 +słucha|słu=cha|9 +służbie|służ=bie|9 +słówkiem|słów=kiem|9 +tabaki|ta=ba=ki|9 +troskliwie|tro=skli=wie|9 +trzecią|trze=cią|9 +trzeszczały|trzesz=cza=ły|9 +turkotały|tur=ko=ta=ły|9 +twardym|twar=dym|9 +uciechą|ucie=chą|9 +ukradł|ukradł|9 +uniósł|uniósł|9 +ustami|usta=mi|9 +uśmiechał|uśmie=chał|9 +wchodził|wcho=dził|9 +wełniaków|weł=nia=ków|9 +wiatry|wia=try|9 +widziałem|wi=dzia=łem|9 +widzieć|wi=dzieć|9 +wieczory|wie=czo=ry|9 +wieków|wie=ków|9 +Wielki|Wiel=ki|9 +winowata|wi=no=wa=ta|9 +wlazło|wla=zło|9 +wolnelektury|wol=ne=lek=tu=ry|9 +wracał|wra=cał|9 +wrzeciono|wrze=cio=no|9 +wrzątkiem|wrząt=kiem|9 +wspierając|wspie=ra=jąc|9 +wstała|wsta=ła|9 +wstrzymać|wstrzy=mać|9 +wsunął|wsu=nął|9 +wszyćkie|wszyć=kie|9 +wtrącił|wtrą=cił|9 +wtrąciła|wtrą=ci=ła|9 +wybierać|wy=bie=rać|9 +wybierał|wy=bie=rał|9 +wyciągając|wy=cią=ga=jąc|9 +wyciągnąć|wy=cią=gnąć|9 +wyglądając|wy=glą=da=jąc|9 +wyjrzeć|wyj=rzeć|9 +wypadnie|wy=pad=nie|9 +wypominać|wy=po=mi=nać|9 +wypytywać|wy=py=ty=wać|9 +wyrobek|wy=ro=bek|9 +wyrzekania|wy=rze=ka=nia|9 +wzdychy|wzdy=chy|9 +wójtową|wój=to=wą|9 +Zaczęła|Za=czę=ła|9 +zamysły|za=my=sły|9 +zaniesła|za=nie=sła|9 +zapadła|za=pa=dła|9 +zapalczywie|za=pal=czy=wie|9 +zapłacę|za=pła=cę|9 +zapłakała|za=pła=ka=ła|9 +zaradzić|za=ra=dzić|9 +zazierać|za=zie=rać|9 +zbiera|zbie=ra|9 +zdumieniem|zdu=mie=niem|9 +zdumieniu|zdu=mie=niu|9 +zdumiony|zdu=mio=ny|9 +zeszło|ze=szło|9 +zeźlił|ze=źlił|9 +zieleni|zie=le=ni|9 +ziemniakami|ziem=nia=ka=mi|9 +zmówiny|zmó=wi=ny|9 +został|zo=stał|9 +zuchwale|zu=chwa=le|9 +zwarli|zwar=li|9 +zwiesnowy|zwie=sno=wy|9 +zwykle|zwy=kle|9 +złośliwie|zło=śli=wie|9 +łajdus|łaj=dus|9 +ławach|ła=wach|9 +łaziły|ła=zi=ły|9 +śmiało|śmia=ło|9 +śmiech|śmiech|9 +śmignął|śmi=gnął|9 +śpieszył|śpie=szył|9 +śpiewając|śpie=wa=jąc|9 +światłości|świa=tło=ści|9 +święcie|świę=cie|9 +żegnając|że=gna=jąc|9 +żółtych|żół=tych|9 +Alleluja|Al=le=lu=ja|8 +Antkową|Ant=ko=wą|8 +bacznie|bacz=nie|8 +batami|ba=ta=mi|8 +biczem|bi=czem|8 +biedoto|bie=do=to|8 +biegła|bie=gła|8 +broniąc|bro=niąc|8 +brzaskach|brza=skach|8 +brzucho|brzu=cho|8 +bydlątka|by=dląt=ka|8 +błocko|błoc=ko|8 +całunki|ca=łun=ki|8 +chwiał|chwiał|8 +chyciła|chy=ci=ła|8 +chłodzie|chło=dzie|8 +ciepłem|cie=płem|8 +ciepłych|cie=płych|8 +ciągał|cią=gał|8 +ciężkich|cięż=kich|8 +czajki|czaj=ki|8 +czapki|czap=ki|8 +Czekaj|Cze=kaj|8 +czerwonym|czer=wo=nym|8 +czerwonymi|czer=wo=ny=mi|8 +czwartek|czwar=tek|8 +czyniła|czy=ni=ła|8 +dachem|da=chem|8 +dawnej|daw=nej|8 +dawniej|daw=niej|8 +deszczem|desz=czem|8 +Dobranoc|Do=bra=noc|8 +Dołach|Do=łach|8 +druhny|druh=ny|8 +dysząc|dy=sząc|8 +dzieciak|dzie=ciak|8 +dzieciątka|dzie=ciąt=ka|8 +dziedzice|dzie=dzi=ce|8 +dziedzicowi|dzie=dzi=co=wi|8 +dziedziczka|dzie=dzicz=ka|8 +dziedziców|dzie=dzi=ców|8 +dziesięciu|dzie=się=ciu|8 +dziewkę|dziew=kę|8 +dzikim|dzi=kim|8 +dziwny|dziw=ny|8 +dziwował|dzi=wo=wał|8 +dzwonił|dzwo=nił|8 +Dębicy|Dę=bi=cy|8 +dłonią|dło=nią|8 +garnków|garn=ków|8 +godzinami|go=dzi=na=mi|8 +gorączkowo|go=rącz=ko=wo|8 +gospodarce|go=spo=dar=ce|8 +gołymi|go=ły=mi|8 +goście|go=ście|8 +granatowe|gra=na=to=we|8 +gryzły|gry=zły|8 +Grzelą|Grze=lą|8 +gwarno|gwar=no|8 +gwiazd|gwiazd|8 +gwiazda|gwiaz=da|8 +głuchym|głu=chym|8 +huczała|hu=cza=ła|8 +huczało|hu=cza=ło|8 +Jeszczech|Jesz=czech|8 +Jezusowi|Je=zu=so=wi|8 +jęczała|ję=cza=ła|8 +jęczmiona|jęcz=mio=na|8 +kamieni|ka=mie=ni|8 +kamionki|ka=mion=ki|8 +kieliszki|kie=lisz=ki|8 +kluski|klu=ski|8 +kobiecie|ko=bie=cie|8 +Kobusowa|Ko=bu=so=wa|8 +Kozłów|Ko=złów|8 +kołtuny|koł=tu=ny|8 +kościach|ko=ściach|8 +krajach|kra=jach|8 +krwawym|krwa=wym|8 +krzyżów|krzy=żów|8 +książką|książ=ką|8 +Księżyc|Księ=życ|8 +Któren|Któ=ren|8 +kulasów|ku=la=sów|8 +kupami|ku=pa=mi|8 +kupili|ku=pi=li|8 +kwikiem|kwi=kiem|8 +kłopot|kło=pot|8 +kłócić|kłó=cić|8 +latała|la=ta=ła|8 +ludzkiej|ludz=kiej|8 +maciory|ma=cio=ry|8 +miarkuję|miar=ku=ję|8 +mieniło|mie=ni=ło|8 +mierziło|mier=zi=ło|8 +migotały|mi=go=ta=ły|8 +Milczeli|Mil=cze=li|8 +miodem|mio=dem|8 +miłosierdzia|mi=ło=sier=dzia|8 +miłosierdzie|mi=ło=sier=dzie|8 +mlekiem|mle=kiem|8 +mniejsze|mniej=sze|8 +Moiście|Mo=iście|8 +myśleli|my=śle=li|8 +mówiłem|mó=wi=łem|8 +mówiły|mó=wi=ły|8 +młynarzowa|mły=na=rzo=wa|8 +młynicy|mły=ni=cy|8 +nabierał|na=bie=rał|8 +nabrane|na=bra=ne|8 +naciskiem|na=ci=skiem|8 +najpierw|naj=pierw|8 +największy|naj=więk=szy|8 +namawiał|na=ma=wiał|8 +narządziła|na=rzą=dzi=ła|8 +Nastkę|Na=st=kę|8 +naszej|na=szej|8 +naszemu|na=sze=mu|8 +nasłuchiwała|na=słu=chi=wa=ła|8 +Nazajutrz|Na=za=jutrz|8 +niebem|nie=bem|8 +niecierpliwiej|nie=cier=pli=wiej|8 +niedawno|nie=daw=no|8 +niedziel|nie=dziel|8 +niepokój|nie=po=kój|8 +nieznacznie|nie=znacz=nie|8 +nijakiego|ni=ja=kie=go|8 +nijako|ni=ja=ko|8 +nikogój|ni=ko=gój|8 +obaczę|oba=czę|8 +obcych|ob=cych|8 +objęła|ob=ję=ła|8 +obleczenie|ob=le=cze=nie|8 +obronie|obro=nie|8 +obronę|obro=nę|8 +obzierał|obzie=rał|8 +ochoty|ocho=ty|8 +odburknął|od=burk=nął|8 +odparł|od=parł|8 +opadał|opa=dał|8 +opiekę|opie=kę|8 +Organista|Or=ga=ni=sta|8 +ostawił|osta=wił|8 +Ostańcie|Ostań=cie|8 +pachniało|pach=nia=ło|8 +pachnącym|pach=ną=cym|8 +palące|pa=lą=ce|8 +papiery|pa=pie=ry|8 +parafii|pa=ra=fii|8 +parobku|pa=rob=ku|8 +patrzyły|pa=trzy=ły|8 +pełnymi|peł=ny=mi|8 +piasku|pia=sku|8 +pieniądz|pie=niądz|8 +pierwsi|pierw=si|8 +piorunami|pio=ru=na=mi|8 +Pisarz|Pi=sarz|8 +pisarza|pi=sa=rza|8 +pięćdziesiąt|pięć=dzie=siąt|8 +placek|pla=cek|8 +placka|plac=ka|8 +plucha|plu=cha|8 +pobiegł|po=biegł|8 +pochwalony|po=chwa=lo=ny|8 +pochylał|po=chy=lał|8 +pociemniało|po=ciem=nia=ło|8 +poczciwe|po=czci=we|8 +poczciwości|po=czci=wo=ści|8 +poczekać|po=cze=kać|8 +poczerniałe|po=czer=nia=łe|8 +podając|po=da=jąc|8 +podniosły|pod=nio=sły|8 +podoba|po=do=ba|8 +podsunął|pod=su=nął|8 +poglądali|po=glą=da=li|8 +pogrzeb|po=grzeb|8 +pokrzyki|po=krzy=ki|8 +pokrzykując|po=krzy=ku=jąc|8 +polecę|po=le=cę|8 +Polska|Pol=ska|8 +pomogę|po=mo=gę|8 +pomsty|po=msty|8 +ponosi|po=no=si|8 +poratunku|po=ra=tun=ku|8 +poredzali|po=re=dza=li|8 +porwały|po=rwa=ły|8 +Poszli|Po=szli|8 +Poszła|Po=szła|8 +posłuchać|po=słu=chać|8 +potężnie|po=tęż=nie|8 +powiadacie|po=wia=da=cie|8 +powiecie|po=wie=cie|8 +Powiem|Po=wiem|8 +powodu|po=wo=du|8 +powrocie|po=wro=cie|8 +powstrzymując|po=wstrzy=mu=jąc|8 +powtarzał|po=wta=rzał|8 +powtarzała|po=wta=rza=ła|8 +poznać|po=znać|8 +pozwolą|po=zwo=lą|8 +połednia|po=łed=nia|8 +położył|po=ło=żył|8 +połyskiwały|po=ły=ski=wa=ły|8 +Prawie|Pra=wie|8 +procesja|pro=ce=sja|8 +prosili|pro=si=li|8 +prostował|pro=sto=wał|8 +przechwalał|prze=chwa=lał|8 +przecknął|prze=ck=nął|8 +przedzierał|prze=dzie=rał|8 +przedzwaniali|prze=dzwa=nia=li|8 +przejmujący|przej=mu=ją=cy|8 +przejęła|prze=ję=ła|8 +przekąsem|prze=ką=sem|8 +przemóc|prze=móc|8 +przenieść|prze=nieść|8 +przepadł|prze=padł|8 +przepuszczając|prze=pusz=cza=jąc|8 +przerwała|prze=rwa=ła|8 +przeszli|prze=szli|8 +przewalał|prze=wa=lał|8 +przewiady|prze=wia=dy|8 +przełazie|prze=ła=zie|8 +przygotowań|przy=go=to=wań|8 +przyjacielsko|przy=ja=ciel=sko|8 +przymrozek|przy=mro=zek|8 +przypilnuję|przy=pil=nu=ję|8 +przypominki|przy=po=min=ki|8 +przystąpił|przy=stą=pił|8 +przysunął|przy=su=nął|8 +Przyszedł|Przy=szedł|8 +przysłaniały|przy=sła=nia=ły|8 +przywarte|przy=war=te|8 +przywierając|przy=wie=ra=jąc|8 +przyźbie|przy=źbie|8 +próbował|pró=bo=wał|8 +psiekrwie|psie=kr=wie|8 +ptaków|pta=ków|8 +pustym|pu=stym|8 +puszczając|pusz=cza=jąc|8 +puszył|pu=szył|8 +płaczliwie|płacz=li=wie|8 +płomieniem|pło=mie=niem|8 +Płoszków|Płosz=ków|8 +raptem|rap=tem|8 +robiły|ro=bi=ły|8 +rozebrać|ro=ze=brać|8 +rozmawiać|roz=ma=wiać|8 +roznosił|roz=no=sił|8 +rozpacznie|roz=pacz=nie|8 +rozpierała|roz=pie=ra=ła|8 +rozpytywać|roz=py=ty=wać|8 +rozumiała|ro=zu=mia=ła|8 +ruchał|ru=chał|8 +Rzekliście|Rze=kli=ście|8 +rządzi|rzą=dzi|8 +róbcie|rób=cie|8 +różaniec|ró=ża=niec|8 +samotne|sa=mot=ne|8 +samotnie|sa=mot=nie|8 +serdecznym|ser=decz=nym|8 +siarczyste|siar=czy=ste|8 +Siedział|Sie=dział|8 +Siedzieli|Sie=dzie=li|8 +sielne|siel=ne|8 +Sielny|Siel=ny|8 +sienie|sie=nie|8 +sięgał|się=gał|8 +skrzybot|skrzy=bot|8 +skrzypiały|skrzy=pia=ły|8 +smutna|smut=na|8 +spadły|spa=dły|8 +sposobną|spo=sob=ną|8 +spostrzegli|spo=strze=gli|8 +spuścić|spu=ścić|8 +srogiej|sro=giej|8 +srożył|sro=żył|8 +stawały|sta=wa=ły|8 +stojało|sto=ja=ło|8 +Strasznie|Strasz=nie|8 +straszno|strasz=no|8 +strażniki|straż=ni=ki|8 +stróżował|stró=żo=wał|8 +szczeka|szcze=ka|8 +szeroki|sze=ro=ki|8 +szkopek|szko=pek|8 +szkoła|szko=ła|8 +szkołach|szko=łach|8 +szukając|szu=ka=jąc|8 +szukały|szu=ka=ły|8 +szumiały|szu=mia=ły|8 +szybach|szy=bach|8 +słuchaj|słu=chaj|8 +słyszeli|sły=sze=li|8 +taczki|tacz=ki|8 +tknęło|tknę=ło|8 +tocząc|to=cząc|8 +topieli|to=pie=li|8 +topolami|to=po=la=mi|8 +torbeczki|tor=becz=ki|8 +trafił|tra=fił|8 +trupem|tru=pem|8 +Trzeba|Trze=ba|8 +trzeciego|trze=cie=go|8 +trzymam|trzy=mam|8 +tupoty|tu=po=ty|8 +turbacje|tur=ba=cje|8 +turkoty|tur=ko=ty|8 +tęskliwie|tę=skli=wie|8 +tłukąc|tłu=kąc|8 +tłukło|tłu=kło|8 +tłusto|tłu=sto|8 +urwała|urwa=ła|8 +uspokoić|uspo=ko=ić|8 +uspokoiło|uspo=ko=iło|8 +uszach|uszach|8 +utrzymać|utrzy=mać|8 +wagować|wa=go=wać|8 +westchnieniem|wes=tchnie=niem|8 +wetknęła|we=tknę=ła|8 +widziałam|wi=dzia=łam|8 +wierzył|wie=rzył|8 +wietrze|wie=trze|8 +Wilczych|Wil=czych|8 +wilgotny|wil=got=ny|8 +wionął|wio=nął|8 +wiosenny|wio=sen=ny|8 +wisiała|wi=sia=ła|8 +wiszące|wi=szą=ce|8 +wlekąc|wle=kąc|8 +wnątrzu|wną=trzu|8 +wodziła|wo=dzi=ła|8 +Wojtek|Woj=tek|8 +wracali|wra=ca=li|8 +wrzeciona|wrze=cio=na|8 +wsparła|wspar=ła|8 +wspomożenie|wspo=mo=że=nie|8 +wszelaki|wsze=la=ki|8 +wszyćkim|wszyć=kim|8 +wtykał|wty=kał|8 +wybrane|wy=bra=ne|8 +wychodząc|wy=cho=dząc|8 +wyciągały|wy=cią=ga=ły|8 +wyciągnęła|wy=cią=gnę=ła|8 +wyganiać|wy=ga=niać|8 +wyjściu|wyj=ściu|8 +wyniośle|wy=nio=śle|8 +wypominał|wy=po=mi=nał|8 +wypominki|wy=po=min=ki|8 +wyprostował|wy=pro=sto=wał|8 +wystąpił|wy=stą=pił|8 +wytrząchając|wy=trzą=cha=jąc|8 +wzdychała|wzdy=cha=ła|8 +wątpiach|wąt=piach|8 +wściekły|wście=kły|8 +zabaczyć|za=ba=czyć|8 +zabawę|za=ba=wę|8 +zabiega|za=bie=ga|8 +zacinał|za=ci=nał|8 +zaczynać|za=czy=nać|8 +Zaczęli|Za=czę=li|8 +zaglądając|za=glą=da=jąc|8 +zakotłowało|za=ko=tło=wa=ło|8 +zakończył|za=koń=czył|8 +zaledwie|za=le=d=wie|8 +zamieszanie|za=mie=sza=nie|8 +zapaski|za=pa=ski|8 +zapatrzyła|za=pa=trzy=ła|8 +zapierało|za=pie=ra=ło|8 +zapisał|za=pi=sał|8 +zapowietrzona|za=po=wie=trzo=na|8 +zatracenie|za=tra=ce=nie|8 +zawiewał|za=wie=wał|8 +zazierając|za=zie=ra=jąc|8 +zazwyczaj|za=zwy=czaj|8 +zgrozą|zgro=zą|8 +zjedli|zje=dli|8 +zmyślnie|zmyśl=nie|8 +znalazł|zna=lazł|8 +zresztą|zresz=tą|8 +zrobią|zro=bią|8 +zrywać|zry=wać|8 +zwierz|zwierz|8 +zwiesną|zwie=sną|8 +zziajany|zzia=ja=ny|8 +łagodnie|ła=god=nie|8 +Ścierwy|Ścier=wy|8 +ściągać|ścią=gać|8 +śmiały|śmia=ły|8 +środek|śro=dek|8 +świadków|świad=ków|8 +Święty|Świę=ty|8 +żadnego|żad=ne=go|8 +żałosny|ża=ło=sny|8 +żałoście|ża=ło=ście|8 +żołnierka|żoł=nier=ka|8 +żółciły|żół=ci=ły|8 +akurat|aku=rat|7 +alkierz|al=kierz|7 +Antkowa|Ant=ko=wa|7 +baczyła|ba=czy=ła|7 +bawiły|ba=wi=ły|7 +bezmyślnie|bez=myśl=nie|7 +białawe|bia=ła=we|7 +białej|bia=łej|7 +biedoty|bie=do=ty|7 +bielały|bie=la=ły|7 +bladym|bla=dym|7 +bogacz|bo=gacz|7 +bolesny|bo=le=sny|7 +borgować|bor=go=wać|7 +brakuje|bra=ku=je|7 +bruzdami|bruz=da=mi|7 +brzasków|brza=sków|7 +brzegi|brze=gi|7 +brzozy|brzo=zy|7 +budynki|bu=dyn=ki|7 +budził|bu=dził|7 +błogosławił|bło=go=sła=wił|7 +błotem|bło=tem|7 +błyskawice|bły=ska=wi=ce|7 +chceta|chce=ta|7 +chleby|chle=by|7 +chlewów|chle=wów|7 +chlustały|chlu=sta=ły|7 +chmurny|chmur=ny|7 +chodźcie|chodź=cie|7 +chojary|cho=ja=ry|7 +choroba|cho=ro=ba|7 +choroby|cho=ro=by|7 +Choćby|Choć=by|7 +chudziaku|chu=dzia=ku|7 +chwycić|chwy=cić|7 +ciasto|cia=sto|7 +cichej|ci=chej|7 +cichoj|ci=choj|7 +cichością|ci=cho=ścią|7 +ciekawe|cie=ka=we|7 +ciemne|ciem=ne|7 +ciepał|cie=pał|7 +ciesząc|cie=sząc|7 +ciągiem|cią=giem|7 +cofnęła|cof=nę=ła|7 +cudności|cud=no=ści|7 +czerwonej|czer=wo=nej|7 +czterdzieści|czter=dzie=ści|7 +Cztery|Czte=ry|7 +czubami|czu=ba=mi|7 +czyniło|czy=ni=ło|7 +czystej|czy=stej|7 +człowieczą|czło=wie=czą|7 +daleką|da=le=ką|7 +dalszych|dal=szych|7 +deszczów|desz=czów|7 +dniach|dniach|7 +dochodziły|do=cho=dzi=ły|7 +dostaniesz|do=sta=niesz|7 +dostała|do=sta=ła|7 +doszedł|do=szedł|7 +dowiedzieć|do=wie=dzieć|7 +dołkach|doł=kach|7 +dołożę|do=ło=żę|7 +drabiny|dra=bi=ny|7 +drzewin|drze=win|7 +dufności|duf=no=ści|7 +duszach|du=szach|7 +dziada|dzia=da|7 +dzieckiem|dziec=kiem|7 +dzikie|dzi=kie|7 +dzwonki|dzwon=ki|7 +długości|dłu=go=ści|7 +flachę|fla=chę|7 +flaszkę|flasz=kę|7 +gadali|ga=da=li|7 +gadała|ga=da=ła|7 +garściach|gar=ściach|7 +gmerały|gme=ra=ły|7 +gniazd|gniazd|7 +gniewy|gnie=wy|7 +gorzkie|gorz=kie|7 +gorączce|go=rącz=ce|7 +gospodarzem|go=spo=da=rzem|7 +gospodarzom|go=spo=da=rzom|7 +Gospodarzu|Go=spo=da=rzu|7 +gotowane|go=to=wa=ne|7 +Gołębianka|Go=łę=bian=ka|7 +gołębie|go=łę=bie|7 +grubym|gru=bym|7 +gwarzyć|gwa=rzyć|7 +głębiej|głę=biej|7 +głęboki|głę=bo=ki|7 +głębokiej|głę=bo=kiej|7 +innymi|in=ny=mi|7 +intencję|in=ten=cję|7 +jadłem|ja=dłem|7 +jakbym|jak=bym|7 +jakichś|ja=kichś|7 +Jambrożym|Jam=bro=żym|7 +jamory|ja=mo=ry|7 +jarzącymi|ja=rzą=cy=mi|7 +Jasiem|Ja=siem|7 +jasnym|ja=snym|7 +jechał|je=chał|7 +jenszy|jen=szy|7 +Jezusowe|Je=zu=so=we|7 +kajdany|kaj=da=ny|7 +katolickim|ka=to=lic=kim|7 +Każden|Każ=den|7 +kiecki|kiec=ki|7 +kiejście|kiej=ście|7 +klaczy|kla=czy|7 +kogutka|ko=gut=ka|7 +kopania|ko=pa=nia|7 +kopytami|ko=py=ta=mi|7 +korali|ko=ra=li|7 +kołowały|ko=ło=wa=ły|7 +kołysał|ko=ły=sał|7 +kołysały|ko=ły=sa=ły|7 +kończyna|koń=czy=na|7 +krajów|kra=jów|7 +krzami|krza=mi|7 +krzyknąć|krzyk=nąć|7 +krótką|krót=ką|7 +książkę|książ=kę|7 +księżego|księ=że=go|7 +Któregoś|Któ=re=goś|7 +kurzawą|ku=rza=wą|7 +kurzył|ku=rzył|7 +kwiatach|kwia=tach|7 +kądzieli|ką=dzie=li|7 +kłonicą|kło=ni=cą|7 +kłosów|kło=sów|7 +lecieli|le=cie=li|7 +lekciej|lek=ciej|7 +litanię|li=ta=nię|7 +lśniące|lśnią=ce|7 +marnego|mar=ne=go|7 +martwy|mar=twy|7 +matczyne|mat=czy=ne|7 +matula|ma=tu=la|7 +miarkujecie|miar=ku=je=cie|7 +miedzą|mie=dzą|7 +Miemce|Miem=ce|7 +minucie|mi=nu=cie|7 +minutę|mi=nu=tę|7 +miotał|mio=tał|7 +mocarz|mo=carz|7 +molestować|mo=le=sto=wać|7 +motyle|mo=ty=le|7 +możesz|mo=żesz|7 +mroczało|mro=cza=ło|7 +myślałem|my=śla=łem|7 +mógłby|mógł=by|7 +młodego|mło=de=go|7 +młodszy|młod=szy|7 +młynarzem|mły=na=rzem|7 +młynarzowego|mły=na=rzo=we=go|7 +młynarzów|mły=na=rzów|7 +nabrzmiałe|na=brzmia=łe|7 +nadzieją|na=dzie=ją|7 +Najpierw|Naj=pierw|7 +najpierwsi|naj=pierw=si|7 +nalazł|na=lazł|7 +namawiać|na=ma=wiać|7 +nasienia|na=sie=nia|7 +naszło|na=szło|7 +natomiast|na=to=miast|7 +niebieskie|nie=bie=skie|7 +nieboszczyka|nie=bosz=czy=ka|7 +niechaj|nie=chaj|7 +Niechby|Niech=by|7 +niektórzy|nie=któ=rzy|7 +niemałą|nie=ma=łą|7 +nienawiścią|nie=na=wi=ścią|7 +niesło|nie=sło|7 +Niewiela|Nie=wie=la|7 +nosili|no=si=li|7 +nowiny|no=wi=ny|7 +obcasami|ob=ca=sa=mi|7 +obertelek|obe=r=te=lek|7 +oberwać|obe=rwać|7 +obficie|ob=fi=cie|7 +obiecywał|obie=cy=wał|7 +obsiadły|ob=sia=dły|7 +ochotę|ocho=tę|7 +odchodzi|od=cho=dzi|7 +odchodził|od=cho=dził|7 +odchodząc|od=cho=dząc|7 +odetchnąć|ode=tchnąć|7 +odpowiedziała|od=po=wie=dzia=ła|7 +odzipnąć|od=zip=nąć|7 +oglądali|oglą=da=li|7 +ogniska|ogni=ska|7 +ogrodu|ogro=du|7 +ogrody|ogro=dy|7 +ogromnych|ogrom=nych|7 +opuszczenie|opusz=cze=nie|7 +opłatek|opła=tek|7 +opłotkami|opłot=ka=mi|7 +ostatnia|ostat=nia|7 +ostawał|osta=wał|7 +ostawiając|osta=wia=jąc|7 +ostawiła|osta=wi=ła|7 +ozorami|ozo=ra=mi|7 +ołtarze|oł=ta=rze|7 +pachnie|pach=nie|7 +padnie|pad=nie|7 +panowie|pa=no=wie|7 +paradą|pa=ra=dą|7 +Parobek|Pa=ro=bek|7 +patrzę|pa=trzę|7 +pazurów|pa=zu=rów|7 +Pańskiej|Pań=skiej|7 +pedzieć|pe=dzieć|7 +pełnej|peł=nej|7 +pełnym|peł=nym|7 +piesków|pie=sków|7 +pieśnią|pie=śnią|7 +Pijcie|Pij=cie|7 +pilnowała|pil=no=wa=ła|7 +pioruny|pio=ru=ny|7 +piosneczki|pio=snecz=ki|7 +piskiem|pi=skiem|7 +piąciu|pią=ciu|7 +plebanią|ple=ba=nią|7 +pluchę|plu=chę|7 +pobożny|po=boż=ny|7 +pochylały|po=chy=la=ły|7 +poczekaj|po=cze=kaj|7 +poczerwieniała|po=czer=wie=nia=ła|7 +pocznę|po=cznę|7 +podawać|po=da=wać|7 +podjął|pod=jął|7 +podnieśli|pod=nie=śli|7 +podobnie|po=dob=nie|7 +podobny|po=dob=ny|7 +podziw|po=dziw|7 +pogadując|po=ga=du=jąc|7 +pognała|po=gna=ła|7 +pojmować|poj=mo=wać|7 +pokazać|po=ka=zać|7 +pokojach|po=ko=jach|7 +Pokrótce|Po=krót=ce|7 +pokręciło|po=krę=ci=ło|7 +Poleciała|Po=le=cia=ła|7 +pomaga|po=ma=ga|7 +pomagała|po=ma=ga=ła|7 +pomstą|po=mstą|7 +pomyślenie|po=my=śle=nie|7 +pomógł|po=mógł|7 +pomówić|po=mó=wić|7 +poniesie|po=nie=sie|7 +poniesą|po=nie=są|7 +popijał|po=pi=jał|7 +poprawił|po=pra=wił|7 +popuszczę|po=pusz=czę|7 +poredzać|po=re=dzać|7 +poredzą|po=re=dzą|7 +poruszać|po=ru=szać|7 +porządków|po=rząd=ków|7 +poręby|po=rę=by|7 +porębę|po=rę=bę|7 +pospólnie|po=spól=nie|7 +postać|po=stać|7 +postronek|po=stro=nek|7 +posłuch|po=słuch|7 +posłyszeć|po=sły=szeć|7 +potaczał|po=ta=czał|7 +potrafi|po=tra=fi|7 +potrzeby|po=trze=by|7 +powiadać|po=wia=dać|7 +powiatu|po=wia=tu|7 +powiedam|po=wie=dam|7 +Powiedział|Po=wie=dział|7 +powraca|po=wra=ca|7 +powrócą|po=wró=cą|7 +powywierane|po=wy=wie=ra=ne|7 +pozostał|po=zo=stał|7 +pozwolić|po=zwo=lić|7 +połowa|po=ło=wa|7 +pośmiewisko|po=śmie=wi=sko|7 +pracował|pra=co=wał|7 +prosiaki|pro=sia=ki|7 +proszę|pro=szę|7 +prowadził|pro=wa=dził|7 +przechodziła|prze=cho=dzi=ła|7 +przedać|przedać|7 +przejmujące|przej=mu=ją=ce|7 +przejęły|prze=ję=ły|7 +przekpiwał|prze=kpi=wał|7 +przekładać|prze=kła=dać|7 +przenikał|prze=ni=kał|7 +przepijając|prze=pi=ja=jąc|7 +przestanku|prze=stan=ku|7 +Przeszedł|Prze=szedł|7 +przewiny|prze=wi=ny|7 +przewodził|prze=wo=dził|7 +przełaz|prze=łaz|7 +prześmiech|prze=śmiech|7 +przychodzą|przy=cho=dzą|7 +przychodź|przy=chodź|7 +przyczajone|przy=cza=jo=ne|7 +przydrożne|przy=droż=ne|7 +przyginały|przy=gi=na=ły|7 +przyglądał|przy=glą=dał|7 +Przyjdę|Przyj=dę|7 +przyjedzie|przy=je=dzie|7 +przynieść|przy=nieść|7 +przyobiecał|przy=obie=cał|7 +przypominając|przy=po=mi=na=jąc|7 +przysiedli|przy=sie=dli|7 +przysiąść|przy=siąść|7 +przyszedłem|przy=sze=dłem|7 +przywieść|przy=wieść|7 +przywiózł|przy=wiózł|7 +przyzba|przy=zba|7 +przyśpiewując|przy=śpie=wu=jąc|7 +próbowała|pró=bo=wa=ła|7 +Psiachmać|Psiach=mać|7 +ptactwa|ptac=twa|7 +ptasie|pta=sie|7 +płachta|płach=ta|7 +pławiły|pła=wi=ły|7 +płomienie|pło=mie=nie|7 +płomień|pło=mień|7 +radosną|ra=do=sną|7 +ratunek|ra=tu=nek|7 +rejenta|re=jen=ta|7 +robicie|ro=bi=cie|7 +robili|ro=bi=li|7 +robotny|ro=bot=ny|7 +rosach|ro=sach|7 +rozbierała|roz=bie=ra=ła|7 +rozbłysły|roz=bły=sły|7 +rozebrało|ro=ze=bra=ło|7 +rozgorzały|roz=go=rza=ły|7 +rozniesły|roz=nie=sły|7 +rozpalonymi|roz=pa=lo=ny=mi|7 +rozpiera|roz=pie=ra|7 +rozpoznał|roz=po=znał|7 +rozrosłe|roz=ro=słe|7 +rozumiał|ro=zu=miał|7 +rozumieć|ro=zu=mieć|7 +rozważając|roz=wa=ża=jąc|7 +rozłożył|roz=ło=żył|7 +ruszyły|ru=szy=ły|7 +Rzekłem|Rze=kłem|7 +Rzepek|Rze=pek|7 +rzęsiście|rzę=si=ście|7 +różańcem|ró=żań=cem|7 +samymi|sa=my=mi|7 +serdecznej|ser=decz=nej|7 +serdeczny|ser=decz=ny|7 +siadając|sia=da=jąc|7 +siekierą|sie=kie=rą|7 +Sikorzyna|Si=ko=rzy=na|7 +skraju|skra=ju|7 +skrzat|skrzat|7 +skrzyczał|skrzy=czał|7 +skrzypiał|skrzy=piał|7 +skrzypice|skrzy=pi=ce|7 +smętarzu|smę=ta=rzu|7 +snopki|snop=ki|7 +Sochowa|So=cho=wa|7 +sołtysa|soł=ty=sa|7 +spieki|spie=ki|7 +spodział|spo=dział|7 +spodziewając|spo=dzie=wa=jąc|7 +spojrzeniem|spoj=rze=niem|7 +spojrzy|spoj=rzy|7 +spostrzegając|spo=strze=ga=jąc|7 +spozierał|spo=zie=rał|7 +srebrne|srebr=ne|7 +srogich|sro=gich|7 +Stacha|Sta=cha|7 +stadko|stad=ko|7 +stawali|sta=wa=li|7 +stojali|sto=ja=li|7 +stojącą|sto=ją=cą|7 +straciła|stra=ci=ła|7 +strawy|stra=wy|7 +strażnikami|straż=ni=ka=mi|7 +strażnikom|straż=ni=kom|7 +struchlałe|stru=chla=łe|7 +strzegli|strze=gli|7 +strzelbę|strzel=bę|7 +stworzenia|stwo=rze=nia|7 +swobodnie|swo=bod=nie|7 +szarym|sza=rym|7 +szczekania|szcze=ka=nia|7 +szukała|szu=ka=ła|7 +słodkie|słod=kie|7 +Słyszeliście|Sły=sze=li=ście|7 +słyszysz|sły=szysz|7 +tabaką|ta=ba=ką|7 +tamtego|tam=te=go|7 +tatulu|ta=tu=lu|7 +toczył|to=czył|7 +trepami|tre=pa=mi|7 +trumny|trum=ny|7 +trwała|trwa=ła|7 +trzaskając|trza=ska=jąc|7 +trzasnęła|trza=snę=ła|7 +trzepały|trze=pa=ły|7 +tumany|tu=ma=ny|7 +tysiąc|ty=siąc|7 +ubrany|ubra=ny|7 +uciekały|ucie=ka=ły|7 +ucieszne|uciesz=ne|7 +uderzyły|ude=rzy=ły|7 +udręki|udrę=ki|7 +ujadały|uja=da=ły|7 +ujrzawszy|uj=rzaw=szy|7 +ująwszy|ująw=szy|7 +umiała|umia=ła|7 +umyślnie|umyśl=nie|7 +unosząc|uno=sząc|7 +upodoby|upodo=by|7 +urzędnik|urzęd=nik|7 +uspokoił|uspo=ko=ił|7 +usypany|usy=pa=ny|7 +uszami|usza=mi|7 +usłyszał|usły=szał|7 +uwierzy|uwie=rzy|7 +uwierzył|uwie=rzył|7 +uwięzi|uwię=zi|7 +uśmiechnął|uśmiech=nął|7 +warczały|war=cza=ły|7 +warkocze|war=ko=cze|7 +warowała|wa=ro=wa=ła|7 +waszych|wa=szych|7 +Weronki|We=ron=ki|7 +westchnienia|wes=tchnie=nia|7 +wiatrak|wia=trak|7 +wiecznie|wiecz=nie|7 +wiedła|wie=dła|7 +wiejskiej|wiej=skiej|7 +wierciło|wier=ci=ło|7 +wierzchu|wierz=chu|7 +wiośniane|wio=śnia=ne|7 +wiośnie|wio=śnie|7 +witając|wi=ta=jąc|7 +wiązać|wią=zać|7 +wojnie|woj=nie|7 +wolniuśko|wol=niuś=ko|7 +wozami|wo=za=mi|7 +wołały|wo=ła=ły|7 +wracając|wra=ca=jąc|7 +Wrzask|Wrzask|7 +wrzawą|wrza=wą|7 +wrzała|wrza=ła|7 +wrzeszczały|wrzesz=cza=ły|7 +wrócili|wró=ci=li|7 +wrótniami|wrót=nia=mi|7 +wschodem|wscho=dem|7 +wskazując|wska=zu=jąc|7 +współczuciem|współ=czu=ciem|7 +wstawać|wsta=wać|7 +wstąpił|wstą=pił|7 +wstążkę|wstąż=kę|7 +wybiera|wy=bie=ra|7 +wychylały|wy=chy=la=ły|7 +wydziera|wy=dzie=ra|7 +wygrażał|wy=gra=żał|7 +wyjrzał|wyj=rzał|7 +wykrzykiwać|wy=krzy=ki=wać|7 +wypomina|wy=po=mi=na|7 +wyrozumie|wy=ro=zu=mie|7 +wyrywać|wy=ry=wać|7 +wyrzekać|wy=rze=kać|7 +wystraszony|wy=stra=szo=ny|7 +wywalili|wy=wa=li=li|7 +wyzierając|wy=zie=ra=jąc|7 +wzburzony|wzbu=rzo=ny|7 +wzdychając|wzdy=cha=jąc|7 +wzdychów|wzdy=chów|7 +wznosił|wzno=sił|7 +wzruszenia|wzru=sze=nia|7 +wątroba|wą=tro=ba|7 +wściekłe|wście=kłe|7 +wściekłością|wście=kło=ścią|7 +zabiegał|za=bie=gał|7 +zabierać|za=bie=rać|7 +zachciało|za=chcia=ło|7 +zaczynał|za=czy=nał|7 +zaczynały|za=czy=na=ły|7 +zaglądać|za=glą=dać|7 +zaglądały|za=glą=da=ły|7 +zagonie|za=go=nie|7 +Zajrzała|Zaj=rza=ła|7 +zajęta|za=ję=ta|7 +zakołysał|za=ko=ły=sał|7 +zakrystii|za=kry=stii|7 +zakrzyczały|za=krzy=cza=ły|7 +zamedytował|za=me=dy=to=wał|7 +zamilkł|za=milkł|7 +zamknięty|za=mknię=ty|7 +zapamiętałością|za=pa=mię=ta=ło=ścią|7 +zapisu|za=pi=su|7 +zapodział|za=po=dział|7 +zapomnieli|za=po=mnie=li|7 +zapraszać|za=pra=szać|7 +zapłacić|za=pła=cić|7 +zarzewie|za=rze=wie|7 +zarzewiem|za=rze=wiem|7 +zasiadł|za=siadł|7 +zasnute|za=snu=te|7 +zasnęła|za=snę=ła|7 +zawodziły|za=wo=dzi=ły|7 +zaśpiewał|za=śpie=wał|7 +zbożem|zbo=żem|7 +zdechła|zde=chła|7 +zdrowy|zdro=wy|7 +zdumienia|zdu=mie=nia|7 +zdążyć|zdą=żyć|7 +zdążyła|zdą=ży=ła|7 +zerwały|ze=rwa=ły|7 +zgodził|zgo=dził|7 +ziarnem|ziar=nem|7 +zipiąc|zi=piąc|7 +zmartwiała|zmar=twia=ła|7 +zmarło|zmar=ło|7 +zmówić|zmó=wić|7 +znaczy|zna=czy|7 +znikąd|zni=kąd|7 +zrywała|zry=wa=ła|7 +zwyczajem|zwy=cza=jem|7 +złocie|zło=cie|7 +złodzieja|zło=dzie=ja|7 +ławkach|ław=kach|7 +łaziła|ła=zi=ła|7 +łąkach|łą=kach|7 +łęgach|łę=gach|7 +ścianach|ścia=nach|7 +Ścierwa|Ścier=wa|7 +ścisnął|ści=snął|7 +ścisnęło|ści=snę=ło|7 +ściółkę|ściół=kę|7 +śledzia|śle=dzia|7 +śmigały|śmi=ga=ły|7 +świecami|świe=ca=mi|7 +świeżo|świe=żo|7 +święci|świę=ci|7 +żałowała|ża=ło=wa=ła|7 +żałością|ża=ło=ścią|7 +życiem|ży=ciem|7 +Żydzie|Ży=dzie|7 +żywota|ży=wo=ta|7 +alkierzu|al=kie=rzu|6 +Antkowie|Ant=ko=wie|6 +Antoni|An=to=ni|6 +baczysz|ba=czysz|6 +barana|ba=ra=na|6 +baraszkował|ba=rasz=ko=wał|6 +baraszkowały|ba=rasz=ko=wa=ły|6 +bekiem|be=kiem|6 +bełkotem|beł=ko=tem|6 +biegali|bie=ga=li|6 +bierzcie|bierz=cie|6 +bieżyj|bie=żyj|6 +bijące|bi=ją=ce|6 +bijącym|bi=ją=cym|6 +bochen|bo=chen|6 +bogactwa|bo=gac=twa|6 +bogacze|bo=ga=cze|6 +bolesne|bo=le=sne|6 +borowego|bo=ro=we=go|6 +Borynowego|Bo=ry=no=we=go|6 +Borynowy|Bo=ry=no=wy|6 +Borynowym|Bo=ry=no=wym|6 +brzaskami|brza=ska=mi|6 +brzegu|brze=gu|6 +brzęki|brzę=ki|6 +buchało|bu=cha=ło|6 +burych|bu=rych|6 +bywały|by=wa=ły|6 +byłaby|by=ła=by|6 +Byście|By=ście|6 +Całkiem|Cał=kiem|6 +całowali|ca=ło=wa=li|6 +całowała|ca=ło=wa=ła|6 +cepami|ce=pa=mi|6 +Chciał|Chciał|6 +chlebem|chle=bem|6 +chlipanie|chli=pa=nie|6 +chmurzyskami|chmu=rzy=ska=mi|6 +chmurą|chmu=rą|6 +chodźmy|chodź=my|6 +Chodźta|Chodź=ta|6 +chorej|cho=rej|6 +choćbyś|choć=byś|6 +Chrystus|Chry=stus|6 +chrzęstem|chrzę=stem|6 +chwalił|chwa=lił|6 +chwasty|chwa=sty|6 +chwytając|chwy=ta=jąc|6 +chłodu|chło=du|6 +chłopskich|chłop=skich|6 +cielak|cie=lak|6 +ciemny|ciem=ny|6 +cieple|cie=ple|6 +ciepnął|ciep=nął|6 +ciepłym|cie=płym|6 +cieszyć|cie=szyć|6 +cieszył|cie=szył|6 +ciołak|cio=łak|6 +ciołka|cioł=ka|6 +ciurkiem|ciur=kiem|6 +ciągnąć|cią=gnąć|6 +ciągłe|cią=głe|6 +ciężkimi|cięż=ki=mi|6 +ciżbie|ciż=bie|6 +coście|co=ście|6 +czasownika|cza=sow=ni=ka|6 +czegóż|cze=góż|6 +czołem|czo=łem|6 +czujące|czu=ją=ce|6 +czynnością|czyn=no=ścią|6 +człowiecze|czło=wie=cze|6 +córuchno|có=ruch=no|6 +dachach|da=chach|6 +dalekim|da=le=kim|6 +dalekościach|da=le=ko=ściach|6 +dawali|da=wa=li|6 +deliberował|de=li=be=ro=wał|6 +diabli|dia=bli|6 +dlatego|dla=te=go|6 +dobremu|do=bre=mu|6 +dobytek|do=by=tek|6 +dochtory|do=chto=ry|6 +doczekać|do=cze=kać|6 +dojrzały|doj=rza=ły|6 +dokładnie|do=kład=nie|6 +dopraszam|do=pra=szam|6 +dostanie|do=sta=nie|6 +dosłyszawszy|do=sły=szaw=szy|6 +dowodził|do=wo=dził|6 +dołoży|do=ło=ży|6 +drapać|dra=pać|6 +dreszcz|dreszcz|6 +drewek|dre=wek|6 +drgały|drga=ły|6 +drzewina|drze=wi=na|6 +dróżce|dróż=ce|6 +drżące|drżą=ce|6 +dwojaki|dwo=ja=ki|6 +dygotała|dy=go=ta=ła|6 +dygotało|dy=go=ta=ło|6 +dygotem|dy=go=tem|6 +dymach|dy=mach|6 +działach|dzia=łach|6 +działy|dzia=ły|6 +dzieciach|dzie=ciach|6 +dziedzicowy|dzie=dzi=co=wy|6 +dziobem|dzio=bem|6 +dzioby|dzio=by|6 +dziwna|dziw=na|6 +Dziwowali|Dzi=wo=wa=li|6 +dzwoniła|dzwo=ni=ła|6 +długachne|dłu=gach=ne|6 +dłuższą|dłuż=szą|6 +dłużył|dłu=żył|6 +dźwigając|dźwi=ga=jąc|6 +farbami|far=ba=mi|6 +gadanie|ga=da=nie|6 +galopem|ga=lo=pem|6 +garnek|gar=nek|6 +gałęziach|ga=łę=ziach|6 +gałęzią|ga=łę=zią|6 +giezło|gie=zło|6 +gorszego|gor=sze=go|6 +gorzko|gorz=ko|6 +gorącymi|go=rą=cy=mi|6 +gospodarki|go=spo=dar=ki|6 +gospodarzami|go=spo=da=rza=mi|6 +gospodarzowi|go=spo=da=rzo=wi|6 +gospodyń|go=spo=dyń|6 +gołębi|go=łę=bi|6 +gościniec|go=ści=niec|6 +grobli|gro=bli|6 +grochem|gro=chem|6 +gromadę|gro=ma=dę|6 +gromnicę|grom=ni=cę|6 +groszem|gro=szem|6 +groziła|gro=zi=ła|6 +groźby|groź=by|6 +groźna|groź=na|6 +grubachne|gru=bach=ne|6 +grusze|gru=sze|6 +grzeszne|grzesz=ne|6 +gwarząc|gwa=rząc|6 +gwiezdne|gwiezd=ne|6 +gąsków|gą=sków|6 +gęsiego|gę=sie=go|6 +gęstniał|gęst=niał|6 +gęstwa|gę=stwa|6 +głaskać|gła=skać|6 +głaskał|gła=skał|6 +głupiego|głu=pie=go|6 +Hameryki|Ha=me=ry=ki|6 +hamował|ha=mo=wał|6 +hardość|har=dość|6 +historię|hi=sto=rię|6 +idziecie|idzie=cie|6 +indory|in=do=ry|6 +izdebki|iz=deb=ki|6 +jabłek|ja=błek|6 +Jagusina|Ja=gu=si=na|6 +Jankla|Jan=kla|6 +jasnością|ja=sno=ścią|6 +jazgoty|ja=zgo=ty|6 +Jaśkiem|Jaś=kiem|6 +jaśniały|ja=śnia=ły|6 +jechali|je=cha=li|6 +jedzie|je=dzie|6 +Jeszczek|Jesz=czek|6 +Jezusowego|Je=zu=so=we=go|6 +jeździli|jeź=dzi=li|6 +jeżeli|je=że=li|6 +Jędrzycha|Ję=drzy=cha|6 +jękliwe|ję=kli=we|6 +jękliwym|ję=kli=wym|6 +języka|ję=zy=ka|6 +kancelarią|kan=ce=la=rią|6 +kapały|ka=pa=ły|6 +kapocie|ka=po=cie|6 +karczmach|karcz=mach|6 +kawale|ka=wa=le|6 +kawałki|ka=wał=ki|6 +Kiedyż|Kie=dyż|6 +kieliszka|kie=lisz=ka|6 +kieszeni|kie=sze=ni|6 +kiełbasa|kieł=ba=sa|6 +kiełbasę|kieł=ba=sę|6 +kijaszek|ki=ja=szek|6 +klęczała|klę=cza=ła|6 +kobieto|ko=bie=to|6 +kobyłę|ko=by=łę|6 +kochana|ko=cha=na|6 +kokoty|ko=ko=ty|6 +komisarza|ko=mi=sa=rza|6 +kompanią|kom=pa=nią|6 +koniecznie|ko=niecz=nie|6 +konstrukcja|kon=struk=cja|6 +Kozłowej|Ko=zło=wej|6 +kołujący|ko=łu=ją=cy|6 +końcówką|koń=ców=ką|6 +końskie|koń=skie|6 +kościołowi|ko=ścio=ło=wi|6 +Kościół|Ko=ściół|6 +krewniaki|krew=nia=ki|6 +krewniaków|krew=nia=ków|6 +krzepciej|krzep=ciej|6 +krzyczą|krzy=czą|6 +krzyżu|krzy=żu|6 +królach|kró=lach|6 +kręcić|krę=cić|6 +kręciły|krę=ci=ły|6 +krętanina|krę=ta=ni=na|6 +kumami|ku=ma=mi|6 +kuntentność|kun=tent=ność|6 +kuropatki|ku=ro=pat=ki|6 +Kużden|Kuż=den|6 +kwiliły|kwi=li=ły|6 +lament|la=ment|6 +lasach|la=sach|6 +leciało|le=cia=ło|6 +leniwie|le=ni=wie|6 +lepszego|lep=sze=go|6 +lepszy|lep=szy|6 +lewentarz|le=wen=tarz|6 +leżących|le=żą=cych|6 +Lipcami|Lip=ca=mi|6 +Maciejowi|Ma=cie=jo=wi|6 +mamrotać|mam=ro=tać|6 +markotnie|mar=kot=nie|6 +matczynych|mat=czy=nych|6 +miarkował|miar=ko=wał|6 +miarkujcie|miar=kuj=cie|6 +migotał|mi=go=tał|6 +milcząca|mil=czą=ca|6 +mocniejszy|moc=niej=szy|6 +modlić|mo=dlić|6 +modliła|mo=dli=ła|6 +mrowiły|mro=wi=ły|6 +mruczała|mru=cza=ła|6 +mrówki|mrów=ki|6 +musiałem|mu=sia=łem|6 +musisz|mu=sisz|6 +mówiłam|mó=wi=łam|6 +młynarczyk|mły=nar=czyk|6 +nabożeństwa|na=bo=żeń=stwa|6 +nabrzmiały|na=brzmia=ły|6 +nadchodził|nad=cho=dził|6 +naokół|na=okół|6 +napisane|na=pi=sa=ne|6 +naprawdę|na=praw=dę|6 +narodził|na=ro=dził|6 +Nastce|Na=st=ce|6 +Nastusię|Na=stu=się|6 +naszczekiwał|na=szcze=ki=wał|6 +naszych|na=szych|6 +natknęła|na=tknę=ła|6 +niczegój|ni=cze=gój|6 +niczemu|ni=cze=mu|6 +niechże|niech=że|6 +niedzieli|nie=dzie=li|6 +niektóry|nie=któ=ry|6 +nieletko|nie=let=ko|6 +niemocy|nie=mo=cy|6 +niepowstrzymanie|nie=po=wstrzy=ma=nie|6 +nieprzyjacioły|nie=przy=ja=cio=ły|6 +nierad|nie=rad|6 +nierada|nie=ra=da|6 +niesie|nie=sie|6 +niesprawiedliwość|nie=spra=wie=dli=wość|6 +niesąc|nie=sąc|6 +nieustępliwą|nie=ustę=pli=wą|6 +niezadługo|nie=za=dłu=go|6 +Niezgorzej|Nie=zgo=rzej|6 +niosąc|nio=sąc|6 +niosły|nio=sły|6 +obaczył|oba=czył|6 +obejmował|obej=mo=wał|6 +oberwał|obe=rwał|6 +objaśniał|ob=ja=śniał|6 +objaśniała|ob=ja=śnia=ła|6 +obleczenia|ob=le=cze=nia|6 +obleczeniu|ob=le=cze=niu|6 +obmierzły|ob=mier=z=ły|6 +obrazem|ob=ra=zem|6 +obrony|obro=ny|6 +obzierać|obzie=rać|6 +ochfiarowała|ochfia=ro=wa=ła|6 +ociężale|ocię=ża=le|6 +oczekując|ocze=ku=jąc|6 +oddała|od=da=ła|6 +oddech|od=dech|6 +oddechy|od=de=chy|6 +odebrał|ode=brał|6 +odeszli|ode=szli|6 +odmówienie|od=mó=wie=nie|6 +odpowie|od=po=wie|6 +odsunął|od=su=nął|6 +odwracał|od=wra=cał|6 +odwracała|od=wra=ca=ła|6 +oganiając|oga=nia=jąc|6 +ogiery|ogie=ry|6 +ogniste|ogni=ste|6 +ogrodzie|ogro=dzie|6 +ojcowego|oj=co=we=go|6 +okrutnie|okrut=nie|6 +okręcił|okrę=cił|6 +omacku|omac=ku|6 +opadła|opa=dła|6 +opierał|opie=rał|6 +opowiadając|opo=wia=da=jąc|6 +opowiedzieć|opo=wie=dzieć|6 +oprzytomniał|oprzy=tom=niał|6 +organistą|or=ga=ni=stą|6 +Organiścina|Or=ga=ni=ści=na|6 +ostrzej|ostrzej|6 +otwarcie|otwar=cie|6 +otwierać|otwie=rać|6 +otwierało|otwie=ra=ło|6 +otwierały|otwie=ra=ły|6 +ozorach|ozo=rach|6 +ozwarł|ozwarł|6 +oścież|oścież|6 +ożenić|oże=nić|6 +paciorków|pa=cior=ków|6 +padała|pa=da=ła|6 +palcem|pal=cem|6 +pamięta|pa=mię=ta|6 +Panajezusowe|Pa=na=je=zu=so=we|6 +panuje|pa=nu=je|6 +papierosy|pa=pie=ro=sy|6 +papieru|pa=pie=ru|6 +pasieki|pa=sie=ki|6 +paśniki|pa=śni=ki|6 +pedział|pe=dział|6 +piekle|pie=kle|6 +piesneczki|pie=snecz=ki|6 +Pietrkiem|Pietr=kiem|6 +Pietrusia|Pie=tru=sia|6 +pieściwie|pie=ści=wie|6 +pieśni|pie=śni|6 +pilnuje|pil=nu=je|6 +pobili|po=bi=li|6 +pochowku|po=chow=ku|6 +pociecha|po=cie=cha|6 +pociechę|po=cie=chę|6 +pociągając|po=cią=ga=jąc|6 +pociągnęła|po=cią=gnę=ła|6 +poczęstunek|po=czę=stu=nek|6 +podjeść|pod=jeść|6 +Podlesia|Pod=le=sia|6 +podmurówki|pod=mu=rów=ki|6 +Podniesła|Pod=nie=sła|6 +podniesło|pod=nie=sło|6 +Podniósł|Pod=niósł|6 +podsuwając|pod=su=wa=jąc|6 +podszedł|pod=szedł|6 +poduszki|po=dusz=ki|6 +podzielić|po=dzie=lić|6 +pogadywać|po=ga=dy=wać|6 +pogrzebie|po=grze=bie|6 +pojechała|po=je=cha=ła|6 +pojękiwał|po=ję=ki=wał|6 +pokazało|po=ka=za=ło|6 +pokazały|po=ka=za=ły|6 +polśniewał|po=lśnie=wał|6 +polśniewały|po=lśnie=wa=ły|6 +pomdlałe|po=mdla=łe|6 +pomieścić|po=mie=ścić|6 +Poniechajcie|Po=nie=chaj=cie|6 +poniektórych|po=nie=któ=rych|6 +poniesły|po=nie=sły|6 +ponieśli|po=nie=śli|6 +ponosił|po=no=sił|6 +popuszczał|po=pusz=czał|6 +popłakiwała|po=pła=ki=wa=ła|6 +poredzili|po=re=dzi=li|6 +porykiwania|po=ry=ki=wa=nia|6 +posiał|po=siał|6 +postawili|po=sta=wi=li|6 +postało|po=sta=ło|6 +postronki|po=stron=ki|6 +postękiwał|po=stę=ki=wał|6 +poszumem|po=szu=mem|6 +posłucha|po=słu=cha|6 +potoczył|po=to=czył|6 +powiadaj|po=wia=daj|6 +powinna|po=win=na|6 +pozdrowienia|po=zdro=wie=nia|6 +pozwolili|po=zwo=li=li|6 +pościel|po=ściel|6 +pośpiechu|po=śpie=chu|6 +pożogą|po=żo=gą|6 +pożyczyć|po=ży=czyć|6 +prawdziwie|praw=dzi=wie|6 +prawdziwy|praw=dzi=wy|6 +prawej|pra=wej|6 +prosząc|pro=sząc|6 +prosząco|pro=szą=co|6 +prośby|proś=by|6 +Pryczek|Pry=czek|6 +przeciwił|prze=ciw=ił|6 +przeciwiła|prze=ciw=i=ła|6 +przeciągle|prze=cią=gle|6 +przedostawać|prze=do=sta=wać|6 +przekradać|prze=kra=dać|6 +przemawiał|prze=ma=wiał|6 +przemieni|prze=mie=ni|6 +przemówił|prze=mó=wił|6 +przeniósł|prze=niósł|6 +przepadła|prze=pa=dła|6 +Przepili|Prze=pi=li|6 +przepomnieć|prze=po=mnieć|6 +przepuści|prze=pu=ści|6 +przepychać|prze=py=chać|6 +przerażających|prze=ra=ża=ją=cych|6 +przeróżnych|prze=róż=nych|6 +przetaczało|prze=ta=cza=ło|6 +przeto|prze=to|6 +przetrzymać|prze=trzy=mać|6 +przewalała|prze=wa=la=ła|6 +przewalały|prze=wa=la=ły|6 +prześpiegi|prze=śpie=gi|6 +przychodzę|przy=cho=dzę|6 +przycichła|przy=ci=chła|6 +przyciszać|przy=ci=szać|6 +przyginał|przy=gi=nał|6 +przygiął|przy=giął|6 +przyjacielstwie|przy=ja=ciel=stwie|6 +przyjacioły|przy=ja=cio=ły|6 +przyjaźnie|przy=jaź=nie|6 +Przyjdzie|Przyj=dzie|6 +przyjrzał|przyj=rzał|6 +przykazywał|przy=ka=zy=wał|6 +przyklęknął|przy=klęk=nął|6 +przymierzać|przy=mie=rzać|6 +przyniesie|przy=nie=sie|6 +przyniesę|przy=nie=sę|6 +przyniewalać|przy=nie=wa=lać|6 +przyodziane|przy=odzia=ne|6 +przyodziać|przy=odziać|6 +przyodziała|przy=odzia=ła|6 +przypiecku|przy=piec=ku|6 +przypominał|przy=po=mi=nał|6 +przystał|przy=stał|6 +przystało|przy=sta=ło|6 +przysłoniła|przy=sło=ni=ła|6 +przytrafić|przy=tra=fić|6 +przytwierdzał|przy=twier=dzał|6 +przytwierdzała|przy=twier=dza=ła|6 +przywarł|przy=warł|6 +przywierała|przy=wie=ra=ła|6 +przyziemnych|przy=ziem=nych|6 +przyłożył|przy=ło=żył|6 +Przyłęka|Przy=łę=ka|6 +przędziwem|przę=dzi=wem|6 +przędzy|przę=dzy|6 +próbując|pró=bu=jąc|6 +psiakrew|psia=krew|6 +pszczelnym|psz=czel=nym|6 +pszczoły|psz=czo=ły|6 +pszenicy|psze=ni=cy|6 +ptaszka|ptasz=ka|6 +ptaszków|ptasz=ków|6 +puszcza|pusz=cza|6 +puszczały|pusz=cza=ły|6 +puszczę|pusz=czę|6 +puściły|pu=ści=ły|6 +pyskiem|py=skiem|6 +pyskuje|py=sku=je|6 +płacił|pła=cił|6 +płakali|pła=ka=li|6 +płakały|pła=ka=ły|6 +płonęły|pło=nę=ły|6 +Płoszki|Płosz=ki|6 +Płoszkową|Płosz=ko=wą|6 +płynie|pły=nie|6 +robaki|ro=ba=ki|6 +robotach|ro=bo=tach|6 +rodzonego|ro=dzo=ne=go|6 +rogate|ro=ga=te|6 +rozchodzili|roz=cho=dzi=li|6 +rozchodził|roz=cho=dził|6 +rozeszły|ro=ze=szły|6 +rozgorzałe|roz=go=rza=łe|6 +rozgorzałymi|roz=go=rza=ły=mi|6 +rozkołysane|roz=ko=ły=sa=ne|6 +rozlewając|roz=le=wa=jąc|6 +Rozmyślała|Roz=my=śla=ła|6 +roznosiło|roz=no=si=ło|6 +rozpaczliwie|roz=pacz=li=wie|6 +rozpaczy|roz=pa=czy|6 +rozpaczą|roz=pa=czą|6 +rozpalonym|roz=pa=lo=nym|6 +rozpozna|roz=po=zna|6 +rozpłakała|roz=pła=ka=ła|6 +roztrzęsiona|roz=trzę=sio=na|6 +rozwodząc|roz=wo=dząc|6 +ruchem|ru=chem|6 +ruchomą|ru=cho=mą|6 +ruszali|ru=sza=li|6 +rzeczach|rze=czach|6 +rzewnie|rzew=nie|6 +rzucali|rzu=ca=li|6 +rzutem|rzu=tem|6 +rzędami|rzę=da=mi|6 +różnościach|róż=no=ściach|6 +rączków|rącz=ków|6 +sadami|sa=da=mi|6 +sadzić|sa=dzić|6 +saniach|sa=niach|6 +saniami|sa=nia=mi|6 +schodzić|scho=dzić|6 +schować|scho=wać|6 +schowała|scho=wa=ła|6 +sennym|sen=nym|6 +siedzeniu|sie=dze=niu|6 +siedząca|sie=dzą=ca|6 +sieroto|sie=ro=to|6 +silniej|sil=niej|6 +siwiły|si=wi=ły|6 +sięgając|się=ga=jąc|6 +skamlała|skam=la=ła|6 +skamlały|skam=la=ły|6 +skarżąc|skar=żąc|6 +skowronkowe|skow=ron=ko=we|6 +skowytem|sko=wy=tem|6 +skończyć|skoń=czyć|6 +skończą|skoń=czą|6 +skropił|skro=pił|6 +skrzypkach|skrzyp=kach|6 +skrzyły|skrzy=ły|6 +skręcał|skrę=cał|6 +skręcie|skrę=cie|6 +smakuje|sma=ku=je|6 +smutki|smut=ki|6 +smutkiem|smut=kiem|6 +smutno|smut=no|6 +smutny|smut=ny|6 +Sołtys|Soł=tys|6 +spadła|spa=dła|6 +spluwał|splu=wał|6 +spoglądali|spo=glą=da=li|6 +spoglądać|spo=glą=dać|6 +spokoić|spo=ko=ić|6 +spokojności|spo=koj=no=ści|6 +sponiewierał|spo=nie=wie=rał|6 +spostrzegła|spo=strze=gła|6 +spotkać|spo=tkać|6 +spozierać|spo=zie=rać|6 +Sprawa|Spra=wa|6 +sprawiedliwie|spra=wie=dli=wie|6 +Sprawiedliwie|Spra=wie=dli=wie|6 +sprawiedliwością|spra=wie=dli=wo=ścią|6 +sprawić|spra=wić|6 +sprawię|spra=wię|6 +sprawił|spra=wił|6 +sprawnie|spraw=nie|6 +spuścił|spu=ścił|6 +spólnie|spól=nie|6 +spółki|spół=ki|6 +spółkę|spół=kę|6 +spłaty|spła=ty|6 +Stachowi|Sta=cho=wi|6 +stajanie|sta=ja=nie|6 +Starszy|Star=szy|6 +staruchy|sta=ru=chy|6 +stawiać|sta=wiać|6 +sterczały|ster=cza=ły|6 +stodołami|sto=do=ła=mi|6 +stodołą|sto=do=łą|6 +stodół|sto=dół|6 +stojące|sto=ją=ce|6 +stojących|sto=ją=cych|6 +stosunku|sto=sun=ku|6 +stołem|sto=łem|6 +strata|stra=ta|6 +strażnik|straż=nik|6 +straży|stra=ży|6 +strwożeni|str=wo=że=ni|6 +strzech|strzech|6 +stróżujące|stró=żu=ją=ce|6 +studnie|stud=nie|6 +studnią|stud=nią|6 +szarej|sza=rej|6 +szarości|sza=ro=ści|6 +szarzały|sza=rza=ły|6 +szczerym|szcze=rym|6 +szczękały|szczę=ka=ły|6 +szerokim|sze=ro=kim|6 +szkodę|szko=dę|6 +szkołą|szko=łą|6 +szmatami|szma=ta=mi|6 +sznury|sznu=ry|6 +szumnie|szum=nie|6 +szydziła|szy=dzi=ła|6 +szykowali|szy=ko=wa=li|6 +Szymkiem|Szym=kiem|6 +szynkwasem|szynk=wa=sem|6 +sądach|są=dach|6 +sędzia|sę=dzia|6 +słodkich|słod=kich|6 +słodkiej|słod=kiej|6 +słodkością|słod=ko=ścią|6 +słoneczny|sło=necz=ny|6 +słuchając|słu=cha=jąc|6 +Słuchali|Słu=cha=li|6 +Słuchała|Słu=cha=ła|6 +słyszy|sły=szy|6 +tamtej|tam=tej|6 +targał|tar=gał|6 +Tereski|Te=re=ski|6 +trusia|tru=sia|6 +trzask|trzask|6 +trzewiki|trze=wi=ki|6 +trzymaj|trzy=maj|6 +tumanach|tu=ma=nach|6 +twojej|two=jej|6 +tygodnia|ty=go=dnia|6 +tylachny|ty=lach=ny|6 +tęskliwe|tę=skli=we|6 +tęsknicy|tę=sk=ni=cy|6 +tęsknie|tę=sk=nie|6 +tęskności|tę=sk=no=ści|6 +tęskność|tę=sk=ność|6 +tłucze|tłu=cze|6 +tłumaczyć|tłu=ma=czyć|6 +tłusty|tłu=sty|6 +ubranie|ubra=nie|6 +uchwalić|uchwa=lić|6 +uciecha|ucie=cha|6 +uciekała|ucie=ka=ła|6 +udźwignie|udźwi=gnie|6 +ukrzywdził|ukrzyw=dził|6 +Ulisia|Uli=sia|6 +uniosła|unio=sła|6 +upadek|upa=dek|6 +uparcie|upar=cie|6 +upominał|upo=mi=nał|6 +uradzi|ura=dzi|6 +uroczyste|uro=czy=ste|6 +urodny|urod=ny|6 +urodzić|uro=dzić|6 +Urzędnik|Urzęd=nik|6 +ustawili|usta=wi=li|6 +utrapień|utra=pień|6 +utrudzony|utru=dzo=ny|6 +utrzymanie|utrzy=ma=nie|6 +uwidzi|uwi=dzi|6 +uwijał|uwi=jał|6 +ułapiło|uła=pi=ło|6 +Wachników|Wach=ni=ków|6 +wagował|wa=go=wał|6 +ważnie|waż=nie|6 +Wczoraj|Wczo=raj|6 +wczorajszej|wczo=raj=szej|6 +weselej|we=se=lej|6 +weselne|we=sel=ne|6 +Widziałem|Wi=dzia=łem|6 +widłami|wi=dła=mi|6 +wieczornych|wie=czor=nych|6 +wiedziały|wie=dzia=ły|6 +wielgachnych|wiel=gach=nych|6 +wielkich|wiel=kich|6 +wierząc|wie=rząc|6 +wilgotne|wil=got=ne|6 +większość|więk=szość|6 +wokoło|wo=ko=ło|6 +wołają|wo=ła=ją|6 +wpatrzona|wpa=trzo=na|6 +wpierając|wpie=ra=jąc|6 +wracać|wra=cać|6 +wraził|wra=ził|6 +wrzasku|wrza=sku|6 +wrzawie|wrza=wie|6 +wrzawy|wrza=wy|6 +wrzeszczeli|wrzesz=cze=li|6 +wrzeszczeć|wrzesz=czeć|6 +wrzątek|wrzą=tek|6 +wsadził|wsa=dził|6 +wstąpiła|wstą=pi=ła|6 +Wszyćko|Wszyć=ko|6 +wybiegając|wy=bie=ga=jąc|6 +wybojach|wy=bo=jach|6 +wybrała|wy=bra=ła|6 +wybuchnęła|wy=buch=nę=ła|6 +wychodziły|wy=cho=dzi=ły|6 +wydała|wy=da=ła|6 +wydało|wy=da=ło|6 +wykrztusiła|wy=krztu=si=ła|6 +wykrzykiwała|wy=krzy=ki=wa=ła|6 +wykręcał|wy=krę=cał|6 +wylazł|wy=lazł|6 +wynosili|wy=no=si=li|6 +wynosić|wy=no=sić|6 +wypadła|wy=pa=dła|6 +wypatruje|wy=pa=tru=je|6 +wypatrując|wy=pa=tru=jąc|6 +wyprawiała|wy=pra=wia=ła|6 +wyprowadził|wy=pro=wa=dził|6 +wypuścił|wy=pu=ścił|6 +wypytywał|wy=py=ty=wał|6 +wyrzekając|wy=rze=ka=jąc|6 +wyschła|wy=schła|6 +wysiedzieć|wy=sie=dzieć|6 +wysokościach|wy=so=ko=ściach|6 +wystraszona|wy=stra=szo=na|6 +wyszły|wy=szły|6 +wytrzyma|wy=trzy=ma|6 +wywartych|wy=war=tych|6 +wywiódł|wy=wiódł|6 +wywodzić|wy=wo=dzić|6 +wywrzeć|wy=wrzeć|6 +wyzdrowiał|wy=zdro=wiał|6 +Wyście|Wy=ście|6 +wyżalić|wy=ża=lić|6 +wzburzona|wzbu=rzo=na|6 +wzburzone|wzbu=rzo=ne|6 +wzgórze|wzgó=rze|6 +Wzieni|Wzie=ni|6 +Wzięła|Wzię=ła|6 +wątpia|wąt=pia|6 +węgieł|wę=gieł|6 +węzełek|wę=ze=łek|6 +własne|wła=sne|6 +włosach|wło=sach|6 +zabawiał|za=ba=wiał|6 +zabierał|za=bie=rał|6 +zabite|za=bi=te|6 +zachodził|za=cho=dził|6 +zachorzał|za=cho=rzał|6 +zachrapał|za=chra=pał|6 +zaczerwienione|za=czer=wie=nio=ne|6 +zaczynało|za=czy=na=ło|6 +zadyszana|za=dy=sza=na|6 +zadzie|za=dzie|6 +zagadać|za=ga=dać|6 +zagadując|za=ga=du=jąc|6 +zajadlej|za=ja=dlej|6 +zajrzy|zaj=rzy|6 +zajrzyj|zaj=rzyj|6 +zajączka|za=jącz=ka|6 +zakatrupię|za=ka=tru=pię|6 +zakolebały|za=ko=le=ba=ły|6 +zakładał|za=kła=dał|6 +zaległo|za=le=gło|6 +zamamrotał|za=mam=ro=tał|6 +zamknął|za=mknął|6 +zamrze|za=mrze|6 +zapachem|za=pa=chem|6 +zapadała|za=pa=da=ła|6 +zapadł|za=padł|6 +zapalili|za=pa=li=li|6 +zapartym|za=par=tym|6 +zapatrzył|za=pa=trzył|6 +zapowiadał|za=po=wia=dał|6 +zapowiedzi|za=po=wie=dzi|6 +zaprzestał|za=prze=stał|6 +zapłacił|za=pła=cił|6 +zarobić|za=ro=bić|6 +zaroiły|za=ro=iły|6 +zarzewia|za=rze=wia|6 +zasiedli|za=sie=dli|6 +zaszczekał|za=szcze=kał|6 +zawarczał|za=war=czał|6 +zawarty|za=war=ty|6 +zawiał|za=wiał|6 +Zawrzyj|Za=wrzyj|6 +zawziął|za=wziął|6 +zawziętości|za=wzię=to=ści|6 +Zawżdy|Za=wż=dy|6 +zaśpiewali|za=śpie=wa=li|6 +zaśpiewała|za=śpie=wa=ła|6 +zbierało|zbie=ra=ło|6 +zdawało|zda=wa=ło|6 +zdrowe|zdro=we|6 +zdumiał|zdu=miał|6 +zebrali|ze=bra=li|6 +zebranie|ze=bra=nie|6 +zechce|ze=chce|6 +zestrachane|ze=stra=cha=ne|6 +zeszedł|ze=szedł|6 +zeszli|ze=szli|6 +zeszły|ze=szły|6 +zginęło|zgi=nę=ło|6 +zgodzić|zgo=dzić|6 +zgromiła|zgro=mi=ła|6 +zielonym|zie=lo=nym|6 +zieloną|zie=lo=ną|6 +zięcia|zię=cia|6 +zjadliwie|zja=dli=wie|6 +zmartwienie|zmar=twie=nie|6 +znalazła|zna=la=zła|6 +zobaczy|zo=ba=czy|6 +zostanie|zo=sta=nie|6 +zrozumiał|zro=zu=miał|6 +zrywały|zry=wa=ły|6 +zwlókł|zwlókł|6 +zwoził|zwo=ził|6 +zwyczajny|zwy=czaj=ny|6 +złotem|zło=tem|6 +łajdusy|łaj=du=sy|6 +łańcuchem|łań=cu=chem|6 +łańcuchu|łań=cu=chu|6 +ścierpieć|ścier=pieć|6 +ścieżaj|ście=żaj|6 +ściszone|ści=szo=ne|6 +śledzie|śle=dzie|6 +śmiałości|śmia=ło=ści|6 +śmielej|śmie=lej|6 +śmiercią|śmier=cią|6 +śmiertelne|śmier=tel=ne|6 +śpiewa|śpie=wa|6 +śpiewają|śpie=wa=ją|6 +śpiewki|śpiew=ki|6 +śpiewów|śpie=wów|6 +świadkach|świad=kach|6 +światełka|świa=teł=ka|6 +światu|świa=tu|6 +światłość|świa=tłość|6 +świetle|świe=tle|6 +świątecznie|świą=tecz=nie|6 +święconego|świę=co=ne=go|6 +Święta|Świę=ta|6 +świętami|świę=ta=mi|6 +świętości|świę=to=ści|6 +źrebak|źre=bak|6 +żarliwie|żar=li=wie|6 +żartował|żar=to=wał|6 +żałosną|ża=ło=sną|6 +Żydowi|Ży=do=wi|6 +żółtym|żół=tym|6 +żółtymi|żół=ty=mi|6 +ambonie|am=bo=nie|5 +Ambroży|Am=bro=ży|5 +baczycie|ba=czy=cie|5 +Balcerków|Bal=cer=ków|5 +beczkę|becz=kę|5 +bezładnie|bez=ład=nie|5 +bełkot|beł=kot|5 +białego|bia=łe=go|5 +biedny|bied=ny|5 +biedotę|bie=do=tę|5 +bieganina|bie=ga=ni=na|5 +biegał|bie=gał|5 +biegała|bie=ga=ła|5 +biegnącej|bie=gną=cej|5 +biegnącą|bie=gną=cą|5 +bielmem|biel=mem|5 +bielone|bie=lo=ne|5 +bijatyki|bi=ja=ty=ki|5 +bladych|bla=dych|5 +bolenia|bo=le=nia|5 +bolesna|bo=le=sna|5 +boskiego|bo=skie=go|5 +brewiarz|bre=wiarz|5 +brzemię|brze=mię|5 +brzózek|brzó=zek|5 +bródkę|bród=kę|5 +budziła|bu=dzi=ła|5 +bujnie|buj=nie|5 +burknął|burk=nął|5 +butach|bu=tach|5 +butelkę|bu=tel=kę|5 +bydlątko|by=dląt=ko|5 +bydłem|by=dłem|5 +Bylicą|By=li=cą|5 +Byliście|By=li=ście|5 +Bójcie|Bój=cie|5 +błyskawica|bły=ska=wi=ca|5 +błyszczący|błysz=czą=cy|5 +całunkami|ca=łun=ka=mi|5 +Chcesz|Chcesz|5 +Chodźmy|Chodź=my|5 +chorobę|cho=ro=bę|5 +chorzał|cho=rzał|5 +chroniąc|chro=niąc|5 +chrzcie|chrzcie|5 +chudziaczek|chu=dzia=czek|5 +chwaci|chwa=ci|5 +chwała|chwa=ła|5 +chwile|chwi=le|5 +chwilą|chwi=lą|5 +chwyciła|chwy=ci=ła|5 +chybciej|chyb=ciej|5 +chybcikiem|chyb=ci=kiem|5 +chyliły|chy=li=ły|5 +chylące|chy=lą=ce|5 +chytał|chy=tał|5 +chłodny|chłod=ny|5 +chłopakom|chło=pa=kom|5 +chłopakowi|chło=pa=ko=wi|5 +chłopski|chłop=ski|5 +chłopskiego|chłop=skie=go|5 +chłopskiej|chłop=skiej|5 +ciekawość|cie=ka=wość|5 +ciemnica|ciem=ni=ca|5 +ciepła|cie=pła|5 +cieszy|cie=szy|5 +ciągnąc|cią=gnąc|5 +Cięgiem|Cię=giem|5 +ciężkiego|cięż=kie=go|5 +cmentarza|cmen=ta=rza|5 +cudzych|cu=dzych|5 +cudzym|cu=dzym|5 +czająco|cza=ją=co|5 +czarny|czar=ny|5 +czarnymi|czar=ny=mi|5 +czasach|cza=sach|5 +czekam|cze=kam|5 +czekanie|cze=ka=nie|5 +czekałam|cze=ka=łam|5 +czekały|cze=ka=ły|5 +czemuś|cze=muś|5 +czemuż|cze=muż|5 +czerwona|czer=wo=na|5 +czworakach|czwo=ra=kach|5 +czynili|czy=ni=li|5 +czyniły|czy=ni=ły|5 +czynności|czyn=no=ści|5 +czysto|czy=sto|5 +czyścu|czy=ś=cu|5 +człowieczy|czło=wie=czy|5 +dalach|da=lach|5 +dawnych|daw=nych|5 +deskami|de=ska=mi|5 +Deszcz|Deszcz|5 +diabeł|dia=beł|5 +dobrocią|do=bro=cią|5 +doczekawszy|do=cze=kaw=szy|5 +doczekał|do=cze=kał|5 +dokumentnie|do=ku=ment=nie|5 +dorznąć|do=rznąć|5 +dorzucał|do=rzu=cał|5 +dostało|do=sta=ło|5 +doszło|do=szło|5 +dotykał|do=ty=kał|5 +dowiedział|do=wie=dział|5 +drapał|dra=pał|5 +drewniany|drew=nia=ny|5 +Drugie|Dru=gie|5 +drwiąco|drwią=co|5 +Drzewa|Drze=wa|5 +drzewie|drze=wie|5 +dufnością|duf=no=ścią|5 +dufność|duf=ność|5 +dusili|du=si=li|5 +dworska|dwor=ska|5 +dworów|dwo=rów|5 +dychanie|dy=cha=nie|5 +dychał|dy=chał|5 +dygować|dy=go=wać|5 +dymiły|dy=mi=ły|5 +dziadku|dziad=ku|5 +dziadówka|dzia=dów=ka|5 +działów|dzia=łów|5 +dzieciska|dzie=ci=ska|5 +dzieciński|dzie=ciń=ski|5 +dziedzicowym|dzie=dzi=co=wym|5 +dziedziczki|dzie=dzicz=ki|5 +dziesiąte|dzie=sią=te|5 +dziesiątku|dzie=siąt=ku|5 +dzieuchom|dzieu=chom|5 +Dzieuchy|Dzieu=chy|5 +dzieuszyne|dzie=uszy=ne|5 +dzikich|dzi=kich|5 +dziwne|dziw=ne|5 +dziwnych|dziw=nych|5 +dziwnym|dziw=nym|5 +dziwowali|dzi=wo=wa=li|5 +dzwonienie|dzwo=nie=nie|5 +dzwonią|dzwo=nią|5 +dzwonić|dzwo=nić|5 +dzwonów|dzwo=nów|5 +dłuższe|dłuż=sze|5 +fajeczkę|fa=jecz=kę|5 +Filipki|Fi=lip=ki|5 +fleciku|fle=ci=ku|5 +frasunki|fra=sun=ki|5 +Fundacja|Fun=da=cja|5 +gadzina|ga=dzi=na|5 +galante|ga=lan=te|5 +gardziele|gar=dzie=le|5 +gardła|gar=dła|5 +garnczek|garn=czek|5 +garnka|garn=ka|5 +garnuszek|gar=nu=szek|5 +gałązek|ga=łą=zek|5 +gdakały|gda=ka=ły|5 +gmerał|gme=rał|5 +gniazda|gniaz=da|5 +gniazdach|gniaz=dach|5 +gniewał|gnie=wał|5 +gorsza|gor=sza|5 +gorzałce|go=rzał=ce|5 +gorącością|go=rą=co=ścią|5 +gorącość|go=rą=cość|5 +gorącym|go=rą=cym|5 +gorącą|go=rą=cą|5 +Gospodarze|Go=spo=da=rze|5 +gospodynię|go=spo=dy=nię|5 +Gołębiów|Go=łę=biów|5 +granuli|gra=nu=li|5 +grdyką|grdy=ką|5 +grobie|gro=bie|5 +groził|gro=ził|5 +grożąc|gro=żąc|5 +grubachny|gru=bach=ny|5 +gruntu|grun=tu|5 +grzbiety|grzbie=ty|5 +gwarem|gwa=rem|5 +gwarów|gwa=rów|5 +gąszczów|gąsz=czów|5 +gębusię|gę=bu=się|5 +gęgoty|gę=go=ty|5 +gęstwy|gę=stwy|5 +gęstych|gę=stych|5 +głodne|głod=ne|5 +głowach|gło=wach|5 +głowiny|gło=wi=ny|5 +głośne|gło=śne|5 +głupiemu|głu=pie=mu|5 +głębin|głę=bin|5 +hukiem|hu=kiem|5 +hukliwie|hu=kli=wie|5 +huknęli|huk=nę=li|5 +huknęły|huk=nę=ły|5 +hurkotał|hur=ko=tał|5 +idących|idą=cych|5 +inszej|in=szej|5 +Jagustynce|Ja=gu=styn=ce|5 +Jagustynkę|Ja=gu=styn=kę|5 +Jakieś|Ja=kieś|5 +jarzące|ja=rzą=ce|5 +jarzących|ja=rzą=cych|5 +jastrzębie|ja=strzę=bie|5 +jazgotliwe|ja=zgo=tli=we|5 +jałowców|ja=łow=ców|5 +jechała|je=cha=ła|5 +jednemu|jed=ne=mu|5 +jedyna|je=dy=na|5 +jedyny|je=dy=ny|5 +jensze|jen=sze|5 +jesienne|je=sien=ne|5 +jesiennych|je=sien=nych|5 +Jesień|Je=sień|5 +Jezusa|Je=zu=sa|5 +jęczały|ję=cza=ły|5 +jękliwy|ję=kli=wy|5 +kalkulował|kal=ku=lo=wał|5 +kamienia|ka=mie=nia|5 +kamionek|ka=mio=nek|5 +kapelusz|ka=pe=lusz|5 +kazali|ka=za=li|5 +kałduny|kał=du=ny|5 +Każdemu|Każ=de=mu|5 +kiełbas|kieł=bas|5 +kilkadziesiąt|kil=ka=dzie=siąt|5 +klepisku|kle=pi=sku|5 +klucze|klu=cze|5 +klątwy|klą=twy|5 +klęczał|klę=czał|5 +klęczek|klę=czek|5 +kobiecych|ko=bie=cych|5 +kolanach|ko=la=nach|5 +kolanami|ko=la=na=mi|5 +kolebać|ko=le=bać|5 +kolebał|ko=le=bał|5 +kolorowych|ko=lo=ro=wych|5 +kominów|ko=mi=nów|5 +komornicy|ko=mor=ni=cy|5 +komorniki|ko=mor=ni=ki|5 +koników|ko=ni=ków|5 +konisko|ko=ni=sko|5 +kopcach|kop=cach|5 +korzeniami|ko=rze=nia=mi|5 +korzeń|ko=rzeń|5 +kostki|kost=ki|5 +kołkami|koł=ka=mi|5 +kołować|ko=ło=wać|5 +kożucha|ko=żu=cha|5 +kreminał|kre=mi=nał|5 +kropidło|kro=pi=dło|5 +krowami|kro=wa=mi|5 +krwawy|krwa=wy|5 +krwawych|krwa=wych|5 +kryjąc|kry=jąc|5 +kryminale|kry=mi=na=le|5 +kryminał|kry=mi=nał|5 +krzach|krzach|5 +krzaki|krza=ki|5 +krzykać|krzy=kać|5 +krzątając|krzą=ta=jąc|5 +krążyły|krą=ży=ły|5 +kręciła|krę=ci=ła|5 +kręciło|krę=ci=ło|5 +kręgiem|krę=giem|5 +którejś|któ=rejś|5 +Kubowa|Ku=bo=wa|5 +kujnął|kuj=nął|5 +kulasa|ku=la=sa|5 +kużdego|kuż=de=go|5 +kwardo|kwar=do|5 +kwietne|kwiet=ne|5 +kępami|kę=pa=mi|5 +kłoniły|kło=ni=ły|5 +kłócili|kłó=ci=li|5 +Kłębem|Kłę=bem|5 +lakudra|la=ku=dra|5 +lampki|lamp=ki|5 +latach|la=tach|5 +latego|la=te=go|5 +lepsza|lep=sza|5 +lepszych|lep=szych|5 +licząca|li=czą=ca|5 +Lipcom|Lip=com|5 +Lipczaków|Lip=cza=ków|5 +litery|li=te=ry|5 +litości|li=to=ści|5 +loboga|lo=bo=ga|5 +lękiem|lę=kiem|5 +machorki|ma=chor=ki|5 +Maciuś|Ma=ciuś|5 +macochą|ma=co=chą|5 +Magdzie|Mag=dzie|5 +majstrował|maj=stro=wał|5 +makiem|ma=kiem|5 +mamrotała|mam=ro=ta=ła|5 +marnacji|mar=na=cji|5 +Marysi|Ma=ry=si|5 +marzenia|ma=rze=nia|5 +marzeń|ma=rzeń|5 +masłem|ma=słem|5 +matczynej|mat=czy=nej|5 +matuli|ma=tu=li|5 +medytować|me=dy=to=wać|5 +miarkuje|miar=ku=je|5 +miasteczka|mia=stecz=ka|5 +miasto|mia=sto|5 +Michała|Mi=cha=ła|5 +mierzi|mier=zi|5 +mieszkania|miesz=ka=nia|5 +mieszkał|miesz=kał|5 +mignęła|mi=gnę=ła|5 +miłowanie|mi=ło=wa=nie|5 +miłował|mi=ło=wał|5 +miłościwy|mi=ło=ści=wy|5 +mocnym|moc=nym|5 +modrawych|mo=dra=wych|5 +modrawą|mo=dra=wą|5 +modrych|mo=drych|5 +modrzały|mo=drza=ły|5 +mogłaby|mo=gła=by|5 +moiściewy|mo=iście=wy|5 +motyczkami|mo=tycz=ka=mi|5 +możemy|mo=że=my|5 +mrocznej|mrocz=nej|5 +mroczno|mrocz=no|5 +mrokiem|mro=kiem|5 +muzykanci|mu=zy=kan=ci|5 +muzyką|mu=zy=ką|5 +mąciło|mą=ci=ło|5 +młodym|mło=dym|5 +młotem|mło=tem|5 +młynarczyka|mły=nar=czy=ka|5 +nabożnym|na=boż=nym|5 +nabrał|na=brał|5 +naczynia|na=czy=nia|5 +nadziei|na=dziei|5 +naglądać|na=glą=dać|5 +nagrzany|na=grza=ny|5 +nagłym|na=głym|5 +najczęściej|naj=czę=ściej|5 +najmilejsza|naj=mi=lej=sza|5 +najmniej|naj=mniej|5 +najpierwej|naj=pier=wej|5 +najspokojniej|naj=spo=koj=niej|5 +największe|naj=więk=sze|5 +największą|naj=więk=szą|5 +namyśle|na=my=śle|5 +naprędce|na=pręd=ce|5 +naradę|na=ra=dę|5 +narobiła|na=ro=bi=ła|5 +Narodzie|Na=ro=dzie|5 +narządzić|na=rzą=dzić|5 +nastawał|na=sta=wał|5 +naszczekiwania|na=szcze=ki=wa=nia|5 +natknąwszy|na=tknąw=szy|5 +nawrócił|na=wró=cił|5 +niebieską|nie=bie=ską|5 +nieboszczyk|nie=bosz=czyk|5 +niedaleczko|nie=da=lecz=ko|5 +Niedługo|Nie=dłu=go|5 +niegdzie|nie=gdzie|5 +niejedni|nie=jed=ni|5 +nienawiści|nie=na=wi=ści|5 +nienawiść|nie=na=wiść|5 +nieodstępnie|nie=od=stęp=nie|5 +niepamięci|nie=pa=mię=ci|5 +niepomiernie|nie=po=mier=nie|5 +nieprzejrzane|nie=przej=rza=ne|5 +nieprzerwanym|nie=prze=rwa=nym|5 +nieprzytomnym|nie=przy=tom=nym|5 +nieskończone|nie=skoń=czo=ne|5 +nieskończonym|nie=skoń=czo=nym|5 +niesporo|nie=spo=ro|5 +nieszczęsna|nie=szczę=sna|5 +nieszczęścia|nie=szczę=ścia|5 +nieszporach|nie=szpo=rach|5 +nieubłaganą|nie=ubła=ga=ną|5 +nieustępliwie|nie=ustę=pli=wie|5 +nieustępliwy|nie=ustę=pli=wy|5 +niewola|nie=wo=la|5 +niezgorsza|nie=zgor=sza|5 +niezgłębioną|nie=zgłę=bio=ną|5 +niezmierną|nie=zmier=ną|5 +nikiej|ni=kiej|5 +nowinki|no=win=ki|5 +Nowoczesna|No=wo=cze=sna|5 +Obaczcie|Obacz=cie|5 +Obaczycie|Oba=czy=cie|5 +Obejrzała|Obej=rza=ła|5 +obrazik|ob=ra=zik|5 +obrazu|ob=ra=zu|5 +obroku|ob=ro=ku|5 +obrosłe|ob=ro=słe|5 +obszedł|ob=szedł|5 +obwisłe|ob=wi=słe|5 +obwisłymi|ob=wi=sły=mi|5 +obłożony|ob=ło=żo=ny|5 +ocierając|ocie=ra=jąc|5 +oczymgnieniu|oczym=gnie=niu|5 +odbierze|od=bie=rze|5 +oddawał|od=da=wał|5 +odejściu|odej=ściu|5 +oderwał|ode=rwał|5 +odgłosy|od=gło=sy|5 +odjechał|od=je=chał|5 +odkrzyknęła|od=krzyk=nę=ła|5 +odmawiać|od=ma=wiać|5 +odmawiał|od=ma=wiał|5 +odprawiała|od=pra=wia=ła|5 +odwracały|od=wra=ca=ły|5 +odzywała|od=zy=wa=ła|5 +odzywały|od=zy=wa=ły|5 +odświętnie|od=święt=nie|5 +oganiać|oga=niać|5 +ogląda|oglą=da|5 +ogniami|ognia=mi|5 +ognisty|ogni=sty|5 +ogrodzenie|ogro=dze=nie|5 +ogromna|ogrom=na|5 +ojcowa|oj=co=wa|5 +ojcowy|oj=co=wy|5 +okolicach|oko=li=cach|5 +okresach|okre=sach|5 +okrwawiony|okrwa=wio=ny|5 +oktawę|okta=wę|5 +okwiat|okwiat|5 +omijali|omi=ja=li|5 +opamiętała|opa=mię=ta=ła|5 +oparcie|opar=cie|5 +opowiadań|opo=wia=dań|5 +oprócz|oprócz|5 +orzydla|orzy=dla|5 +ostaje|osta=je|5 +ostaniesz|osta=niesz|5 +ostatek|osta=tek|5 +ostatniego|ostat=nie=go|5 +ostrym|ostrym|5 +oszroniałych|oszro=nia=łych|5 +otwartych|otwar=tych|5 +otwierając|otwie=ra=jąc|5 +otworzyła|otwo=rzy=ła|5 +ozewrzeć|ozew=rzeć|5 +ozimin|ozi=min|5 +oziminy|ozi=mi=ny|5 +oślepłe|ośle=płe|5 +paczkę|pacz=kę|5 +padając|pa=da=jąc|5 +palmami|pal=ma=mi|5 +pamiętajcie|pa=mię=taj=cie|5 +pamiętam|pa=mię=tam|5 +Panienka|Pa=nien=ka|5 +papierem|pa=pie=rem|5 +parafia|pa=ra=fia|5 +parszywego|par=szy=we=go|5 +parzył|pa=rzył|5 +parzyła|pa=rzy=ła|5 +pasiastych|pa=sia=stych|5 +paskudny|pa=skud=ny|5 +pastwiska|pa=stwi=ska|5 +patrzeli|pa=trze=li|5 +patrzyli|pa=trzy=li|5 +pewnością|pew=no=ścią|5 +pełzały|peł=za=ły|5 +pierun|pie=run|5 +pierzyna|pie=rzy=na|5 +piesku|pie=sku|5 +Pietras|Pie=tras|5 +pijani|pi=ja=ni|5 +pijanica|pi=ja=ni=ca|5 +pijcie|pij=cie|5 +pilnujcie|pil=nuj=cie|5 +pisane|pi=sa=ne|5 +pisarzem|pi=sa=rzem|5 +piskorz|pi=skorz|5 +piszczy|pisz=czy|5 +pięciu|pię=ciu|5 +piękna|pięk=na|5 +Piękny|Pięk=ny|5 +plątała|plą=ta=ła|5 +plątały|plą=ta=ły|5 +pobladły|po=bla=dły|5 +pocałowała|po=ca=ło=wa=ła|5 +pochylając|po=chy=la=jąc|5 +pociągnęli|po=cią=gnę=li|5 +poczciwa|po=czci=wa|5 +poczeka|po=cze=ka|5 +poczerwieniał|po=czer=wie=niał|5 +poczynało|po=czy=na=ło|5 +poczynały|po=czy=na=ły|5 +podatek|po=da=tek|5 +podawał|po=da=wał|5 +podjadł|pod=jadł|5 +Podniesienie|Pod=nie=sie=nie|5 +podnosili|pod=no=si=li|5 +podobna|po=dob=na|5 +podorędziu|po=do=rę=dziu|5 +podrapał|po=dra=pał|5 +podtykała|pod=ty=ka=ła|5 +podzieje|po=dzie=je|5 +podzieję|po=dzie=ję|5 +podzięce|po=dzię=ce|5 +pogadywała|po=ga=dy=wa=ła|5 +pogadywały|po=ga=dy=wa=ły|5 +pogardliwie|po=gar=dli=wie|5 +Pogoda|Po=go=da|5 +pogroza|po=gro=za|5 +pogrozy|po=gro=zy|5 +pogwizdywał|po=gwiz=dy=wał|5 +pogłaskał|po=gła=skał|5 +pojadał|po=ja=dał|5 +Pokażcie|Po=każ=cie|5 +pokosy|po=ko=sy|5 +pokrako|po=kra=ko|5 +pokryte|po=kry=te|5 +pokrywał|po=kry=wał|5 +pokrzykiwał|po=krzy=ki=wał|5 +pokręcił|po=krę=cił|5 +pokutę|po=ku=tę|5 +pokładali|po=kła=da=li|5 +polazł|po=lazł|5 +poleci|po=le=ci|5 +pomagając|po=ma=ga=jąc|5 +pomagało|po=ma=ga=ło|5 +pomarli|po=mar=li|5 +pomarła|po=mar=ła|5 +pomarłych|po=mar=łych|5 +pomiarkowali|po=miar=ko=wa=li|5 +pomiarkuj|po=miar=kuj|5 +pomogły|po=mo=gły|5 +pomstować|po=msto=wać|5 +pomstowała|po=msto=wa=ła|5 +poniechaj|po=nie=chaj|5 +poniechali|po=nie=cha=li|5 +popołudnie|po=po=łu=dnie|5 +poprowadzi|po=pro=wa=dzi|5 +popuścić|po=pu=ścić|5 +portek|por=tek|5 +porwali|po=rwa=li|5 +Porwał|Po=rwał|5 +porywając|po=ry=wa=jąc|5 +porębie|po=rę=bie|5 +postawi|po=sta=wi|5 +postawią|po=sta=wią|5 +postawiła|po=sta=wi=ła|5 +postąpił|po=stą=pił|5 +posypał|po=sy=pał|5 +Poszłabym|Po=szła=bym|5 +posępne|po=sęp=ne|5 +posępnie|po=sęp=nie|5 +posłać|po=słać|5 +posłuchu|po=słu=chu|5 +potrafiła|po=tra=fi=ła|5 +potrzebie|po=trze=bie|5 +potężne|po=tęż=ne|5 +Powiadają|Po=wia=da=ją|5 +Powiadam|Po=wia=dam|5 +Powiadał|Po=wia=dał|5 +Powiedają|Po=wie=da=ją|5 +powiedając|po=wie=da=jąc|5 +Powiedali|Po=wie=da=li|5 +powiedzcie|po=wiedz=cie|5 +powiedzenie|po=wie=dze=nie|5 +Powiedziałem|Po=wie=dzia=łem|5 +powiekami|po=wie=ka=mi|5 +powiesić|po=wie=sić|5 +powiew|po=wiew|5 +powinno|po=win=no|5 +powinny|po=win=ny|5 +powlekła|po=wle=kła|5 +powrósła|po=wró=sła|5 +powstały|po=wsta=ły|5 +pozdrawiał|po=zdra=wiał|5 +poznali|po=zna=li|5 +pozostać|po=zo=stać|5 +pozwalał|po=zwa=lał|5 +pozwolił|po=zwo=lił|5 +pozwolę|po=zwo=lę|5 +połcie|po=łcie|5 +połowę|po=ło=wę|5 +Południe|Po=łu=dnie|5 +pożaru|po=ża=ru|5 +pożywi|po=ży=wi|5 +pradziada|pra=dzia=da|5 +prażyło|pra=ży=ło|5 +proboszczem|pro=bosz=czem|5 +progach|pro=gach|5 +prosiaka|pro=sia=ka|5 +prosięta|pro=się=ta|5 +prosiętami|pro=się=ta=mi|5 +proste|pro=ste|5 +Prosto|Pro=sto|5 +prostowała|pro=sto=wa=ła|5 +prosty|pro=sty|5 +proszalne|pro=szal=ne|5 +proszonym|pro=szo=nym|5 +przebierały|prze=bie=ra=ły|5 +przebrał|prze=brał|5 +przechylił|prze=chy=lił|5 +przecie|prze=cie|5 +przeciągały|prze=cią=ga=ły|5 +przeciągnął|prze=cią=gnął|5 +przeczytał|prze=czy=tał|5 +przednim|przed=nim|5 +przegryźć|prze=gryźć|5 +przejmowały|przej=mo=wa=ły|5 +przejrzyste|przej=rzy=ste|5 +przekładał|prze=kła=dał|5 +przelewa|prze=le=wa|5 +przemawiać|prze=ma=wiać|5 +przemienił|prze=mie=nił|5 +przemogła|prze=mo=gła|5 +przemoże|prze=mo=że|5 +przemyślnie|prze=myśl=nie|5 +przeniesła|prze=nie=sła|5 +przenikliwy|prze=ni=kli=wy|5 +przenosiny|prze=no=si=ny|5 +Przepijcie|Prze=pij=cie|5 +przepiórki|prze=piór=ki|5 +przepomniał|prze=po=mniał|5 +przepomniała|prze=po=mnia=ła|5 +przepraszać|prze=pra=szać|5 +przeraźliwie|prze=raź=li=wie|5 +przerywając|prze=ry=wa=jąc|5 +przeschnie|prze=schnie|5 +przesiadywał|prze=sia=dy=wał|5 +przesiedział|prze=sie=dział|5 +przestała|prze=sta=ła|5 +przetak|prze=tak|5 +Przewrotny|Prze=wrot=ny|5 +przezwiska|prze=zwi=ska|5 +przezwisko|prze=zwi=sko|5 +przełknąć|prze=łknąć|5 +prześmiewał|prze=śmie=wał|5 From e76ebb27d739c0f4cfd736db7238435f51b6553a Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Fri, 15 May 2026 09:50:41 -0500 Subject: [PATCH 72/93] fix: Prepare SD card font caches from txt reader (#1973) ## Summary SD card font fixes: - `TxtReaderActivity` needs to call `renderer.ensureSdCardFontReady` to build the advance lookup table to support rendering with SD card fonts. This revealed that `TxtReaderActivity` was inconsistently performing layout with `getTextWidth`, when the renderer actually uses `getTextAdvanceX`, which can lead to minor inconsistencies in alignment. - Avoid allocating one big `allText` string in `ParsedText::layoutAndExtractLines`. Instead, pass the vector of word strings directly to `SdCardFont::buildAdvanceTable`, where the algorithm just needs to iterate codepoints anyway. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ --------- Co-authored-by: Justin Mitchell --- lib/EpdFont/SdCardFont.cpp | 155 +++++++++++--------- lib/EpdFont/SdCardFont.h | 8 +- lib/Epub/Epub/ParsedText.cpp | 16 +- lib/GfxRenderer/GfxRenderer.cpp | 13 +- lib/GfxRenderer/GfxRenderer.h | 2 + src/activities/reader/TxtReaderActivity.cpp | 20 ++- 6 files changed, 125 insertions(+), 89 deletions(-) diff --git a/lib/EpdFont/SdCardFont.cpp b/lib/EpdFont/SdCardFont.cpp index 8b49671979..6c860949af 100644 --- a/lib/EpdFont/SdCardFont.cpp +++ b/lib/EpdFont/SdCardFont.cpp @@ -16,11 +16,13 @@ static_assert(sizeof(EpdUnicodeInterval) == 12, "EpdUnicodeInterval must be 12 b static_assert(sizeof(EpdKernClassEntry) == 3, "EpdKernClassEntry must be 3 bytes to match .cpfont file layout"); static_assert(sizeof(EpdLigaturePair) == 8, "EpdLigaturePair must be 8 bytes to match .cpfont file layout"); +namespace { + // FNV-1a hash for content-based font ID generation -static constexpr uint32_t FNV_OFFSET = 2166136261u; -static constexpr uint32_t FNV_PRIME = 16777619u; +constexpr uint32_t FNV_OFFSET = 2166136261u; +constexpr uint32_t FNV_PRIME = 16777619u; -static uint32_t fnv1a(const uint8_t* data, size_t len, uint32_t hash = FNV_OFFSET) { +uint32_t fnv1a(const uint8_t* data, size_t len, uint32_t hash = FNV_OFFSET) { for (size_t i = 0; i < len; i++) { hash ^= data[i]; hash *= FNV_PRIME; @@ -29,16 +31,44 @@ static uint32_t fnv1a(const uint8_t* data, size_t len, uint32_t hash = FNV_OFFSE } // .cpfont magic bytes -static constexpr char CPFONT_MAGIC[8] = {'C', 'P', 'F', 'O', 'N', 'T', '\0', '\0'}; +constexpr char CPFONT_MAGIC[8] = {'C', 'P', 'F', 'O', 'N', 'T', '\0', '\0'}; // CPFONT_VERSION is defined as a #define in SdCardFont.h so it can be // stringified into FONT_MANIFEST_URL. -static constexpr uint32_t HEADER_SIZE = 32; -static constexpr uint32_t STYLE_TOC_ENTRY_SIZE = 32; +constexpr uint32_t HEADER_SIZE = 32; +constexpr uint32_t STYLE_TOC_ENTRY_SIZE = 32; // Helper to read little-endian values from byte buffer -static inline uint16_t readU16(const uint8_t* p) { return p[0] | (p[1] << 8); } -static inline int16_t readI16(const uint8_t* p) { return static_cast(p[0] | (p[1] << 8)); } -static inline uint32_t readU32(const uint8_t* p) { return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } +inline uint16_t readU16(const uint8_t* p) { return p[0] | (p[1] << 8); } +inline int16_t readI16(const uint8_t* p) { return static_cast(p[0] | (p[1] << 8)); } +inline uint32_t readU32(const uint8_t* p) { return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } + +// Walks a null-terminated UTF-8 string and appends each unique codepoint to +// codepoints[0..cpCount-1] via O(n²) dedup. Returns true if the buffer +// reached maxCount (cap hit), false if all codepoints fit. +bool collectUniqueCodepoints(const char* text, uint32_t* codepoints, uint32_t& cpCount, uint32_t maxCount) { + const unsigned char* p = reinterpret_cast(text); + while (*p) { + uint32_t cp = utf8NextCodepoint(&p); + if (cp == 0) break; + bool found = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == cp) { + found = true; + break; + } + } + if (!found) { + if (cpCount >= maxCount) return true; + codepoints[cpCount++] = cp; + } + } + return false; +} + +const char* asCStr(const std::string& s) { return s.c_str(); } +const char* asCStr(const char* s) { return s; } + +} // namespace SdCardFont::~SdCardFont() { freeAll(); } @@ -1019,64 +1049,10 @@ uint16_t SdCardFont::getAdvance(uint32_t codepoint, uint8_t style) const { return 0; } -int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) { - if (!loaded_) return -1; - styleMask = resolveStyleMask(styleMask); - if (styleMask == 0) return 0; - - // Note: advance table is preserved across calls. We only fetch codepoints - // not already present, then merge them in. Use clearPersistentCache() to - // wipe the table when the font/size/family changes. - - unsigned long startMs = millis(); - - // Step 1: Extract unique codepoints, capped at MAX_UNIQUE_CODEPOINTS. - // The dedup buffer is sized to the cap, not total chars — a large EPUB section - // may contain 50K+ characters but real text has far fewer unique codepoints. - // 4096 × 4 bytes = 16KB temporary; bounded regardless of input size. - static constexpr uint32_t MAX_UNIQUE_CODEPOINTS = 4096; - uint32_t* codepoints = new (std::nothrow) uint32_t[MAX_UNIQUE_CODEPOINTS]; - if (!codepoints) { - LOG_ERR("SDCF", "buildAdvanceTable: failed to allocate codepoint buffer (%u bytes)", MAX_UNIQUE_CODEPOINTS * 4); - return -1; - } - uint32_t cpCount = 0; - bool hitCap = false; - - // Second pass: collect unique codepoints via O(n²) dedup. - // Bounded by uniqueCount × totalChars comparisons. For 2000 unique from 2291 total, - // worst case ~4.6M comparisons of uint32_t — ~30ms on 160MHz RISC-V, acceptable - // for one-time section indexing. - const unsigned char* p = reinterpret_cast(utf8Text); - while (*p) { - uint32_t cp = utf8NextCodepoint(&p); - if (cp == 0) break; - - bool found = false; - for (uint32_t i = 0; i < cpCount; i++) { - if (codepoints[i] == cp) { - found = true; - break; - } - } - if (!found) { - if (cpCount >= MAX_UNIQUE_CODEPOINTS) { - hitCap = true; - break; - } - codepoints[cpCount++] = cp; - } - } - if (hitCap) { - LOG_ERR("SDCF", "buildAdvanceTable: unique codepoint cap (%u) hit, layout may be approximate", - MAX_UNIQUE_CODEPOINTS); - } - - // Sort for ordered glyph index mapping and final table output - std::sort(codepoints, codepoints + cpCount); - - // Step 2: For each requested style, fetch any codepoints not yet cached and - // merge them into the persistent advance table. +// Given a sorted array of unique codepoints, resolve glyph indices per style, +// batch-read advanceX from SD, and merge into the persistent advance table. +// Caller owns the codepoints buffer. +int SdCardFont::fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask) { int totalMissed = 0; for (uint8_t si = 0; si < MAX_STYLES; si++) { if (!(styleMask & (1 << si)) || !styles_[si].present) continue; @@ -1175,12 +1151,55 @@ int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) { ADVANCE_CACHE_LIMIT); } - delete[] codepoints; + return totalMissed; +} + +template +int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask) { + if (!loaded_) return -1; + styleMask = resolveStyleMask(styleMask); + if (styleMask == 0) return 0; + + unsigned long startMs = millis(); + // +2 reserved slots for space and hyphen injected after the main scan. + static constexpr uint32_t MAX_UNIQUE_CODEPOINTS = 4096; + uint32_t* codepoints = new (std::nothrow) uint32_t[MAX_UNIQUE_CODEPOINTS + 2]; + if (!codepoints) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to allocate codepoint buffer (%u bytes)", MAX_UNIQUE_CODEPOINTS * 4); + return -1; + } + uint32_t cpCount = 0; + bool hitCap = false; + + for (auto it = begin; it != end && !hitCap; ++it) { + hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS); + } + + if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; })) + codepoints[cpCount++] = ' '; + if (includeHyphen && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == '-'; })) + codepoints[cpCount++] = '-'; + + if (hitCap) { + LOG_ERR("SDCF", "buildAdvanceTable: unique codepoint cap (%u) hit, layout may be approximate", + MAX_UNIQUE_CODEPOINTS); + } + std::sort(codepoints, codepoints + cpCount); + int totalMissed = fetchAdvancesForCodepoints(codepoints, cpCount, styleMask); + delete[] codepoints; stats_.prewarmTotalMs = millis() - startMs; return totalMissed; } +int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) { + return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask); +} + +int SdCardFont::buildAdvanceTable(const std::vector& words, bool includeHyphen, uint8_t styleMask) { + return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask); +} + // --- Stats --- void SdCardFont::logStats(const char* label) { diff --git a/lib/EpdFont/SdCardFont.h b/lib/EpdFont/SdCardFont.h index c826000e2d..d4d680edb2 100644 --- a/lib/EpdFont/SdCardFont.h +++ b/lib/EpdFont/SdCardFont.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include "EpdFont.h" #include "EpdFontData.h" @@ -43,10 +45,11 @@ class SdCardFont { int prewarm(const char* utf8Text, uint8_t styleMask = 0x0F, bool metadataOnly = false); // Build a compact advance-only table for layout measurement. - // Extracts ALL unique codepoints from utf8Text (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. // Returns number of codepoints not found in font coverage. int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F); + int buildAdvanceTable(const std::vector& words, bool includeHyphen, uint8_t styleMask = 0x0F); // Look up advanceX for a codepoint from the advance table. // Returns the 12.4 fixed-point advance, or 0 if not found. @@ -236,6 +239,9 @@ class SdCardFont { void applyKernLigaturePointers(PerStyle& s, EpdFontData& data) const; void applyGlyphMissCallback(uint8_t styleIdx); int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const; + int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask); + template + int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask); int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly); // Global helpers diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index e348270b9b..09412d193b 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -255,20 +255,6 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo // (advanceX only, no bitmaps) for all unique codepoints in this paragraph so // that calculateWordWidths() can measure text without on-demand SD I/O. if (renderer.isSdCardFont(fontId)) { - // Reserve upfront so the joined text allocates exactly once. Without this, - // paragraphs with many words trigger a chain of vector-like reallocations - // inside std::string during layout — visible in prewarm timings for SD fonts. - size_t totalSize = hyphenationEnabled ? 1 : 0; - if (!words.empty()) totalSize += words.size() - 1; // inter-word spaces - for (const auto& w : words) totalSize += w.size(); - std::string allText; - allText.reserve(totalSize); - for (size_t i = 0; i < words.size(); i++) { - if (i > 0) allText += ' '; - allText += words[i]; - } - if (hyphenationEnabled) allText += '-'; - // Style mask: only ask the SD font to load advances for styles actually // used in this paragraph. Style index is the low two bits (regular/bold/ // italic/bold-italic); the underline bit is irrelevant to advance metrics. @@ -277,7 +263,7 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo styleMask |= static_cast(1u << (static_cast(s) & 0x03)); } if (styleMask == 0) styleMask = 0x01; // defensive: regular only - renderer.ensureSdCardFontReady(fontId, allText.c_str(), styleMask); + renderer.ensureSdCardFontReady(fontId, words, hyphenationEnabled, styleMask); } const int pageWidth = viewportWidth; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index e6f1eadbc2..63c3f10a8f 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -77,11 +77,22 @@ const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const Ep void GfxRenderer::ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask) const { auto it = sdCardFonts_.find(fontId); + if (it != sdCardFonts_.end()) { + int missed = it->second->buildAdvanceTable(utf8Text, styleMask); + if (missed > 0) { + LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed); + } + } +} + +void GfxRenderer::ensureSdCardFontReady(int fontId, const std::vector& words, bool includeHyphen, + uint8_t styleMask) const { + auto it = sdCardFonts_.find(fontId); if (it != sdCardFonts_.end()) { // Augment the persistent advance-only table for layout measurement. // The table survives across paragraphs/sections (capped per font), so // repeated indexing of the same SD font amortizes glyph-metric SD reads. - int missed = it->second->buildAdvanceTable(utf8Text, styleMask); + int missed = it->second->buildAdvanceTable(words, includeHyphen, styleMask); if (missed > 0) { LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed); } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 6de6b4adbd..1c7197deda 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -128,6 +128,8 @@ class GfxRenderer { // (which holds a const GfxRenderer&) before measuring word widths. Safe to call on non-SD fonts (no-op). // styleMask: bitmask of styles to prepare (bit 0=regular, 1=bold, 2=italic, 3=bold-italic). void ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask = 0x0F) const; + void ensureSdCardFontReady(int fontId, const std::vector& words, bool includeHyphen, + uint8_t styleMask = 0x0F) const; // Orientation control (affects logical width/height and coordinate transforms) void setOrientation(const Orientation o) { orientation = o; } diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 6d95245d07..c4c8552222 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -192,6 +192,17 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector } buffer[chunkSize] = '\0'; + // Prime the SD card font's advance table with this chunk's codepoints. + // Without this, every getTextAdvanceX() call in the wrap loop below triggers + // on-demand glyph loads through the 8-slot overflow ring buffer, which + // thrashes for any text with more than 8 unique chars (i.e. all English), + // floods the heap with short-lived bitmap allocations, and eventually + // corrupts FreeRTOS state. The advance table persists across calls per + // font, so the cost amortizes to ~ASCII-size after the first chunk. + if (renderer.isSdCardFont(cachedFontId)) { + renderer.ensureSdCardFontReady(cachedFontId, reinterpret_cast(buffer), /*styleMask=*/0x01); + } + // Parse lines from buffer size_t pos = 0; @@ -231,7 +242,7 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector break; } - int lineWidth = renderer.getTextWidth(cachedFontId, line.c_str()); + int lineWidth = renderer.getTextAdvanceX(cachedFontId, line.c_str(), EpdFontFamily::REGULAR); if (lineWidth <= viewportWidth) { outLines.push_back(line); @@ -242,7 +253,8 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector // Find break point size_t breakPos = line.length(); - while (breakPos > 0 && renderer.getTextWidth(cachedFontId, line.substr(0, breakPos).c_str()) > viewportWidth) { + while (breakPos > 0 && renderer.getTextAdvanceX(cachedFontId, line.substr(0, breakPos).c_str(), + EpdFontFamily::REGULAR) > viewportWidth) { // Try to break at space size_t spacePos = line.rfind(' ', breakPos - 1); if (spacePos != std::string::npos && spacePos > 0) { @@ -354,12 +366,12 @@ void TxtReaderActivity::renderPage() { // x already set to left margin break; case CrossPointSettings::CENTER_ALIGN: { - int textWidth = renderer.getTextWidth(cachedFontId, line.c_str()); + int textWidth = renderer.getTextAdvanceX(cachedFontId, line.c_str(), EpdFontFamily::REGULAR); x = cachedOrientedMarginLeft + (contentWidth - textWidth) / 2; break; } case CrossPointSettings::RIGHT_ALIGN: { - int textWidth = renderer.getTextWidth(cachedFontId, line.c_str()); + int textWidth = renderer.getTextAdvanceX(cachedFontId, line.c_str(), EpdFontFamily::REGULAR); x = cachedOrientedMarginLeft + contentWidth - textWidth; break; } From 8b93fb333be856e18ef63ed8b621f7f675a862df Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Fri, 15 May 2026 11:44:47 -0400 Subject: [PATCH 73/93] fix: Add documentation for USB-locked Xteink devices (#1990) Document the Xteink Unlocker tool requirement for third-party purchased xteink units that ship with USB flashing locked. Include warnings about bricking risks when flashing unsupported firmwares (e.g. Papyrix) on locked devices, as they may permanently lock the device with no recovery path. --- README.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c870a156cc..2281d768c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CrossPoint Reader -Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink). +Firmware for **Xteink** e-paper display readers (X3 and X4) (unaffiliated with Xteink). Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller. CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official @@ -11,7 +11,7 @@ Xteink firmware. It aims to match or improve upon the standard EPUB reading expe ## Motivation E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited -customisation. The **Xteink X4** is an affordable, e-paper device, however the official firmware remains closed. +customisation. **Xteink** devices (such as the X3 and X4) are affordable e-paper readers, however the official firmware remains closed. CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truly unlock the device's potential. @@ -19,7 +19,7 @@ CrossPoint Reader aims to: * Provide a **fully open-source alternative** to the official firmware. * Offer a **document reader** capable of handling EPUB content on constrained hardware. * Support **customisable font, layout, and display** options. -* Run purely on the **Xteink X4 hardware**. +* Run purely on **Xteink hardware**. This project is **not affiliated with Xteink**; it's built as a community project. @@ -49,11 +49,35 @@ See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint, For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document. +## USB-locked devices (Xteink Unlocker) + +Some Xteink units purchased from third-party stores (e.g. AliExpress) ship with USB flashing locked from the factory. +If your device is locked, you will need to use the **Xteink Unlocker** tool available at +https://crosspointreader.com/#unlock-tool before you can flash CrossPoint. + +**You do not need this tool if you bought your device directly from xteink.com.** Those units are not locked. + +**Not sure if your device is locked?** Power it on, connect the USB-C cable, and try flashing via the web flasher first +(see [Installing](#installing) below). If the browser's serial device picker does not show your device, try a different +USB port or browser before assuming the device is locked. Only reach for the unlocker if the device still doesn't appear. + +> ### ⚠️ WARNING: READ THIS BEFORE USING THE UNLOCKER ⚠️ +> +> **The only officially supported firmwares in the unlock tool are CrossPoint and CrossInk.** +> +> Flashing any other firmware on a USB-locked device may **permanently brick the device** or leave it **permanently +> stuck on that firmware with no recovery path**. Once USB flashing is re-locked, your only way back is via OTA, and if +> the firmware you flashed doesn't support OTA, **there is no way out**. +> +> **The Papyrix fork has removed OTA update support from its code.** If you flash Papyrix onto a +> USB-locked unit, you will have **zero update or recovery path** and will be stuck on it forever. **Do not flash +> Papyrix (or any other unsupported firmware) on a locked device.** + ## Installing ### Web (latest firmware) -1. Connect your Xteink X4 to your computer via USB-C and wake/unlock the device +1. Connect your Xteink device to your computer via USB-C and wake/unlock the device 2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware" To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap @@ -61,7 +85,7 @@ back to the other partition using the "Swap boot partition" button here https:// ### Web (specific firmware version) -1. Connect your Xteink X4 to your computer via USB-C +1. Connect your Xteink device to your computer via USB-C 2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases) 3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section @@ -75,7 +99,7 @@ back to the other partition using the "Swap boot partition" button here https:// pip install esptool ``` 2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases) -3. Connect your Xteink X4 to your computer via USB-C. +3. Connect your Xteink device to your computer via USB-C. 4. Note the device location. On Linux, run `dmesg` after connecting. On MacOS, run : ```bash log stream --predicate 'subsystem == "com.apple.iokit"' --info @@ -97,7 +121,7 @@ See [Development](#development) below. * **PlatformIO Core** (`pio`) or **VS Code + PlatformIO IDE** * Python 3.8+ * USB-C cable for flashing the ESP32-C3 -* Xteink X4 +* Xteink device (X3 or X4) ### Checking out the code @@ -112,7 +136,7 @@ git submodule update --init --recursive ### Flashing your device -Connect your Xteink X4 to your computer via USB-C and run the following command. +Connect your Xteink device to your computer via USB-C and run the following command. ```sh pio run --target upload @@ -191,7 +215,7 @@ principles, please see [GOVERNANCE.md](GOVERNANCE.md). --- -CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**. +CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the hardware**. Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I was making CrossPoint. From 9601a795da081dedd64463014c76172a9826cf7a Mon Sep 17 00:00:00 2001 From: Uri Tauber Date: Fri, 15 May 2026 23:38:22 +0300 Subject: [PATCH 74/93] fix: update README.md to reflect the current state of crosspoint (#1812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary As noted in [#1680](https://github.com/crosspoint-reader/crosspoint-reader/discussions/1680#discussioncomment-16661106), the README hasn't been updated in a while and has fallen behind the actual firmware. This PR brings it up to date. Beyond the feature list, I added a section acknowledging community forks worth knowing about. I also took some deliberate editorial choices around how CrossPoint is framed — I think it has the potential to be more than just "an alternative Xteink firmware", and the wording reflects that. A note on process: I wrote the bulk of the text myself, but used AI tools to scan the codebase and catch features I might have missed, and to clean up my English (I'm fluent but not a native speaker). If any line reads as unnatural or AI-sounding, please flag it — I'd rather fix it than leave it. --- One thing outside the scope of this PR: I think the cover photo could use a refresh, ideally replaced with a small gallery showing different CrossPoint screens. If you have a professional camera and an Xteink device and want to help with that, let me know. --------- Co-authored-by: Zach Nelson --- README.md | 252 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 143 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 2281d768c9..7e1c05ae8b 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,54 @@ # CrossPoint Reader -Firmware for **Xteink** e-paper display readers (X3 and X4) (unaffiliated with Xteink). -Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller. +CrossPoint is open-source e-reader firmware - community-built, fully hackable, free forever. It's maintained by a growing community of developers and readers who believe your device should do what you want - not what a manufacturer decided for you. -CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official -Xteink firmware. It aims to match or improve upon the standard EPUB reading experience. +**Now running on:** ESP32C3-based Xteink [X4](https://www.xteink.com/products/xteink-x4) and [X3](https://www.xteink.com/products/xteink-x3). -![](./docs/images/cover.jpg) +![CrossPoint Reader running on Xteink device](./docs/images/cover.jpg) -## Motivation +## What can CrossPoint do? -E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited -customisation. **Xteink** devices (such as the X3 and X4) are affordable e-paper readers, however the official firmware remains closed. -CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truly unlock the device's -potential. +- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more. -CrossPoint Reader aims to: -* Provide a **fully open-source alternative** to the official firmware. -* Offer a **document reader** capable of handling EPUB content on constrained hardware. -* Support **customisable font, layout, and display** options. -* Run purely on **Xteink hardware**. +- **Various formats**: native handling for `.epub`, `.xtc/.xtch`, `.txt`, and `.bmp`. -This project is **not affiliated with Xteink**; it's built as a community project. +- **Screenshots.** -## Features & Usage +- **Custom fonts**: install your favorite fonts on the SD card. -- [x] EPUB parsing and rendering (EPUB 2 and EPUB 3) -- [x] Image support within EPUB -- [x] Saved reading position -- [x] File explorer with file picker - - [x] Basic EPUB picker from root directory - - [x] Support nested folders - - [ ] EPUB picker with cover art -- [x] Custom sleep screen - - [x] Cover sleep screen -- [x] Wifi book upload -- [x] Wifi OTA updates -- [x] KOReader Sync integration for cross-device reading progress -- [x] Configurable font, layout, and display options - - [ ] User provided fonts - - [ ] Full UTF support -- [x] Screen rotation +- **Tilt page turn (X3 only)**. -Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages). +- **Library workflow**: folder browser, hidden-file toggle, long-press delete, recent books, SD-cache management. -See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint, including the -[KOReader Sync quick setup](./USER_GUIDE.md#367-koreader-sync-quick-setup). +- **Wireless workflows**: + + - File transfer web UI + - EPUB Optimizer + - Web settings UI/API (edit many device settings from browser) + - WebSocket fast uploads + - WebDAV handler + - AP mode (hotspot) and STA mode (join existing WiFi), both with QR helpers + - Calibre wireless connect flow + - OPDS browser with saved servers (up to 8), search, pagination, and direct download + - OTA update checks and installs from GitHub releases -For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document. +- **Customization**: multiple themes (Classic, Lyra, Lyra Extended, RoundedRaff), sleep screen modes, front/side button remapping, status bar controls, power-button behavior, refresh cadence, and more. + +- **Localization**: 22 UI languages and counting. + +### Coming soon: + +- RTL support — Arabic, Hebrew, and Farsi. + +- Bookmarks. + +- Dictionary lookup — inline word lookup without leaving the reader. + +- More themes. + +- Much more! stay tuned. + +--- ## USB-locked devices (Xteink Unlocker) @@ -57,90 +58,112 @@ https://crosspointreader.com/#unlock-tool before you can flash CrossPoint. **You do not need this tool if you bought your device directly from xteink.com.** Those units are not locked. -**Not sure if your device is locked?** Power it on, connect the USB-C cable, and try flashing via the web flasher first -(see [Installing](#installing) below). If the browser's serial device picker does not show your device, try a different +**Not sure if your device is locked?** Power it on, connect the USB-C cable, and try flashing via the web flasher first (see +[Install firmware](#install-firmware) below). If the browser's serial device picker does not show your device, try a different USB port or browser before assuming the device is locked. Only reach for the unlocker if the device still doesn't appear. > ### ⚠️ WARNING: READ THIS BEFORE USING THE UNLOCKER ⚠️ -> +> > **The only officially supported firmwares in the unlock tool are CrossPoint and CrossInk.** -> +> > Flashing any other firmware on a USB-locked device may **permanently brick the device** or leave it **permanently > stuck on that firmware with no recovery path**. Once USB flashing is re-locked, your only way back is via OTA, and if > the firmware you flashed doesn't support OTA, **there is no way out**. -> +> > **The Papyrix fork has removed OTA update support from its code.** If you flash Papyrix onto a > USB-locked unit, you will have **zero update or recovery path** and will be stuck on it forever. **Do not flash > Papyrix (or any other unsupported firmware) on a locked device.** -## Installing +## Install firmware + +### Web installer (recommended) -### Web (latest firmware) +1. Connect your device to your computer via USB-C and wake/unlock the device +2. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), and choose an official CrossPoint release. -1. Connect your Xteink device to your computer via USB-C and wake/unlock the device -2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware" +### Web installer (specific version) -To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap -back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug. +1. Connect your device to your computer via USB-C and wake/unlock the device +2. Download a `firmware.bin` from [Releases](https://github.com/crosspoint-reader/crosspoint-reader/releases), local build, or continuous integration artifact. +3. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), click "Custom .bin" and upload a `firmware.bin`. -### Web (specific firmware version) +### Revert to Official Firmware -1. Connect your Xteink device to your computer via USB-C -2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases) -3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section +To revert to the official firmware, you can also flash the latest official firmware using https://crosspointreader.com/#flash-tools. -To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap -back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug. +### Command line -### Command line (specific firmware version) +1. Install [`esptool`](https://github.com/espressif/esptool): -1. Install [`esptool`](https://github.com/espressif/esptool) : ```bash pip install esptool ``` -2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases) -3. Connect your Xteink device to your computer via USB-C. -4. Note the device location. On Linux, run `dmesg` after connecting. On MacOS, run : + +2. Download `firmware.bin` from the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases). +3. Connect your device via USB-C. +4. Find the device port. On Linux, run `dmesg` after connecting. On macOS: + ```bash log stream --predicate 'subsystem == "com.apple.iokit"' --info ``` -5. Flash the firmware : + +5. Flash: + ```bash esptool.py --chip esp32c3 --port /dev/ttyACM0 --baud 921600 write_flash 0x10000 /path/to/firmware.bin ``` -Change `/dev/ttyACM0` to the device for your system. + +Adjust `/dev/ttyACM0` to match your system. ### Manual -See [Development](#development) below. +See [Development quick start](#development-quick-start) below. -## Development +--- -### Prerequisites +## Documentation -* **PlatformIO Core** (`pio`) or **VS Code + PlatformIO IDE** -* Python 3.8+ -* USB-C cable for flashing the ESP32-C3 -* Xteink device (X3 or X4) +- [User Guide](./USER_GUIDE.md) +- [Web server usage](./docs/webserver.md) +- [Web server endpoints](./docs/webserver-endpoints.md) +- [Project scope](./SCOPE.md) +- [Contributing docs](./docs/contributing/README.md) -### Checking out the code +--- -CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository: +## Development quick start -``` +### Prerequisites + +- [pioarduino](https://github.com/pioarduino/pioarduino) or VS Code + pioarduino plugin +- Python 3.8+ +- `clang-format` 21 +- USB-C cable supporting data transfer + +### Setup + +```bash git clone --recursive https://github.com/crosspoint-reader/crosspoint-reader +cd crosspoint-reader -# Or, if you've already cloned without --recursive: +# if cloned without --recursive: git submodule update --init --recursive ``` -### Flashing your device - -Connect your Xteink device to your computer via USB-C and run the following command. +### Build / flash / monitor -```sh +```bash pio run --target upload ``` + +### Contributor pre-PR checks + +```bash +./bin/clang-format-fix +pio check -e default +pio run -e default +``` + ### Debugging After flashing the new features, it’s recommended to capture detailed logs from the serial port. @@ -150,7 +173,9 @@ First, make sure all required Python packages are installed: ```python python3 -m pip install pyserial colorama matplotlib ``` -after that run the script: + +After that run the script: + ```sh # For Linux # This was tested on Debian and should work on most Linux systems. @@ -159,63 +184,72 @@ python3 scripts/debugging_monitor.py # For macOS python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101 ``` + Minor adjustments may be required for Windows. +--- + ## Internals -CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only -has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based -on this constraint. +CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based on this constraint. ### Data caching The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows: - -``` +```text .crosspoint/ -├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_` -│ ├── progress.bin # Stores reading progress (chapter, page, etc.) -│ ├── cover.bmp # Book cover image (once generated) -│ ├── book.bin # Book metadata (title, author, spine, table of contents, etc.) -│ └── sections/ # All chapter data is stored in the sections subdirectory -│ ├── 0.bin # Chapter data (screen count, all text layout info, etc.) -│ ├── 1.bin # files are named by their index in the spine +├── epub_/ # one directory per book, named by content hash +│ ├── progress.bin # reading position (chapter, page, etc.) +│ ├── cover.bmp # generated cover image +│ ├── book.bin # metadata: title, author, spine, TOC +│ └── sections/ # per-chapter layout cache +│ ├── 0.bin +│ ├── 1.bin │ └── ... -│ -└── epub_189013891/ ``` -Deleting the `.crosspoint` directory will clear the entire cache. - -Due the way it's currently implemented, the cache is not automatically cleared when a book is deleted and moving a book -file will use a new cache directory, resetting the reading progress. +Removing `/.crosspoint` clears all cached metadata and forces a full regeneration on next open. Note: the cache isn't cleared automatically when you delete a book, and moving a file to a new path resets its reading progress. For more details on the internal file structures, see the [file formats document](./docs/file-formats.md). +--- + ## Contributing -Contributions are very welcome! +Contributions are welcome. If you're new to the codebase, start with the [contributing docs](./docs/contributing/README.md). For things to work on, check the [ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas) — leave a comment before starting so we don't duplicate effort. + +Everyone here is a volunteer, so please be respectful and patient. For governance and community expectations, see [GOVERNANCE.md](./GOVERNANCE.md). + +--- + +## Community forks + +One of the best things about open source is that anyone can take the code in a different direction. If you need something outside CrossPoint's [scope](./SCOPE.md), check out the community forks: + +- [CrossInk](https://github.com/uxjulia/CrossInk) — Typography and reading tracking: Bionic Reading (bolds word stems to create fixation points), guide dots between words, improved paragraph indents, and replaces the default fonts with ChareInk/Lexend/Bitter. + +- [papyrix-reader](https://github.com/bigbag/papyrix-reader) — Adds FB2 and MD format support. Actively maintained with Arabic script support. Custom themes via SD card. + +- [crosspet](https://github.com/trilwu/crosspet) — A Vietnamese fork that adds a Tamagotchi-style virtual chicken that grows based on your reading milestones (pages read, streaks, care). Also: Flashcards, Weather, Pomodoro timer, and mini-games. + +- [crosspoint-reader (jpirnay)](https://github.com/jpirnay/crosspoint-reader) — Faster integration of functionality. Tracks upstream PRs and integrates the good ones ahead of the official merge. + +- [crosspoint-reader-cjk](https://github.com/aBER0724/crosspoint-reader-cjk) — Purpose-built for Chinese, Japanese, and Korean reading. -If you are new to the codebase, start with the [contributing docs](./docs/contributing/README.md). +- [inx](https://github.com/obijuankenobiii/inx) — Completely reimagines the user interface with tabbed navigation. -If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas). -If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort. +- ~~[PlusPoint](https://github.com/ngxson/pluspoint-reader) — custom JS apps support.~~ (Unmaintained) -Everyone here is a volunteer, so please be respectful and patient. For more details on our governance and community -principles, please see [GOVERNANCE.md](GOVERNANCE.md). +- [crosspoint-reader-papers3](https://github.com/juicecultus/crosspoint-reader-papers3) — Crosspoint port for M5Stack Paper S3. -### To submit a contribution: +**Note:** Many of these features will make their way into CrossPoint over time. We maintain a slower pace to ensure rock-solid stability and squash bugs before they reach your device. -1. Fork the repo -2. Create a branch (`feature/dithering-improvement`) -3. Make changes -4. Submit a PR +Want to build your own device? Be sure to check out the [de-link](https://github.com/iandchasse/de-link) project. --- -CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the hardware**. +CrossPoint Reader is **not affiliated with Xteink or any device manufacturer**. -Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I -was making CrossPoint. +Huge shoutout to [diy-esp32-epub-reader](https://github.com/atomic14/diy-esp32-epub-reader), which inspired this project. From 49bffae6f0a11ed445d8bc6dade72ef6bbc0cf5c Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Fri, 15 May 2026 15:51:43 -0500 Subject: [PATCH 75/93] chore: Update version to 1.3.0 (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This release adds SD card fonts — the most-requested feature since launch — brings the X3 to first-class status, redesigns the on-screen keyboard, overhauls OPDS, and ships SD-card firmware updates. 144 changes from 53 contributors, 32 of whom are new to the project. **🔠 SD Card Fonts** Custom fonts are here. A complete font subsystem lets you install and use fonts beyond the three built-in families. A new `.cpfont` binary format packs multiple styles (regular, bold, italic, bold-italic) into a single file per size, with on-demand glyph loading from the SD card. A two-pass prewarm renderer bulk-reads glyphs per page, achieving near-flash performance for Latin text and viable CJK rendering. Fonts can be downloaded over WiFi directly from the device, uploaded via the web interface, or copied manually to the SD card. The build pipeline ships a 17-family font library (serif, sans, mono, accessibility) with CI distribution via a dedicated crosspoint-fonts repository. As a bonus, CJK characters no longer get spurious hyphens at line breaks, and an advance-table cache eliminates 30+ second stalls during CJK section indexing. **📱 X3 Comes of Age** The X3 graduates from initial bring-up to a proper target. Grayscale antialiasing is sharper, EPUB images render correctly, OTA updates work, and sleep screen dimensions are dialed in. The headline addition: gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device to turn pages hands-free. SD-card firmware update support and X3 bootloader compatibility mean users can update without a USB connection. **⌨️ Redesigned On-Screen Keyboard** The keyboard has been completely redesigned with improved layout, better key feedback, and a fix for the space key barely moving the cursor. Text entry across WiFi setup, OPDS search, and KOSync login is noticeably smoother. **👁️ Focus Reading** A new reading mode bolds the initial characters of each word (similar to Bionic Reading) to create artificial fixation points, helping improve reading speed and focus. The bolding ratio is 45%, with a minimum of 1 character and a maximum of 9, applied dynamically during indexing. **📚 OPDS Overhaul** OPDS gains in-catalog search with next/prev page navigation, support for multiple servers, correct handling of relative paths and query parameters (fixing CopyParty compatibility), and KOReader-compatible download filenames. **🔤 Text Rendering Refinements** Combining marks (diacritics) now use font metrics for positioning instead of heuristics, proportional numeral spacing is supported, and differential rounding eliminates uneven inter-glyph gaps. Hyphenation now recognizes ISO 639-2 language codes, nested block-level CSS styles are tracked correctly, and horizontal CSS insets are capped at 2em to prevent runaway margins. Bookerly has been replaced with Noto Serif for licensing reasons. **🎨 New Theme: RoundedRaff** A new rounded theme joins the theme picker, with fixes for sleep cover crop grid artifacts. **🔋 Battery & Power** Battery percentage smoothing on the X4 eliminates jittery readings. A short press on the power button can be set to trigger a manual screen refresh — handy for clearing ghosting. **📶 WiFi & Networking** WiFi connections now self-heal from transient drops without manual intervention, and a dBm signal strength indicator appears during web server sessions. WiFi networks can be edited directly from the web UI. **🔄 KOSync** Reading position sync is significantly more accurate. The old character-offset approach frequently landed on the wrong paragraph after syncing between devices — the new xpath-based mapping syncs at the paragraph level, matching KOReader's own behavior. A separate fix switches the HTTP layer to `esp_http_client`, and the reader now releases ~65KB of EPUB heap before the TLS handshake — together these eliminate the out-of-memory crashes that plagued KOSync on large books. **🛡️ Stability** Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP handling fixed, boot loops with large XTC files eliminated, legacy XTC headers supported, the OTA updater now streams GitHub release JSON instead of buffering it in RAM, and a JPEG downscaler y-axis scale factor bug is corrected. **🌐 Languages** Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish translations received significant updates. --- Also in this release: **SD-card firmware updates without USB**, **file extensions in the file browser**, **full path bar navigation**, **end-of-book navigation improvements**, **XTC status bar**, **smarter "Cover + Custom" sleep screens**, **set sleep cover from the BMP viewer**, **orientation-aware popups**, **page turn buttons that follow orientation**, **long-press delete for directories**, **context-aware screenshot filenames with book title**, **crash reason displayed on boot**, **empty line rendering in the TXT reader**, **wallpaper recency buffer to prevent clustering**, **font family deletion from the device**, **next/prev labels in the BMP viewer**, **non-breaking space justification fix**, **README guidance for USB-locked third-party Xteink units**, and a long tail of web UI polish, i18n memory optimizations, and code quality improvements. ## What's Changed ### Features * feat: add SD card font support with on-device download and web management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and @mcrosson * feat: Initial support for the x3 by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/875 * feat: X3 grayscale antialiasing improvements by @juicecultus in https://github.com/crosspoint-reader/crosspoint-reader/pull/1607 * feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by @juicecultus in https://github.com/crosspoint-reader/crosspoint-reader/pull/1636 * feat(update): SD-card firmware update + X3 bootloader compatibility by @eunchurn in https://github.com/crosspoint-reader/crosspoint-reader/pull/1786 * feat: self-heal from transient WiFi loss, add dBm indicator during WebServerActivity by @jeremydk in https://github.com/crosspoint-reader/crosspoint-reader/pull/1780 * feat: edit wifi networks in webui by @osteotek in https://github.com/crosspoint-reader/crosspoint-reader/pull/1743 * feat: add OPDS search support & next/prev page navigation by @rxmmah in https://github.com/crosspoint-reader/crosspoint-reader/pull/1462 * feat: Support for multiple OPDS servers by @osteotek in https://github.com/crosspoint-reader/crosspoint-reader/pull/1209 * feat: Adjust Navigation at End of Book by @nscheung in https://github.com/crosspoint-reader/crosspoint-reader/pull/1425 * feat: Display file extensions in File Browser by @CaptainFrito in https://github.com/crosspoint-reader/crosspoint-reader/pull/1019 * feat: show full path bar in file browser by @zgredex in https://github.com/crosspoint-reader/crosspoint-reader/pull/1411 * feat: enable manual screen refresh on power button short press by @bdeshi in https://github.com/crosspoint-reader/crosspoint-reader/pull/1626 * feat: Rework "Cover + Custom" sleep screens to show covers only when currently reading by @iandchasse in https://github.com/crosspoint-reader/crosspoint-reader/pull/1256 * feat: Set sleep cover from BMP viewer by @el in https://github.com/crosspoint-reader/crosspoint-reader/pull/1104 * feat: show crash reason on boot by @ngxson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1453 * feat: Support for proportional numeral spacing by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1414 * feat: add orientation-aware popups for reader activities by @mrtnvgr in https://github.com/crosspoint-reader/crosspoint-reader/pull/1428 * feat: smooth battery percentage for x4 by @jonvex in https://github.com/crosspoint-reader/crosspoint-reader/pull/1635 * feat: context-aware screenshot filenames with book title by @jonstieglitz in https://github.com/crosspoint-reader/crosspoint-reader/pull/1589 * feat(theme): add roundedraff theme and fix sleep cover crop grid artifacts by @bunsoootchi in https://github.com/crosspoint-reader/crosspoint-reader/pull/918 * feat: Page turn button orientation change by @mchuck in https://github.com/crosspoint-reader/crosspoint-reader/pull/1069 * feat: Status bar for XTC files by @leecming82 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1849 * feat: enhance long press action to delete both files and directories by @WuTofu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1803 * feat: Added Slovenian translation by @thehijacker in https://github.com/crosspoint-reader/crosspoint-reader/pull/1551 * feat: focus reading by @vjapolitzer in https://github.com/crosspoint-reader/crosspoint-reader/pull/1670 * feat: add next / prev labels to bmp viewer by @Telemaniaka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1852 * feat: add font family deletion functionality by @WuTofu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1919 * feat: separate into "Download All" and "Update All" in font manager by @WuTofu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1955 * feat: verify CRC32 checksum for font files by @WuTofu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1904 * feat: increase default weight of Bitter font for improved rendering by @uxjulia in https://github.com/crosspoint-reader/crosspoint-reader/pull/1922 * feat: allow unnamed intervals by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1903 ### Fixes * fix: epub images not rendering correctly on x3 by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1572 * fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1805 * fix: boot looping when opening large XTC files by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1648 * fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1627 * fix: two small memory leaks by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1628 * fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by @trilwu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1381 * fix: Read GH release JSON as stream in OTA updater by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1810 * fix: support legacy XTC file headers where pageTableOffset=48 by @uxjulia in https://github.com/crosspoint-reader/crosspoint-reader/pull/1816 * fix: Use font metrics for combining mark positioning by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1310 * fix: Use differential rounding for consistent inter-glyph spacing by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1413 * fix: Support hyphenation for EPUBs using ISO 639-2 language codes by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1461 * fix: Track block style stack for nested styles by @daveallie in https://github.com/crosspoint-reader/crosspoint-reader/pull/1582 * fix: cap per-side horizontal CSS inset at 2em by @rhoopr in https://github.com/crosspoint-reader/crosspoint-reader/pull/1694 * fix: increase loadable epub size by @CSCMe in https://github.com/crosspoint-reader/crosspoint-reader/pull/1638 * fix: Switch to xpath map for paragraph level syncing in KOSync by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1686 * fix: free Epub RAM and simplify KOSync navigation via ActivityManager by @wylanswets in https://github.com/crosspoint-reader/crosspoint-reader/pull/1860 * fix: improve KOSync bidirectional position matching accuracy by @wylanswets in https://github.com/crosspoint-reader/crosspoint-reader/pull/1897 * fix: Fix failing very first wifi connection attempt by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1521 * fix: avoid skipping chapter after screenshot by @Mraulio in https://github.com/crosspoint-reader/crosspoint-reader/pull/1625 * fix: back navigation from BMPViewer by @Telemaniaka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1597 * fix: Fix ghosting on exit of BMPViewer by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1432 * fix: make footnotes consider orientation for gutters by @Telemaniaka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1665 * fix: footnote link text by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1666 * fix: Erroneous navigation with long filenames in footnote links by @CSCMe in https://github.com/crosspoint-reader/crosspoint-reader/pull/1723 * fix: prevent wallpaper clustering with 16-entry recency buffer by @zgredex in https://github.com/crosspoint-reader/crosspoint-reader/pull/1606 * fix: webserver /delete API backward compatibility by @DianaNites in https://github.com/crosspoint-reader/crosspoint-reader/pull/1475 * fix: relative opds paths and query param with copyparty by @philips in https://github.com/crosspoint-reader/crosspoint-reader/pull/1535 * fix: use same file name as KOReader for OPDS downloads by @spfenwick in https://github.com/crosspoint-reader/crosspoint-reader/pull/1286 * fix: pressing space barely moves input cursor (#1729) by @pablohc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1733 * fix: keyboard feedback #1644 by @pablohc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1697 * fix: pluralize folder/file counts correctly in file list summary by @fain182 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1701 * fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1814 * fix: two roundedraff bugs by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1851 * fix: overlap in download font list layout by @pablohc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1900 * fix: remove duplicate 'Download Fonts' menu entry and improve navigation by @zgredex in https://github.com/crosspoint-reader/crosspoint-reader/pull/1893 * fix: Add common ligatures to SD font conversion ranges by @znelson * fix: capture instantiateVariableFont return value by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1911 * fix: Roundraff theme home menu offset with no recent books by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1845 * fix: Missing navigation button labels in Roundedraff theme by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1905 * fix: gracefully resolve fonts missing variants by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1921 * fix: distribute justifyExtra to non-breaking space tokens by @prawnwhoyawns in https://github.com/crosspoint-reader/crosspoint-reader/pull/1783 * fix: remove percent rendering from activities by @mcrosson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1901 * fix: Restore performance in fontconvert_sdcard.py by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1924 * fix: Prepare SD card font caches from txt reader by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1973 * fix: make script help paths lightweight by @sabraman in https://github.com/crosspoint-reader/crosspoint-reader/pull/1937 * fix: Replaced Bookerly with Noto Serif for licensing reasons by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1736 * fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler by @WuTofu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1807 * fix: display empty lines in txt reader by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1841 * fix: short-press power action triggered after screenshot combo release by @pablohc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1853 * fix: correct Russian auto-turn translations by @a-ignatev in https://github.com/crosspoint-reader/crosspoint-reader/pull/1566 * fix: Update Ukrainian translations for footnotes (issue 1409) by @mirus-ua in https://github.com/crosspoint-reader/crosspoint-reader/pull/1585 * fix: missing swedish translations by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1667 * fix: Add swedish keyboard translations by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1726 * fix: swedish translations by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1762 * fix: swedish translation by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1829 * fix: swedish translation by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1888 * fix: Polish translation by @th0m4sek in https://github.com/crosspoint-reader/crosspoint-reader/pull/1909 * fix: Ukrainian-translation by @KymAndriy in https://github.com/crosspoint-reader/crosspoint-reader/pull/1946 * fix: Ukrainian translation by @KymAndriy in https://github.com/crosspoint-reader/crosspoint-reader/pull/1939 * fix: python requirements files by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1768 * fix: missing requirement by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1896 * fix: Use LOG_ macros in loc functions by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1794 ### Internal * refactor: redesign on-screen keyboard by @pablohc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1644 * refactor: replace picojpeg with JPEGDEC for cover art conversion by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1517 * refactor: Refactor drawArc / fillArc for faster execution by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1540 * perf: replace i18n pointer tables with offset tables, strip unused strings by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1408 * refactor: Store only unique localization strings in offset buffers by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1802 * refactor: Move language setting into JSON settings by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1796 * refactor: Use C++20 'requires' in ActivityResult constructor by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1420 * refactor: Use default member initializers for JpegContext and PngContext by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1435 * refactor: logPrintf and predefined log level strings by @CSCMe in https://github.com/crosspoint-reader/crosspoint-reader/pull/1546 * refactor: RAII scoped open/close for ZipFile by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1433 * refactor: Deduplicated BMP header writing in Xtc by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1439 * refactor: Added shared XML parser teardown helper by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1438 * refactor: Removed redundant FsFile close() calls by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1434 * refactor: Deduplicate battery drawing code and fix Lyra charging indicator by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1437 * refactor: Deduplicate Roundraff battery drawing by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1847 * refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1817 * refactor: Avoid vector for page turn rates list by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1818 * refactor: Use std::size instead of sizeof/sizeof by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1819 * refactor: Use fixed-size integers for BookMetadataCache data by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1844 * refactor: Simplify isReaderActivity bookkeeping by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1838 * refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1837 * refactor: change ukrainian translation to adaptation and add missing lines by @KymAndriy in https://github.com/crosspoint-reader/crosspoint-reader/pull/1828 * chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in https://github.com/crosspoint-reader/crosspoint-reader/pull/1465 * chore: clang-format.fix.ps1 script: Add .venv to list of path exclusions by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1515 * chore: Updating sleep screen dimensions for X3 by @jensechu in https://github.com/crosspoint-reader/crosspoint-reader/pull/1688 * chore: Clarify X3 RTC in SCOPE.md by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1687 * chore: Improved Italian translations by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1685 * chore: change ukrainian translation to adaptation by @KymAndriy in https://github.com/crosspoint-reader/crosspoint-reader/pull/1684 * chore: Update spanish.yaml by @mvidelatraduc in https://github.com/crosspoint-reader/crosspoint-reader/pull/1717 * chore: One Italian translation tweak by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1718 * chore: git pre-commit hook for format fix by @osteotek in https://github.com/crosspoint-reader/crosspoint-reader/pull/1730 * chore: Update SDK to fork in CrossPoint org by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1836 * chore: Added RAM to firmware_size_history.py script by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1830 * chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1878 * feat: cap compressed group size at 64 KB by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1913 * fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay in https://github.com/crosspoint-reader/crosspoint-reader/pull/1910 * feat: include short SHA in CROSSPOINT_VERSION by @osteotek in https://github.com/crosspoint-reader/crosspoint-reader/pull/1728 * feat: show long branch names by @steka in https://github.com/crosspoint-reader/crosspoint-reader/pull/1727 * feat: enable pio build cache by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1769 * style: put page name first in browser titles by @fain182 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1703 * style: unify page headers across web UI by @fain182 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1702 * style: move file type badges into Type column by @fain182 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1793 * style: align action buttons vertically with page title by @fain182 in https://github.com/crosspoint-reader/crosspoint-reader/pull/1795 * docs: Update README with firmware flashing instructions by @ryneches in https://github.com/crosspoint-reader/crosspoint-reader/pull/1654 * docs: fix typos by @kianmeng in https://github.com/crosspoint-reader/crosspoint-reader/pull/1705 * docs: update README.md to reflect the current state of crosspoint by @Uri-Tauber in https://github.com/crosspoint-reader/crosspoint-reader/pull/1812 * docs: Add documentation for USB-locked Xteink devices by @itsthisjustin in https://github.com/crosspoint-reader/crosspoint-reader/pull/1990 * docs: expand first use of OPDS acronym and provide a wikipedia link by @sizezero in https://github.com/crosspoint-reader/crosspoint-reader/pull/1824 * docs: fix KOReader sync guide link by @sabraman in https://github.com/crosspoint-reader/crosspoint-reader/pull/1930 * docs: fix hyphenation updater script name by @sabraman in https://github.com/crosspoint-reader/crosspoint-reader/pull/1931 * fix: sd font download urls in docs by @mcrosson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1945 * fix: sd font folder paths in documentation by @mcrosson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1944 * chore: Add verbose mode to build-sd-fonts.py by @znelson in https://github.com/crosspoint-reader/crosspoint-reader/pull/1923 ## New Contributors * @a-ignatev made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1566 * @CSCMe made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1546 * @thehijacker made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1551 * @Telemaniaka made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1597 * @Mraulio made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1625 * @rxmmah made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1462 * @bdeshi made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1626 * @DianaNites made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1475 * @ryneches made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1654 * @zgredex made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1411 * @jonvex made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1635 * @KymAndriy made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1684 * @jensechu made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1688 * @kianmeng made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1705 * @philips made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1535 * @fain182 made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1701 * @mvidelatraduc made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1717 * @bunsoootchi made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/918 * @rhoopr made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1694 * @spfenwick made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1286 * @trilwu made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1381 * @jonstieglitz made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1589 * @uxjulia made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1816 * @mchuck made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1069 * @sizezero made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1824 * @leecming82 made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1849 * @jeremydk made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1780 * @WuTofu made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1803 * @wylanswets made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1860 * @sabraman made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1930 * @prawnwhoyawns made their first contribution in https://github.com/crosspoint-reader/crosspoint-reader/pull/1783 * @mcrosson made their first contribution as co-author on SD card font support **Full Changelog**: https://github.com/crosspoint-reader/crosspoint-reader/compare/1.2.0...release/1.3.0 --------- Co-authored-by: Justin Mitchell Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com> Co-authored-by: Uri Tauber --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9d14b7145a..39a526d099 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ build_cache_dir = .cache extra_configs = platformio.local.ini [crosspoint] -version = 1.2.0 +version = 1.3.0 [base] platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip From f8c7dfc2a20ac34ace90d3c040c50c46c3d6b2e9 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Fri, 15 May 2026 17:27:54 -0700 Subject: [PATCH 76/93] fix: silent-reboot on wifi activity exit to clear heap fragmentation (#1908) WiFi/LWIP/netif teardown scatters long-lived allocations across the heap, leaving ~50KB of contiguous space unrecoverable without a reboot. Reboot the SoC on exit from any wifi-using activity to guarantee a clean heap. An RTC_NOINIT flag survives the reboot and tells setup() to skip the boot splash and route the user back where they came from: - File transfer / Calibre / OPDS / Font download -> home - KOReader sync -> currently-open EPUB Activities check WiFi.getMode() before rebooting, so backing out of the network mode menu without joining doesn't trigger a cycle. KOSync also esp_wifi_stop()s after the sync result so the radio is off while the user reads it; full teardown happens at the reboot. ## Additional Context The silent reboot skips the booting splash screen - it visibly looks like a screen refresh. This does cause a disconnection/reconnection blip for developers actively pulling logs over serial, but `pio device monitor` and the like successfully reconnect and feed in the early boot serial. as an example: ``` [256676] [DBG] [ACT] Exiting activity: KOReaderSync [256706] [DBG] [MAIN] Silent restart (target=reader) ESP-ROM:esp32c3-api1-20210207 Build:Feb 7 2021 rst:0xc (RTC_SW_CPU_RST),boot:0xf (SPI_FAST_FLASH_BOOT) Saved PC:0x403872bc SPIWP:0xee mode:DIO, clock div:1 load:0x3fcd72a0,len:0x990 load:0x403cbf10,len:0xac8 load:0x403ce710,len:0x4d28 entry 0x403cbf10 [22] [INF] [MAIN] Hardware detect: X4 [29] [SD] SD card detected [43] [DBG] [CPS] Settings loaded from file [58] [DBG] [KRS] Loaded KOReader credentials for user: jeremydk [69] [DBG] [OPS] Loaded 1 OPDS servers from file [69] [DBG] [UI] Using Lyra theme [70] [DBG] [MAIN] Starting CrossPoint version 1.2.0-dev-detached-bde75787 ... [203] [DBG] [ACT] Entering activity: Reader [211] [DBG] [EBP] Loading ePub: /Halting State - Charles Stross.epub [221] [DBG] [BMC] Loaded cache data: 51 spine, 41 TOC entries [246] [DBG] [CSS] Loaded 41 rules from cache [247] [DBG] [EBP] Loaded ePub: /Halting State - Charles Stross.epub ``` --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _PARTIALLY_ --- src/SilentRestart.h | 8 ++++ .../browser/OpdsBookBrowserActivity.cpp | 11 +++-- .../network/CalibreConnectActivity.cpp | 14 +++--- .../network/CrossPointWebServerActivity.cpp | 48 ++++--------------- .../network/CrossPointWebServerActivity.h | 1 - .../reader/KOReaderSyncActivity.cpp | 25 +++++----- src/activities/reader/KOReaderSyncActivity.h | 6 +++ .../settings/FontDownloadActivity.cpp | 11 +++-- src/main.cpp | 45 ++++++++++++++++- 9 files changed, 102 insertions(+), 67 deletions(-) create mode 100644 src/SilentRestart.h diff --git a/src/SilentRestart.h b/src/SilentRestart.h new file mode 100644 index 0000000000..f94345c5ff --- /dev/null +++ b/src/SilentRestart.h @@ -0,0 +1,8 @@ +#pragma once + +// ESP.restart() with an RTC_NOINIT flag that survives the reboot, so setup() +// skips the boot splash and routes straight to a destination. Used to clear +// heap fragmentation accumulated during a wifi session. + +void silentRestart(); // home screen +void silentRestartToReader(); // currently-open EPUB (APP_STATE.openEpubPath) diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index 96e4e28c01..b1d5a8d42d 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -8,6 +8,7 @@ #include #include "MappedInputManager.h" +#include "SilentRestart.h" #include "activities/network/WifiSelectionActivity.h" #include "activities/util/KeyboardEntryActivity.h" #include "components/UITheme.h" @@ -40,9 +41,14 @@ void OpdsBookBrowserActivity::onEnter() { void OpdsBookBrowserActivity::onExit() { Activity::onExit(); - WiFi.mode(WIFI_OFF); entries.clear(); navigationHistory.clear(); + + if (WiFi.getMode() != WIFI_MODE_NULL) { + WiFi.disconnect(false); + delay(30); + silentRestart(); + } } void OpdsBookBrowserActivity::loop() { @@ -364,8 +370,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) { requestUpdate(true); fetchFeed(currentPath); } else { - WiFi.disconnect(); - WiFi.mode(WIFI_OFF); + // Leave WiFi up; onExit's silent reboot handles teardown without fragmenting. state = BrowserState::ERROR; errorMessage = tr(STR_WIFI_CONN_FAILED); requestUpdate(); diff --git a/src/activities/network/CalibreConnectActivity.cpp b/src/activities/network/CalibreConnectActivity.cpp index a42ab16e2e..ca8b51992a 100644 --- a/src/activities/network/CalibreConnectActivity.cpp +++ b/src/activities/network/CalibreConnectActivity.cpp @@ -7,6 +7,7 @@ #include #include "MappedInputManager.h" +#include "SilentRestart.h" #include "WifiSelectionActivity.h" #include "components/UITheme.h" #include "fontIds.h" @@ -51,14 +52,11 @@ void CalibreConnectActivity::onEnter() { void CalibreConnectActivity::onExit() { Activity::onExit(); - stopWebServer(); - MDNS.end(); - - delay(50); - WiFi.disconnect(false); - delay(30); - WiFi.mode(WIFI_OFF); - delay(30); + if (WiFi.getMode() != WIFI_MODE_NULL) { + WiFi.disconnect(false); + delay(30); + silentRestart(); + } } void CalibreConnectActivity::onWifiSelectionComplete(const bool connected) { diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index 71f1bf4137..44ccfb5bcb 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -11,6 +11,7 @@ #include "MappedInputManager.h" #include "NetworkModeSelectionActivity.h" +#include "SilentRestart.h" #include "WifiSelectionActivity.h" #include "activities/network/CalibreConnectActivity.h" #include "components/UITheme.h" @@ -75,36 +76,16 @@ void CrossPointWebServerActivity::onExit() { state = WebServerActivityState::SHUTTING_DOWN; - // Stop the web server first (before disconnecting WiFi) - stopWebServer(); - - // Stop mDNS - MDNS.end(); - - // Stop DNS server if running (AP mode) - if (dnsServer) { - LOG_DBG("WEBACT", "Stopping DNS server..."); - dnsServer->stop(); - delete dnsServer; - dnsServer = nullptr; - } - - // Brief wait for LWIP stack to flush pending packets - delay(50); - - // Disconnect WiFi gracefully - if (isApMode) { - LOG_DBG("WEBACT", "Stopping WiFi AP..."); - WiFi.softAPdisconnect(true); - } else { - LOG_DBG("WEBACT", "Disconnecting WiFi (graceful)..."); - WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame + // Skip reboot if WiFi was never activated (e.g. user backed out of mode selection). + if (WiFi.getMode() != WIFI_MODE_NULL) { + if (isApMode) { + WiFi.softAPdisconnect(true); + } else { + WiFi.disconnect(false); + } + delay(30); + silentRestart(); } - delay(30); // Allow disconnect frame to be sent - - LOG_DBG("WEBACT", "Setting WiFi mode OFF..."); - WiFi.mode(WIFI_OFF); - delay(30); // Allow WiFi hardware to power down LOG_DBG("WEBACT", "Free heap at onExit end: %d bytes", ESP.getFreeHeap()); } @@ -270,15 +251,6 @@ void CrossPointWebServerActivity::startWebServer() { } } -void CrossPointWebServerActivity::stopWebServer() { - if (webServer && webServer->isRunning()) { - LOG_DBG("WEBACT", "Stopping web server..."); - webServer->stop(); - LOG_DBG("WEBACT", "Web server stopped"); - } - webServer.reset(); -} - void CrossPointWebServerActivity::loop() { // Handle different states if (state == WebServerActivityState::SERVER_RUNNING) { diff --git a/src/activities/network/CrossPointWebServerActivity.h b/src/activities/network/CrossPointWebServerActivity.h index bc685c6b1c..557bf4437e 100644 --- a/src/activities/network/CrossPointWebServerActivity.h +++ b/src/activities/network/CrossPointWebServerActivity.h @@ -59,7 +59,6 @@ class CrossPointWebServerActivity final : public Activity { void onWifiSelectionComplete(bool connected); void startAccessPoint(); void startWebServer(); - void stopWebServer(); public: explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index 9643e14a8a..834c2e6f5b 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,7 @@ #include "KOReaderDocumentId.h" #include "MappedInputManager.h" #include "ReaderUtils.h" +#include "SilentRestart.h" #include "activities/ActivityManager.h" #include "activities/network/WifiSelectionActivity.h" #include "components/UITheme.h" @@ -47,15 +49,6 @@ void syncTimeWithNTP() { LOG_DBG("KOSync", "NTP sync timeout, using fallback"); } } -void wifiOff() { - if (esp_sntp_enabled()) { - esp_sntp_stop(); - } - WiFi.disconnect(false); - delay(100); - WiFi.mode(WIFI_OFF); - delay(100); -} } // namespace void KOReaderSyncActivity::ensureEpubLoaded() { @@ -269,8 +262,10 @@ void KOReaderSyncActivity::performUpload() { const auto result = KOReaderSyncClient::updateProgress(progress); + // Drop the radio while user reads the result; full teardown happens at silent reboot. + esp_wifi_stop(); + if (result != KOReaderSyncClient::OK) { - wifiOff(); { RenderLock lock(*this); state = SYNC_FAILED; @@ -280,7 +275,6 @@ void KOReaderSyncActivity::performUpload() { return; } - wifiOff(); { RenderLock lock(*this); state = UPLOAD_COMPLETE; @@ -299,6 +293,9 @@ void KOReaderSyncActivity::onEnter() { return; } + // Past this point every path uses WiFi. + wifiActivated = true; + // Check if already connected (e.g. from settings page auth) if (WiFi.status() == WL_CONNECTED) { LOG_DBG("KOSync", "Already connected to WiFi"); @@ -315,7 +312,11 @@ void KOReaderSyncActivity::onEnter() { void KOReaderSyncActivity::onExit() { Activity::onExit(); - wifiOff(); + if (wifiActivated) { + WiFi.disconnect(false); + delay(30); + silentRestartToReader(); + } } void KOReaderSyncActivity::render(RenderLock&&) { diff --git a/src/activities/reader/KOReaderSyncActivity.h b/src/activities/reader/KOReaderSyncActivity.h index 1f07e4f7ec..7cc824ba2f 100644 --- a/src/activities/reader/KOReaderSyncActivity.h +++ b/src/activities/reader/KOReaderSyncActivity.h @@ -78,6 +78,12 @@ class KOReaderSyncActivity final : public Activity { // Selection in result screen (0=Apply, 1=Upload) int selectedOption = 0; + // Tracks whether this session activated WiFi. Set in onEnter past the credentials + // check; checked in onExit to decide whether to silent-reboot. Can't rely on + // WiFi.getMode() because performUpload() calls esp_wifi_stop() on the way out, + // which makes WiFi.getMode() return WIFI_MODE_NULL. + bool wifiActivated = false; + void onWifiSelectionComplete(bool success); void performSync(); void performUpload(); diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index 27abc5758e..ab9bf50bb5 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -10,6 +10,7 @@ #include "MappedInputManager.h" #include "SdCardFontSystem.h" +#include "SilentRestart.h" #include "activities/network/WifiSelectionActivity.h" #include "activities/util/ConfirmationActivity.h" #include "components/UITheme.h" @@ -30,10 +31,12 @@ void FontDownloadActivity::onEnter() { void FontDownloadActivity::onExit() { Activity::onExit(); - WiFi.disconnect(false); - delay(100); - WiFi.mode(WIFI_OFF); - delay(100); + + if (WiFi.getMode() != WIFI_MODE_NULL) { + WiFi.disconnect(false); + delay(30); + silentRestart(); + } } void FontDownloadActivity::onWifiSelectionComplete(const bool success) { diff --git a/src/main.cpp b/src/main.cpp index 5f679d6313..4a220843a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -131,6 +131,29 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); unsigned long t1 = 0; unsigned long t2 = 0; +// Definitions for SilentRestart.h. RTC_NOINIT survives ESP.restart() but not power loss. +RTC_NOINIT_ATTR uint32_t silentRebootMagic; +RTC_NOINIT_ATTR uint32_t silentRebootTarget; +constexpr uint32_t SILENT_REBOOT_MAGIC = 0xC1EAB007; +constexpr uint32_t SILENT_REBOOT_TARGET_HOME = 0; +constexpr uint32_t SILENT_REBOOT_TARGET_READER = 1; + +void silentRestart() { + silentRebootTarget = SILENT_REBOOT_TARGET_HOME; + silentRebootMagic = SILENT_REBOOT_MAGIC; + LOG_DBG("MAIN", "Silent restart (target=home)"); + delay(50); + ESP.restart(); +} + +void silentRestartToReader() { + silentRebootTarget = SILENT_REBOOT_TARGET_READER; + silentRebootMagic = SILENT_REBOOT_MAGIC; + LOG_DBG("MAIN", "Silent restart (target=reader)"); + delay(50); + ESP.restart(); +} + // Verify power button press duration on wake-up from deep sleep // Pre-condition: isWakeupByPowerButton() == true void verifyPowerButtonDuration() { @@ -238,6 +261,15 @@ void setup() { t1 = millis(); HalSystem::begin(); + + // Read-and-clear so a panic later in setup() doesn't loop into silent reboot. + // Bound the target range too — RTC_NOINIT memory is uninitialized on cold boot. + const bool isSilentReboot = (silentRebootMagic == SILENT_REBOOT_MAGIC); + const uint32_t snapshotTarget = + (isSilentReboot && silentRebootTarget <= SILENT_REBOOT_TARGET_READER) ? silentRebootTarget : 0; + silentRebootMagic = 0; + silentRebootTarget = 0; + gpio.begin(); powerManager.begin(); halTiltSensor.begin(); @@ -315,7 +347,11 @@ void setup() { setupDisplayAndFonts(); - activityManager.goToBoot(); + // First paint after silent reboot is HALF_REFRESH (SDK forces it after begin()'s + // panel reset); subsequent paints FAST. + if (!isSilentReboot) { + activityManager.goToBoot(); + } APP_STATE.loadFromFile(); RECENT_BOOKS.loadFromFile(); @@ -327,6 +363,13 @@ void setup() { } else if (HalSystem::isRebootFromPanic()) { // If we rebooted from a panic, go to crash report screen to show the panic info activityManager.goToCrashReport(); + } else if (isSilentReboot && snapshotTarget == SILENT_REBOOT_TARGET_READER && !APP_STATE.openEpubPath.empty()) { + activityManager.goToReader(APP_STATE.openEpubPath); + } else if (isSilentReboot) { + // target == home (or reader with no open book): land on home — don't fall + // through to the sleep-wake "resume reader" logic, which fires on stale + // openEpubPath + lastSleepFromReader from a prior session. + activityManager.goHome(); } else if (APP_STATE.openEpubPath.empty() || !APP_STATE.lastSleepFromReader || mappedInputManager.isPressed(MappedInputManager::Button::Back) || APP_STATE.readerActivityLoadCount > 0) { // Boot to home screen if no book is open, last sleep was not from reader, back button is held, or reader activity From d712b45d1fc878570bca9b9122c7079bc2c416a3 Mon Sep 17 00:00:00 2001 From: luca <31419534+alan0ford@users.noreply.github.com> Date: Sat, 16 May 2026 03:52:42 +0200 Subject: [PATCH 77/93] fix: update Italian translation (#1970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * **What is the goal of this PR?** Update the Italian translation. * **What changes are included?** Took the latest `english.yaml` as reference and updated `italian.yaml` accordingly, translating new strings and revising existing ones where needed. Specific changes can be inspected from the diff. ## Additional Context * Nothing special to flag — happy to adjust any wording the reviewer disagrees with. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ — Claude provided a first-pass draft; I revised and rewrote a substantial portion by hand. --- lib/I18n/translations/italian.yaml | 101 +++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 45e9f842a5..611aeab4f7 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Aprire questo URL nel browser" STR_OR_HTTP_PREFIX: "o http://" STR_SCAN_QR_HINT: "oppure scansionare il codice QR col telefono:" STR_CALIBRE_WIRELESS: "Calibre wireless" -STR_CALIBRE_WEB_URL: "URL Web Calibre" +STR_CALIBRE_WEB_URL: "URL OPDS (Calibre)" STR_NETWORK_LEGEND: "* = Protetta | + = Salvata" STR_MAC_ADDRESS: "Indirizzo MAC:" STR_CHECKING_WIFI: "Verifica WiFi..." @@ -70,14 +70,13 @@ STR_IMAGES: "Immagini" STR_IMAGES_DISPLAY: "Visualizza" STR_IMAGES_PLACEHOLDER: "Segnaposto" STR_IMAGES_SUPPRESS: "Nascondi" -STR_SHORT_PWR_BTN: "Pressione breve tasto accensione" +STR_SHORT_PWR_BTN: "Pressione breve accensione" STR_ORIENTATION: "Orientamento lettura" STR_SIDE_BTN_LAYOUT: "Pulsanti laterali (lettore)" -STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior" +STR_LONG_PRESS_BEHAVIOR: "Pressione lunga" STR_LONG_PRESS_BEHAVIOR_OFF: "OFF" -STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip" -STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change" -STR_LONG_PRESS_SKIP: "Pressione lunga: salta capitolo" +STR_LONG_PRESS_BEHAVIOR_SKIP: "Salta capitolo" +STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientamento" STR_FONT_FAMILY: "Font lettore" STR_FONT_SIZE: "Dimensione font lettore" STR_LINE_SPACING: "Interlinea lettore" @@ -101,7 +100,7 @@ STR_KOREADER_USERNAME: "Nome utente KOReader" STR_KOREADER_PASSWORD: "Password KOReader" STR_FILENAME: "Nome file" STR_BINARY: "Binario" -STR_SET_CREDENTIALS_FIRST: "Impostare prima le credenziali" +STR_SET_CREDENTIALS_FIRST: "Prima le credenziali" STR_WIFI_CONN_FAILED: "Connessione WiFi non riuscita" STR_AUTHENTICATING: "Autenticazione..." STR_AUTH_SUCCESS: "Autenticazione riuscita!" @@ -132,10 +131,11 @@ STR_ALWAYS: "Sempre" STR_IGNORE: "Ignora" STR_SLEEP: "Sospendi" STR_PAGE_TURN: "Cambio pagina" +STR_FORCE_REFRESH: "Refresh" STR_PORTRAIT: "Verticale" -STR_LANDSCAPE_CW: "Orizzontale ↻" -STR_INVERTED: "Invertito" -STR_LANDSCAPE_CCW: "Orizzontale ↺" +STR_LANDSCAPE_CW: "Orizzontale Dx" +STR_INVERTED: "Capovolto" +STR_LANDSCAPE_CCW: "Orizzontale Sx" STR_PREV_NEXT: "Prec/Succ" STR_NEXT_PREV: "Succ/Prec" STR_NOTO_SERIF: "Noto Serif" @@ -173,6 +173,7 @@ STR_NO_UPDATE: "Nessun aggiornamento disponibile" STR_UPDATE_FAILED: "Aggiornamento non riuscito" STR_UPDATE_COMPLETE: "Aggiornamento completato" STR_POWER_ON_HINT: "Tenere premuto il tasto di accensione per riavviare" +STR_RESTARTING_HINT: "Riavvio... Se il dispositivo non si accende, tenere premuto il tasto per qualche secondo." STR_NO_ENTRIES: "Nessuna voce trovata" STR_DOWNLOADING: "Download..." STR_DOWNLOAD_FAILED: "Download non riuscito" @@ -181,6 +182,8 @@ STR_UNNAMED: "Senza nome" STR_NO_SERVER_URL: "Nessun Server configurato" STR_FETCH_FEED_FAILED: "Impossibile recuperare il feed" STR_PARSE_FEED_FAILED: "Impossibile analizzare il feed" +STR_NEXT_PAGE: "Pag. successiva »" +STR_PREV_PAGE: "« Pag. precedente" STR_NETWORK_PREFIX: "Rete: " STR_IP_ADDRESS_PREFIX: "Indirizzo IP: " STR_ERROR_GENERAL_FAILURE: "Errore: operazione non riuscita" @@ -228,13 +231,18 @@ STR_EXAMPLE_BOOK: "Titolo del libro" STR_PREVIEW: "Anteprima" STR_TITLE: "Titolo" STR_BATTERY: "Batteria" +STR_XTC_STATUS_BAR: "Barra di stato XTC" +STR_BOTTOM: "Basso" +STR_TOP: "Alto" STR_UI_THEME: "Tema interfaccia" STR_THEME_CLASSIC: "Classico" STR_THEME_LYRA: "Lyra" +STR_THEME_ROUNDEDRAFF: "RoundedRaff" STR_THEME_LYRA_EXTENDED: "Lyra esteso" STR_SUNLIGHT_FADING_FIX: "Correzione luce solare" STR_REMAP_FRONT_BUTTONS: "Rimappa pulsanti frontali" STR_OPDS_BROWSER: "Browser OPDS" +STR_SEARCH: "Cerca" STR_COVER_CUSTOM: "Copertina + Wallpaper" STR_MENU_RECENT_BOOKS: "Libri recenti" STR_NO_RECENT_BOOKS: "Nessun libro recente" @@ -246,15 +254,15 @@ STR_CALIBRE_SETUP: "Configurazione" STR_CALIBRE_STATUS: "Stato" STR_CLEAR_BUTTON: "Elimina" STR_DEFAULT_VALUE: "Predefinito" -STR_REMAP_PROMPT: "Premere un pulsante frontale per ogni funzione" +STR_REMAP_PROMPT: "Assegna premendo un pulsante frontale" STR_UNASSIGNED: "Non assegnato" STR_ALREADY_ASSIGNED: "Già assegnato" -STR_REMAP_RESET_HINT: "Laterale Su: ripristina layout predefinito" +STR_REMAP_RESET_HINT: "Laterale Su: ripristina predefiniti" STR_REMAP_CANCEL_HINT: "Laterale Giù: annulla rimappatura" -STR_HW_BACK_LABEL: "Indietro (1° pulsante)" -STR_HW_CONFIRM_LABEL: "Conferma (2° pulsante)" -STR_HW_LEFT_LABEL: "Sinistra (3° pulsante)" -STR_HW_RIGHT_LABEL: "Destra (4° pulsante)" +STR_HW_BACK_LABEL: "Indietro (1° pul.)" +STR_HW_CONFIRM_LABEL: "Conferma (2° pul.)" +STR_HW_LEFT_LABEL: "Sinistra (3° pul.)" +STR_HW_RIGHT_LABEL: "Destra (4° pul.)" STR_GO_TO_PERCENT: "Vai al %" STR_GO_HOME_BUTTON: "Vai alla home" STR_SYNC_PROGRESS: "Sincronizza avanzamento" @@ -285,17 +293,72 @@ STR_NO_REMOTE_MSG: "Nessun avanzamento remoto trovato" STR_UPLOAD_PROMPT: "Inviare la posizione attuale?" STR_UPLOAD_SUCCESS: "Avanzamenti inviati!" STR_SYNC_FAILED_MSG: "Sincronizzazione non riuscita" -STR_SAVE_PROGRESS_FAILED: "Impossibile salvare il progresso" +STR_SAVE_PROGRESS_FAILED: "Impossibile salvare avanzamenti" STR_SECTION_PREFIX: "Sezione " STR_UPLOAD: "Carica" STR_BOOK_S_STYLE: "Stile libro" STR_EMBEDDED_STYLE: "Stile integrato dell'epub" -STR_FOCUS_READING: "Lettura focalizzata" +STR_FOCUS_READING: "Lettura guidata" STR_OPDS_SERVER_URL: "Server OPDS" +STR_SET_SLEEP_COVER: "Imposta copertina" STR_FOOTNOTES: "Note a piè pagina" STR_NO_FOOTNOTES: "Nessuna nota in questa pagina" STR_LINK: "[link]" STR_SCREENSHOT_BUTTON: "Screenshot" +STR_ADD_SERVER: "Aggiungi server" +STR_SERVER_NAME: "Nome server" +STR_NO_SERVERS: "Nessun server OPDS configurato" +STR_DELETE_SERVER: "Elimina server" +STR_DELETE_CONFIRM: "Eliminare questo server?" +STR_OPDS_SERVERS: "Server OPDS" STR_AUTO_TURN_ENABLED: "Cambio pagina automatico: " STR_AUTO_TURN_PAGES_PER_MIN: "Cambio pagina automatico (pag/min)" -STR_TILT_PAGE_TURN: "Gira pagina con inclinazione" +STR_MANAGE_FONTS: "Gestisci font" +STR_FONT_BROWSER: "Sfoglia font" +STR_LOADING_FONT_LIST: "Elenco font..." +STR_NO_FONTS_AVAILABLE: "Nessun font disponibile" +STR_FONT_INSTALLED: "Font installato!" +STR_FONT_INSTALL_FAILED: "Installazione font non riuscita" +STR_INSTALLED: "Installato" +STR_CONFIRM_DOWNLOAD_PROMPT: "Scaricare?" +STR_SD_CARD_FULL: "Spazio insufficiente su SD" +STR_FILES_LABEL: "File: " +STR_SIZE_LABEL: "Dimensione: " +STR_REDOWNLOAD: "Scarica di nuovo" +STR_DOWNLOAD_ALL: "Scarica tutto" +STR_UPDATE_ALL: "Aggiorna tutto" +STR_ALL_FONTS_INSTALLED: "Tutti i font installati!" +STR_UPDATE_AVAILABLE: "Aggiorna" +STR_CRASH_TITLE: "Errore di sistema" +STR_CRASH_DESCRIPTION: "Un rapporto dettagliato è stato salvato in crash_report.txt. Includere il file per la segnalazione del problema." +STR_CRASH_REASON: "Causa dell'errore:" +STR_CRASH_NO_REASON: "(Nessuna causa registrata)" +STR_TILT_PAGE_TURN: "Inclina per cambio pagina" +STR_KB_HINT_MOVE_CURSOR: "SINISTRA o DESTRA per spostare il cursore" +STR_KB_HINT_RETURN_CURSOR: "SINISTRA per tornare alla posizione del cursore" +STR_KB_HINT_HIDE_PASSWORD: "Prima DESTRA e poi [***] per nascondere la password" +STR_KB_HINT_SHOW_PASSWORD: "Prima DESTRA e poi [abc] per mostrare la password" +STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Premere [***] per nascondere la password" +STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Premere [abc] per mostrare la password" +STR_KB_HINT_EDIT_ENTRY: "Tieni premuto SU per modificare la voce" +STR_KB_TIPS: "Suggerimenti:" +STR_KB_HINT_RETURN_KEYBOARD: "GIÙ per tornare alla tastiera" +STR_KB_HINT_EXIT_URL_MODE: "ABC per uscire dalla modalità URL" +STR_KB_HINT_CLEAR_TEXT: "Tieni spinto CANC per cancellare tutto" +STR_KB_HINT_SECONDARY_CHAR: "Tieni spinto SELEZ. per carattere secondario" +STR_KB_HINT_UPPER_SECONDARY: "Tieni spinto SELEZ. per MAIUS. o car. secondario" +STR_KB_HINT_LOWER_SECONDARY: "Tieni spinto SELEZ. per minus. o car. secondario" +STR_KB_HINT_URL_SNIPPETS: "Premi l'URL per le anteprime" +STR_SD_FIRMWARE_UPDATE: "Aggiornamento firmware da SD" +STR_SELECT_FIRMWARE_FILE: "Seleziona il firmware (.bin)" +STR_NO_BIN_FILES: "Nessun file .bin trovato" +STR_VALIDATING_FIRMWARE: "Convalida firmware..." +STR_INVALID_FIRMWARE: "File firmware non valido" +STR_FIRMWARE_TOO_LARGE: "Firmware troppo grande" +STR_FIRMWARE_TOO_SMALL: "Firmware troppo piccolo" +STR_FIRMWARE_UPDATE_PROMPT: "Aggiornare il firmware?" +STR_FIRMWARE_FILE_OPEN_FAILED: "Impossibile aprire il file" +STR_FIRMWARE_WRITE_FAILED: "Aggiornamento firmware non riuscito" +STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Non spegnere il dispositivo!" +STR_RECOVERY_MODE: "Modalità ripristino" +STR_RECOVERY_MODE_HINT: "Metti il firmware.bin nella SD e selezionalo" From 1afd8b141d17d29c28809a7f7f32f2511d969583 Mon Sep 17 00:00:00 2001 From: WuTofu <5987870+WuTofu@users.noreply.github.com> Date: Sat, 16 May 2026 10:25:48 +0800 Subject: [PATCH 78/93] fix: several QoL updates for SD font's UI (#1965) ## Summary * **What is the goal of this PR?** Improve the UI based on feedback from someone on discord > Downloading ALL fonts feature. > 1.1 Disable sleep when downloading, in my case went directly to sleep just right after downloading. > 1.2 It would be great to have and overall progress indicator as we only have the indication of each font family > 1.3 Any cancel or pause function might come in handy in case battery is running out and then resume or retry with pending fonts * **What changes are included?** - Now the UI can show overall progress across every file being downloaded in the batch, not just progress inside the current family. - Extended `HttpDownloader::downloadToFile()` to accept a cancel flag and abort the download. - Rendered a cancel button in the font download UI while a download is in progress. - `preventAutoSleep()` in `FontDownloadActivity.h` now returns true for `state_ == COMPLETE` and `state_ == ERROR` in addition to `LOADING_MANIFEST` and `DOWNLOADING` ## Additional Context Not very satisfied with how `HttpDownloader.cpp` is right now, might try to refactor it after v1.3.0 --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ --- .../browser/OpdsBookBrowserActivity.cpp | 2 +- .../settings/FontDownloadActivity.cpp | 56 +++++++++++++++---- .../settings/FontDownloadActivity.h | 9 ++- src/network/HttpDownloader.cpp | 20 +++++-- src/network/HttpDownloader.h | 4 +- 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index b1d5a8d42d..40eada071f 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -280,7 +280,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) { downloadTotal = total; requestUpdate(true); }, - server.username, server.password); + nullptr, server.username, server.password); if (result == HttpDownloader::OK) { Epub(filename, "/.crosspoint").clearCache(); diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index ab9bf50bb5..67f5cdf89d 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -175,10 +175,11 @@ bool FontDownloadActivity::fetchAndParseManifest() { // --- Download --- void FontDownloadActivity::downloadAll() { + cancelRequested_ = false; for (size_t i = 0; i < families_.size(); i++) { if (families_[i].installed) continue; downloadFamily(families_[i]); - if (state_ == ERROR) return; + if (state_ == ERROR || cancelRequested_) return; } { @@ -188,10 +189,11 @@ void FontDownloadActivity::downloadAll() { } void FontDownloadActivity::updateAll() { + cancelRequested_ = false; for (size_t i = 0; i < families_.size(); i++) { if (!families_[i].hasUpdate) continue; downloadFamily(families_[i]); - if (state_ == ERROR) return; + if (state_ == ERROR || cancelRequested_) return; } { @@ -267,10 +269,9 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) { RenderLock lock(*this); state_ = DOWNLOADING; downloadingFamilyIndex_ = static_cast(&family - families_.data()); - currentFileIndex_ = 0; - currentFileTotal_ = family.files.size(); fileProgress_ = 0; fileTotal_ = 0; + cancelRequested_ = false; } requestUpdateAndWait(); @@ -286,7 +287,6 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) { { RenderLock lock(*this); - currentFileIndex_ = i; fileProgress_ = 0; fileTotal_ = file.size; } @@ -297,11 +297,30 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) { std::string url = baseUrl_ + file.name; - auto result = HttpDownloader::downloadToFile(url, destPath, [this](size_t downloaded, size_t total) { - fileProgress_ = downloaded; - fileTotal_ = total; - requestUpdate(true); - }); + auto result = HttpDownloader::downloadToFile( + url, destPath, + [this](size_t downloaded, size_t total) { + fileProgress_ = downloaded; + fileTotal_ = total; + mappedInput.update(); + if (mappedInput.isPressed(MappedInputManager::Button::Back) || + mappedInput.wasPressed(MappedInputManager::Button::Back)) { + cancelRequested_ = true; + } + requestUpdate(true); + }, + &cancelRequested_); + + if (result == HttpDownloader::ABORTED) { + fontInstaller_.deleteFamily(family.name.c_str()); + family.installed = false; + family.hasUpdate = false; + { + RenderLock lock(*this); + state_ = FAMILY_LIST; + } + return; + } if (result != HttpDownloader::OK) { LOG_ERR("FONT", "Download failed: %s (%d)", file.name.c_str(), result); @@ -347,6 +366,7 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) { errorMessage_ = "Invalid font file: " + file.name; return; } + currentFileIndex_++; } fontInstaller_.refreshRegistry(); @@ -435,12 +455,25 @@ void FontDownloadActivity::loop() { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (!families_.empty()) { if (isDownloadAllRow(selectedIndex_)) { + currentFileIndex_ = 0; + currentFileTotal_ = 0; + for (const auto& f : families_) { + if (!f.installed) currentFileTotal_ += f.files.size(); + } + downloadAll(); } else if (isUpdateAllRow(selectedIndex_)) { + currentFileIndex_ = 0; + currentFileTotal_ = 0; + for (const auto& f : families_) { + if (f.hasUpdate) currentFileTotal_ += f.files.size(); + } updateAll(); } else { auto& family = families_[familyIndexFromList(selectedIndex_)]; if (!family.installed || family.hasUpdate) { + currentFileIndex_ = 0; + currentFileTotal_ = family.files.size(); downloadFamily(family); } else { promptDeleteSelectedFamily(); @@ -574,6 +607,9 @@ void FontDownloadActivity::render(RenderLock&&) { renderer, Rect{metrics.contentSidePadding, barY, pageWidth - metrics.contentSidePadding * 2, metrics.progressBarHeight}, static_cast(progress * 100), 100); + + const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } else if (state_ == COMPLETE) { renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_FONT_INSTALLED), true, EpdFontFamily::BOLD); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); diff --git a/src/activities/settings/FontDownloadActivity.h b/src/activities/settings/FontDownloadActivity.h index 7f76e7c6a9..90d488a867 100644 --- a/src/activities/settings/FontDownloadActivity.h +++ b/src/activities/settings/FontDownloadActivity.h @@ -34,7 +34,13 @@ class FontDownloadActivity : public Activity { void onExit() override; void loop() override; void render(RenderLock&&) override; - bool preventAutoSleep() override { return state_ == LOADING_MANIFEST || state_ == DOWNLOADING; } + bool preventAutoSleep() override { + return state_ == LOADING_MANIFEST || state_ == DOWNLOADING || + // This is added because HTTPClient is a synchronous/blocking function, + // and blocks the main loop until the download is complete. + // So `activityManager.preventAutoSleep()` is never called during downloading + state_ == COMPLETE || state_ == ERROR; + } bool skipLoopDelay() override { return true; } private: @@ -79,6 +85,7 @@ class FontDownloadActivity : public Activity { size_t fileTotal_ = 0; int downloadingFamilyIndex_ = 0; std::string errorMessage_; + bool cancelRequested_ = false; void onWifiSelectionComplete(bool success); bool fetchAndParseManifest(); diff --git a/src/network/HttpDownloader.cpp b/src/network/HttpDownloader.cpp index b881c859c7..bd26a5ac17 100644 --- a/src/network/HttpDownloader.cpp +++ b/src/network/HttpDownloader.cpp @@ -16,13 +16,17 @@ namespace { class FileWriteStream final : public Stream { public: - FileWriteStream(FsFile& file, size_t total, HttpDownloader::ProgressCallback progress) - : file_(file), total_(total), progress_(std::move(progress)) {} + FileWriteStream(FsFile& file, size_t total, HttpDownloader::ProgressCallback progress, bool* cancelFlag) + : file_(file), total_(total), progress_(std::move(progress)), cancelFlag_(cancelFlag) {} size_t write(uint8_t byte) override { return write(&byte, 1); } size_t write(const uint8_t* buffer, size_t size) override { // Write-through stream for HTTPClient::writeToStream with progress tracking. + if (cancelFlag_ && *cancelFlag_) { + writeOk_ = false; + return 0; + } const size_t written = file_.write(buffer, size); if (written != size) { writeOk_ = false; @@ -48,6 +52,7 @@ class FileWriteStream final : public Stream { size_t downloaded_ = 0; bool writeOk_ = true; HttpDownloader::ProgressCallback progress_; + bool* cancelFlag_; }; } // namespace @@ -101,8 +106,8 @@ bool HttpDownloader::fetchUrl(const std::string& url, std::string& outContent, c } HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& url, const std::string& destPath, - ProgressCallback progress, const std::string& username, - const std::string& password) { + ProgressCallback progress, bool* cancelFlag, + const std::string& username, const std::string& password) { std::unique_ptr client; if (UrlUtils::isHttpsUrl(url)) { auto* secureClient = new NetworkClientSecure(); @@ -155,12 +160,17 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& } // Let HTTPClient handle chunked decoding and stream body bytes into the file. - FileWriteStream fileStream(file, contentLength, progress); + FileWriteStream fileStream(file, contentLength, progress, cancelFlag); const int writeResult = http.writeToStream(&fileStream); file.close(); http.end(); + if (cancelFlag && *cancelFlag) { + Storage.remove(destPath.c_str()); + return ABORTED; + } + if (writeResult < 0) { LOG_ERR("HTTP", "writeToStream error: %d", writeResult); Storage.remove(destPath.c_str()); diff --git a/src/network/HttpDownloader.h b/src/network/HttpDownloader.h index 216840a579..5913c895be 100644 --- a/src/network/HttpDownloader.h +++ b/src/network/HttpDownloader.h @@ -32,6 +32,6 @@ class HttpDownloader { * Download a file to the SD card with optional credentials. */ static DownloadError downloadToFile(const std::string& url, const std::string& destPath, - ProgressCallback progress = nullptr, const std::string& username = "", - const std::string& password = ""); + ProgressCallback progress = nullptr, bool* cancelFlag = nullptr, + const std::string& username = "", const std::string& password = ""); }; From 493b8f97a21b1821169b938d853dc1fcbf340150 Mon Sep 17 00:00:00 2001 From: Stefan Blixten Karlsson Date: Sat, 16 May 2026 04:27:52 +0200 Subject: [PATCH 79/93] feat: Add swedish hyphenation (#1637) ## Summary * Add swedish hyphenation using scripts/update_hypenation.sh * Add hyphenation test data using the Swedish translation of Andy Weir's Project Hail Mary --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- lib/Epub/Epub/hyphenation/Hyphenator.cpp | 7 +- .../Epub/hyphenation/LanguageRegistry.cpp | 5 +- .../Epub/hyphenation/generated/hyph-sv.trie.h | 1491 +++++ scripts/update_hyphenation.sh | 1 + .../HyphenationEvaluationTest.cpp | 3 +- .../resources/swedish_hyphenation_tests.txt | 5012 +++++++++++++++++ 6 files changed, 6513 insertions(+), 6 deletions(-) create mode 100644 lib/Epub/Epub/hyphenation/generated/hyph-sv.trie.h create mode 100644 test/hyphenation_eval/resources/swedish_hyphenation_tests.txt diff --git a/lib/Epub/Epub/hyphenation/Hyphenator.cpp b/lib/Epub/Epub/hyphenation/Hyphenator.cpp index ada620e2bb..ed05d0612b 100644 --- a/lib/Epub/Epub/hyphenation/Hyphenator.cpp +++ b/lib/Epub/Epub/hyphenation/Hyphenator.cpp @@ -22,10 +22,9 @@ 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"}, -}; +static constexpr Iso639Mapping kIso639Mappings[] = {{"eng", "en"}, {"fra", "fr"}, {"fre", "fr"}, {"deu", "de"}, + {"ger", "de"}, {"rus", "ru"}, {"spa", "es"}, {"ita", "it"}, + {"ukr", "uk"}, {"swe", "sv"}}; // Maps a BCP-47 or ISO 639-2 language tag to a language-specific hyphenator. const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) { diff --git a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp index 541e9a277a..fc770c76c4 100644 --- a/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp +++ b/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp @@ -11,6 +11,7 @@ #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 { @@ -22,10 +23,11 @@ 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); -using EntryArray = std::array; +using EntryArray = std::array; const EntryArray& entries() { static const EntryArray kEntries = {{{"english", "en", &englishHyphenator}, @@ -35,6 +37,7 @@ const EntryArray& entries() { {"spanish", "es", &spanishHyphenator}, {"italian", "it", &italianHyphenator}, {"polish", "pl", &polishHyphenator}, + {"swedish", "sv", &swedishHyphenator}, {"ukrainian", "uk", &ukrainianHyphenator}}}; return kEntries; } diff --git a/lib/Epub/Epub/hyphenation/generated/hyph-sv.trie.h b/lib/Epub/Epub/hyphenation/generated/hyph-sv.trie.h new file mode 100644 index 0000000000..0883c5344b --- /dev/null +++ b/lib/Epub/Epub/hyphenation/generated/hyph-sv.trie.h @@ -0,0 +1,1491 @@ +#pragma once + +#include +#include + +#include "Epub/hyphenation/SerializedHyphenationTrie.h" + +// Auto-generated by generate_hyphenation_trie.py. Do not edit manually. +alignas(4) constexpr uint8_t sv_trie_data[] = { + 0x18, 0x23, 0x21, 0x17, 0x19, 0x0E, 0x22, 0x2B, 0x2C, 0x16, 0x0F, 0x1F, 0x20, 0x21, 0x0E, 0x2D, + 0x35, 0x33, 0x17, 0x0E, 0x16, 0x0D, 0x37, 0x49, 0x16, 0x0B, 0x2A, 0x21, 0x0C, 0x36, 0x3F, 0x48, + 0x40, 0x4B, 0x41, 0x29, 0x15, 0x0E, 0x34, 0x4A, 0x2C, 0x0F, 0x3D, 0x01, 0x03, 0x01, 0x17, 0x04, + 0x22, 0x0D, 0x02, 0x0C, 0x0F, 0x0E, 0x0D, 0x0D, 0x0E, 0x02, 0x0B, 0x04, 0x0D, 0x01, 0x0C, 0x02, + 0x0D, 0x02, 0x0F, 0x05, 0x01, 0x0E, 0x04, 0x0B, 0x03, 0x0C, 0x02, 0x0D, 0x0C, 0x04, 0x0F, 0x02, + 0x0C, 0x0C, 0x03, 0x0E, 0x0C, 0x0B, 0x17, 0x0C, 0x20, 0x0D, 0x0F, 0x18, 0x05, 0x0C, 0x02, 0x18, + 0x19, 0x18, 0x0C, 0x0E, 0x20, 0x0F, 0x23, 0x0C, 0x37, 0x0E, 0x0C, 0x0D, 0x19, 0x0C, 0x18, 0x0D, + 0x2B, 0x0E, 0x02, 0x0D, 0x0E, 0x02, 0x0F, 0x0E, 0x05, 0x35, 0x05, 0x21, 0x2D, 0x0C, 0x3E, 0x16, + 0x0E, 0x0B, 0x0E, 0x23, 0x0E, 0x05, 0x17, 0x02, 0x0B, 0x0E, 0x02, 0x0B, 0x0C, 0x04, 0x21, 0x0E, + 0x02, 0x15, 0x2D, 0x0E, 0x04, 0x2C, 0x05, 0x0E, 0x18, 0x0F, 0x54, 0x18, 0x0E, 0x0D, 0x17, 0x04, + 0x0D, 0x0C, 0x0F, 0x0C, 0x05, 0x15, 0x0E, 0x0F, 0x02, 0x17, 0x34, 0x0D, 0x02, 0x1F, 0x02, 0x16, + 0x02, 0x0C, 0x0E, 0x02, 0x17, 0x0E, 0x04, 0x2D, 0x0E, 0x17, 0x05, 0x19, 0x2A, 0x0F, 0x18, 0x0B, + 0x36, 0x0D, 0x0B, 0x21, 0x2C, 0x0D, 0x1F, 0x0C, 0xA0, 0x00, 0x51, 0x21, 0x6C, 0xFD, 0xA0, 0x00, + 0x61, 0xA3, 0x00, 0x41, 0x69, 0x6F, 0x72, 0xF7, 0xFA, 0xFD, 0xA0, 0x00, 0x71, 0xA2, 0x00, 0x41, + 0x72, 0x73, 0xFD, 0xF1, 0xA0, 0x00, 0x82, 0xA0, 0x00, 0x81, 0x22, 0x61, 0x65, 0xFA, 0xFD, 0x21, + 0x72, 0xFB, 0x21, 0x6C, 0xF5, 0xA0, 0x00, 0xA1, 0x21, 0x72, 0xFD, 0x23, 0x65, 0x70, 0x75, 0xEC, + 0xFD, 0xDF, 0xA0, 0x00, 0xB1, 0x21, 0x6C, 0xFD, 0x23, 0x62, 0x70, 0x75, 0xF0, 0xFD, 0xDF, 0xA0, + 0x00, 0xC1, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0xA0, 0x00, 0xD2, 0x21, 0x73, + 0xFD, 0x42, 0x65, 0x6F, 0xFF, 0xF1, 0xFF, 0xA7, 0x44, 0x64, 0x65, 0x67, 0x73, 0xFF, 0xF0, 0xFF, + 0xF6, 0xFF, 0xF9, 0xFF, 0xA0, 0xA0, 0x00, 0xF1, 0x21, 0x70, 0xFD, 0x41, 0x74, 0xFF, 0x9F, 0xA1, + 0x01, 0x01, 0x65, 0xFC, 0x41, 0x6E, 0xFF, 0xA3, 0xC2, 0x00, 0xA1, 0x65, 0x69, 0xFF, 0xFC, 0xFF, + 0x92, 0x22, 0x6B, 0x74, 0xEE, 0xF7, 0x42, 0x61, 0x74, 0xFF, 0x91, 0xFF, 0x78, 0x41, 0x74, 0xFF, + 0x71, 0x21, 0x73, 0xFC, 0xA0, 0x00, 0x41, 0x21, 0x6C, 0xFD, 0xA0, 0x01, 0x12, 0x22, 0x61, 0x73, + 0xFA, 0xFD, 0x4D, 0x62, 0x63, 0x64, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x75, 0x76, + 0xFF, 0x5F, 0xFF, 0x5C, 0xFF, 0x6B, 0xFF, 0x7D, 0xFF, 0x80, 0xFF, 0x89, 0xFF, 0x96, 0xFF, 0xB6, + 0xFF, 0xC6, 0xFF, 0xDF, 0xFF, 0xE4, 0xFF, 0xEF, 0xFF, 0xFB, 0xA0, 0x01, 0x31, 0x21, 0x73, 0xFD, + 0x21, 0x6B, 0xFD, 0x21, 0x6E, 0xFA, 0xA0, 0x01, 0x41, 0x21, 0x73, 0xFD, 0x21, 0x64, 0xFD, 0x21, + 0x6C, 0xFD, 0xA0, 0x01, 0x01, 0xA0, 0x01, 0x51, 0x21, 0x74, 0xFD, 0x22, 0x6B, 0x72, 0xF7, 0xFD, + 0xA4, 0x00, 0x41, 0x61, 0x65, 0x69, 0x6F, 0xE0, 0xE3, 0xEF, 0xFB, 0x41, 0x73, 0xFF, 0x44, 0x41, + 0x70, 0xFE, 0xF9, 0x21, 0x6F, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x6B, 0xFD, 0x22, 0x69, 0x79, 0xEF, + 0xFD, 0x21, 0x72, 0xC5, 0x21, 0xA4, 0xFD, 0xA1, 0x00, 0x41, 0xC3, 0xFD, 0x41, 0x76, 0xFF, 0x49, + 0xA0, 0x01, 0x62, 0x41, 0x6E, 0xFF, 0x28, 0x22, 0x61, 0x69, 0xF9, 0xFC, 0x41, 0x74, 0xFE, 0xCC, + 0x41, 0xA4, 0xFF, 0x64, 0x22, 0x73, 0xC3, 0xF8, 0xFC, 0xA0, 0x01, 0x82, 0x21, 0x69, 0xFD, 0x43, + 0x64, 0x6C, 0x70, 0xFE, 0xD8, 0xFE, 0xBF, 0xFE, 0xB9, 0x21, 0x61, 0xF6, 0x41, 0x64, 0xFE, 0xCB, + 0x21, 0x61, 0xFC, 0xA1, 0x00, 0xA1, 0x72, 0xFD, 0xA2, 0x00, 0xD1, 0x6B, 0x74, 0xF1, 0xFB, 0x41, + 0x72, 0xFE, 0xAB, 0xA0, 0x01, 0xA1, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x48, 0x6B, 0x6C, 0x6E, + 0x72, 0x73, 0x74, 0x76, 0x78, 0xFF, 0xB0, 0xFF, 0xBB, 0xFF, 0xC8, 0xFF, 0xD0, 0xFF, 0xEC, 0xFF, + 0xF3, 0xFF, 0xFD, 0xFE, 0x92, 0x41, 0x72, 0xFE, 0xAD, 0x21, 0x62, 0xFC, 0x41, 0x6D, 0xFF, 0x4A, + 0x21, 0x73, 0xD3, 0x22, 0x61, 0x65, 0xF9, 0xFD, 0xA0, 0x01, 0xB1, 0x21, 0x6C, 0xFD, 0x21, 0x65, + 0xFD, 0x21, 0x67, 0xFD, 0x41, 0x6E, 0xFF, 0x41, 0x42, 0x61, 0x65, 0xFF, 0x3D, 0xFF, 0xFC, 0x21, + 0x72, 0xF9, 0x22, 0xA5, 0xB6, 0xEF, 0xFD, 0xA3, 0x00, 0x41, 0x65, 0x72, 0xC3, 0xD2, 0xDC, 0xFB, + 0x41, 0xA4, 0xFE, 0x65, 0x21, 0xC3, 0xFC, 0x42, 0x6C, 0x73, 0xFF, 0xFD, 0xFE, 0x6B, 0xA1, 0x00, + 0xD1, 0x75, 0xF9, 0x41, 0x6D, 0xFF, 0x0F, 0x41, 0x61, 0xFE, 0x21, 0x21, 0x73, 0xFC, 0x22, 0x65, + 0x75, 0xF5, 0xFD, 0x41, 0x70, 0xFE, 0x1B, 0x41, 0x65, 0xFE, 0x41, 0xA0, 0x01, 0xE1, 0xA0, 0x01, + 0xF2, 0xC6, 0x01, 0xC2, 0x61, 0x64, 0x67, 0x6B, 0x73, 0x74, 0xFE, 0xA3, 0xFE, 0x0D, 0xFF, 0xF6, + 0xFF, 0xFA, 0xFF, 0xFD, 0xFE, 0x0D, 0x42, 0x6B, 0x6F, 0xFF, 0xD1, 0xFE, 0x04, 0x45, 0x62, 0x6B, + 0x6D, 0x6E, 0x73, 0xFE, 0x18, 0xFE, 0x18, 0xFF, 0xD6, 0xFF, 0xE4, 0xFF, 0xF9, 0xA0, 0x02, 0x11, + 0x21, 0x6B, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x69, 0xFD, 0xCF, 0x41, 0x74, 0xFF, + 0x63, 0x22, 0x6C, 0x72, 0xF8, 0xFC, 0xA0, 0x02, 0x31, 0xA1, 0x02, 0x21, 0x73, 0xFD, 0x21, 0x67, + 0xFB, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x70, 0xFF, 0x49, 0x21, 0xB6, 0xFC, 0xA4, 0x00, + 0x41, 0x61, 0x6F, 0x72, 0xC3, 0xD8, 0xE3, 0xF6, 0xFD, 0x41, 0x72, 0xFE, 0x71, 0x21, 0x6B, 0xFC, + 0x21, 0x74, 0xF9, 0x21, 0x6B, 0xFD, 0x41, 0x74, 0xFE, 0x7C, 0x21, 0x73, 0xFC, 0xA3, 0x00, 0xD1, + 0x61, 0x65, 0x75, 0xF0, 0xF6, 0xFD, 0xA1, 0x01, 0xE1, 0x72, 0xB3, 0x21, 0x65, 0xFB, 0x21, 0x6B, + 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x65, 0xFD, 0xA1, 0x21, 0x72, 0xFC, 0xA0, 0x02, 0x41, 0x21, 0x73, + 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x02, 0x51, 0x21, 0x73, 0xFD, 0x21, 0x6B, 0xFD, + 0x21, 0x6C, 0xFD, 0x21, 0xB6, 0xFD, 0x21, 0xC3, 0xFD, 0x41, 0x73, 0xFF, 0x42, 0x21, 0x6E, 0xFC, + 0x41, 0x74, 0xFD, 0x8F, 0x42, 0x6E, 0x73, 0xFE, 0x25, 0xFF, 0xFC, 0x41, 0x6F, 0xFE, 0xA8, 0x21, + 0x6B, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x02, 0x61, 0x21, 0x72, 0xFD, 0x21, 0x6B, + 0xFD, 0x21, 0x72, 0xFD, 0x23, 0xA5, 0xA4, 0xB6, 0xE0, 0xF1, 0xFD, 0xC6, 0x00, 0xD1, 0x61, 0x65, + 0x69, 0x6A, 0x6F, 0xC3, 0xFF, 0xA6, 0xFF, 0xAD, 0xFF, 0xB9, 0xFF, 0xCB, 0xFF, 0xD2, 0xFF, 0xF9, + 0xA0, 0x02, 0x71, 0x21, 0x72, 0xFD, 0x41, 0x6E, 0xFD, 0x2F, 0x41, 0x72, 0xFE, 0x69, 0x21, 0x64, + 0xFC, 0x21, 0xB6, 0xFD, 0xA3, 0x00, 0x41, 0x65, 0x6F, 0xC3, 0xEF, 0xF2, 0xFD, 0x41, 0x75, 0xFD, + 0x58, 0x21, 0x6B, 0xFC, 0x42, 0x74, 0x76, 0xFC, 0xEA, 0xFC, 0xF6, 0xA0, 0x00, 0xD1, 0x44, 0x61, + 0x6F, 0x73, 0x75, 0xFF, 0xFD, 0xFF, 0xFD, 0xFD, 0x7C, 0xFC, 0xEC, 0x41, 0x6B, 0xFC, 0xFA, 0x41, + 0x73, 0xFD, 0x03, 0x21, 0x64, 0xFC, 0xA0, 0x02, 0x82, 0x41, 0x61, 0xFC, 0xD1, 0x21, 0x72, 0xFC, + 0x45, 0x63, 0x6B, 0x6C, 0x74, 0x76, 0xFC, 0xD7, 0xFF, 0xF6, 0xFC, 0xCA, 0xFF, 0xFD, 0xFC, 0xCA, + 0xA0, 0x02, 0x81, 0x49, 0x63, 0x6B, 0x6D, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0x75, 0xFF, 0xBE, 0xFF, + 0xC1, 0xFF, 0xCB, 0xFF, 0xD8, 0xFC, 0xB7, 0xFF, 0xE0, 0xFF, 0xED, 0xFE, 0x0C, 0xFF, 0xFD, 0x42, + 0x65, 0x69, 0xFC, 0x8F, 0xFC, 0x89, 0x21, 0x74, 0xF9, 0x42, 0x70, 0x73, 0xFE, 0xCE, 0xFF, 0xFD, + 0x41, 0x74, 0xFE, 0xA9, 0x21, 0x73, 0xFC, 0xA0, 0x02, 0xA1, 0x21, 0x73, 0xFD, 0x21, 0x73, 0xFD, + 0x21, 0x65, 0xFD, 0x41, 0x61, 0xFC, 0x9F, 0x21, 0x6B, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, + 0x21, 0x62, 0xFD, 0xA4, 0x00, 0x41, 0x61, 0x69, 0x72, 0x75, 0xD6, 0xE1, 0xED, 0xFD, 0x41, 0x65, + 0xFD, 0x34, 0x21, 0x74, 0xFC, 0x42, 0x67, 0x73, 0xFC, 0x73, 0xFF, 0xFD, 0x41, 0x73, 0xFE, 0x61, + 0x21, 0x6E, 0xFC, 0x41, 0x64, 0xFE, 0x28, 0x21, 0x6D, 0xFC, 0x41, 0x65, 0xFE, 0xFE, 0x21, 0x76, + 0xFC, 0x21, 0xB6, 0xFD, 0xA4, 0x00, 0xD1, 0x65, 0x75, 0x79, 0xC3, 0xE1, 0xEC, 0xF3, 0xFD, 0x41, + 0x6B, 0xFD, 0x03, 0x41, 0x67, 0xFD, 0xA2, 0x41, 0x6F, 0xFC, 0x11, 0x41, 0xB6, 0xFC, 0xFA, 0x21, + 0xC3, 0xFC, 0xA0, 0x02, 0x21, 0x21, 0x73, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x74, 0xFD, 0xA1, 0x00, + 0xA1, 0x6F, 0xFD, 0x41, 0x74, 0xFC, 0xD6, 0x21, 0x75, 0xFC, 0x41, 0x6D, 0xFD, 0xE1, 0x41, 0x6E, + 0xFC, 0xCB, 0xA0, 0x02, 0xB1, 0x21, 0x67, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0xA3, 0x00, + 0xA1, 0x61, 0x65, 0x72, 0xEC, 0xF0, 0xFD, 0x41, 0x70, 0xFC, 0x0B, 0x21, 0x70, 0xFC, 0xA8, 0x00, + 0x41, 0x61, 0x65, 0x69, 0x6A, 0x6B, 0x6C, 0x74, 0x75, 0xB1, 0xB5, 0xB9, 0xC1, 0xD0, 0xD9, 0xF0, + 0xFD, 0x41, 0x6C, 0xFB, 0xBD, 0xC1, 0x00, 0xA1, 0x6C, 0xFC, 0x85, 0x22, 0x62, 0x6B, 0xF6, 0xFA, + 0x41, 0x74, 0xFF, 0x43, 0x21, 0x73, 0xFC, 0x41, 0x6C, 0xFB, 0xE8, 0x43, 0x67, 0x6C, 0x6F, 0xFD, + 0x2A, 0xFF, 0xFC, 0xFB, 0xA3, 0x41, 0x70, 0xFD, 0xE2, 0x42, 0x64, 0x73, 0xFF, 0x21, 0xFB, 0xC9, + 0x41, 0x6B, 0xFC, 0xF3, 0x21, 0xA4, 0xFC, 0x22, 0x65, 0xC3, 0xF2, 0xFD, 0xA5, 0x00, 0x41, 0x61, + 0x65, 0x69, 0x6F, 0x72, 0xCF, 0xD8, 0xDF, 0xE9, 0xFB, 0x41, 0x65, 0xFD, 0x62, 0x21, 0x67, 0xFC, + 0x42, 0x61, 0x73, 0xFC, 0x04, 0xFB, 0x6E, 0x41, 0x72, 0xFB, 0x80, 0x21, 0x65, 0xFC, 0xC3, 0x01, + 0xC2, 0x61, 0x72, 0x73, 0xFB, 0xF6, 0xFF, 0xFD, 0xFB, 0x5A, 0x46, 0x6B, 0x6C, 0x6E, 0x70, 0x72, + 0x74, 0xFB, 0x60, 0xFD, 0xB6, 0xFF, 0xE3, 0xFC, 0x38, 0xFF, 0xE6, 0xFF, 0xF4, 0x41, 0x6E, 0xFC, + 0xDB, 0x21, 0x65, 0xFC, 0x21, 0x74, 0xFD, 0x42, 0x72, 0x74, 0xFD, 0xE9, 0xFF, 0xFD, 0x41, 0x64, + 0xFB, 0xFF, 0xC1, 0x01, 0xE1, 0x73, 0xFC, 0x04, 0x21, 0x67, 0xFA, 0x21, 0xA4, 0xFD, 0xA3, 0x00, + 0xD1, 0x61, 0x65, 0xC3, 0xE9, 0xF0, 0xFD, 0x42, 0x61, 0x65, 0xFE, 0x34, 0xFB, 0xAD, 0x41, 0x67, + 0xFB, 0xE8, 0xA0, 0x02, 0xC2, 0x21, 0x73, 0xFD, 0xA0, 0x02, 0xE1, 0x21, 0x72, 0xFD, 0x21, 0x65, + 0xFD, 0x44, 0x6E, 0x72, 0x73, 0x74, 0xFF, 0xED, 0xFF, 0xF4, 0xFB, 0x8C, 0xFF, 0xFD, 0x41, 0x6F, + 0xFA, 0xF0, 0x21, 0x72, 0xFC, 0x23, 0xA5, 0xA4, 0xB6, 0xEC, 0xFD, 0xFD, 0x57, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x78, 0x79, 0xC3, 0xFB, 0x86, 0xFB, 0xD4, 0xFB, 0xF0, 0xFB, 0xFB, 0xFC, 0x50, 0xFC, 0x9B, + 0xFC, 0xB2, 0xFC, 0xC2, 0xFC, 0xF1, 0xFD, 0x32, 0xFD, 0x51, 0xFD, 0xBF, 0xFD, 0xE8, 0xFE, 0x37, + 0xFE, 0x87, 0xFE, 0xB8, 0xFF, 0x12, 0xFF, 0x60, 0xFF, 0x8E, 0xFF, 0xC2, 0xFD, 0xFF, 0xFF, 0xCB, + 0xFF, 0xF9, 0x41, 0x75, 0xFB, 0x32, 0xC2, 0x00, 0xE1, 0x6C, 0x73, 0xFB, 0x95, 0xFB, 0x7C, 0xC1, + 0x02, 0x81, 0x61, 0xFA, 0x9B, 0x41, 0x72, 0xFA, 0xF6, 0x42, 0x63, 0x6F, 0xFA, 0x9E, 0xFA, 0x91, + 0x21, 0x69, 0xF9, 0x42, 0x63, 0x6B, 0xFB, 0x11, 0xFB, 0x11, 0xA0, 0x00, 0xE1, 0xA7, 0x02, 0x01, + 0x62, 0x65, 0x6C, 0x6F, 0x72, 0x73, 0x75, 0xD5, 0xD9, 0xE2, 0xE8, 0xF3, 0xF6, 0xFD, 0x41, 0x73, + 0xFA, 0xF6, 0x41, 0x6B, 0xFA, 0x5C, 0x41, 0x70, 0xFA, 0xF4, 0x24, 0x65, 0x69, 0x73, 0x75, 0xF4, + 0xF4, 0xF8, 0xFC, 0x21, 0x6B, 0xF7, 0x21, 0x67, 0xD4, 0x43, 0x6B, 0x6C, 0x70, 0xFF, 0xD1, 0xFF, + 0xD1, 0xFA, 0x5E, 0x41, 0x70, 0xFA, 0x47, 0x21, 0x6E, 0xC3, 0xA0, 0x02, 0xF1, 0xA1, 0x01, 0x91, + 0x73, 0xFD, 0x41, 0x6C, 0xFA, 0x38, 0x44, 0x61, 0x65, 0x69, 0x75, 0xFF, 0xF1, 0xFF, 0xF7, 0xFF, + 0xFC, 0xFA, 0x34, 0x46, 0x61, 0x65, 0x6A, 0x6F, 0x72, 0x73, 0xFF, 0xD3, 0xFF, 0xD6, 0xFA, 0x27, + 0xFF, 0xE0, 0xFF, 0xF3, 0xFD, 0x38, 0x41, 0x64, 0xFF, 0x94, 0xA0, 0x03, 0x01, 0x21, 0xA4, 0xFD, + 0x42, 0x6F, 0xC3, 0xFA, 0x94, 0xFF, 0xFD, 0xA0, 0x03, 0x12, 0x21, 0x69, 0xFD, 0x41, 0x72, 0xFA, + 0x87, 0x21, 0x6F, 0xFC, 0x23, 0x66, 0x72, 0x74, 0xEC, 0xF6, 0xFD, 0x41, 0x61, 0xFA, 0x0A, 0xA1, + 0x01, 0xD1, 0x72, 0xFC, 0xA0, 0x02, 0x01, 0xA1, 0x01, 0xD1, 0x2E, 0xFD, 0x41, 0x72, 0xFD, 0x24, + 0x41, 0x72, 0xFB, 0x1C, 0x41, 0x6F, 0xF9, 0xD6, 0xA0, 0x00, 0x91, 0x21, 0x72, 0xFD, 0x47, 0x61, + 0x65, 0x67, 0x6C, 0x6F, 0x72, 0x75, 0xFF, 0xE1, 0xFF, 0xE9, 0xFF, 0xEE, 0xFD, 0x12, 0xFF, 0xF2, + 0xFF, 0xF6, 0xFF, 0xFD, 0x21, 0x6A, 0xE4, 0xA0, 0x01, 0x91, 0x42, 0x62, 0x76, 0xF9, 0xCB, 0xFF, + 0xFD, 0xA0, 0x01, 0xD1, 0x21, 0x74, 0xD4, 0xA1, 0x01, 0x91, 0x72, 0xFD, 0x41, 0x6D, 0xF9, 0xAB, + 0x41, 0x62, 0xF9, 0x9A, 0x43, 0x61, 0x69, 0x6F, 0xFF, 0xF8, 0xFA, 0x59, 0xFF, 0xFC, 0x41, 0x61, + 0xFA, 0x16, 0x21, 0x74, 0xFC, 0x41, 0x67, 0xFF, 0x45, 0x41, 0x69, 0xF9, 0x81, 0x22, 0x69, 0x72, + 0xF8, 0xFC, 0x41, 0x69, 0xFE, 0xF8, 0x21, 0x72, 0xFC, 0x42, 0x61, 0x65, 0xFF, 0xFD, 0xF9, 0x71, + 0x42, 0x72, 0x74, 0xFE, 0xEA, 0xF9, 0x77, 0xA0, 0x03, 0x31, 0x22, 0xA5, 0xB6, 0xF6, 0xFD, 0x4B, + 0x61, 0x65, 0x69, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xFF, 0xB8, 0xFE, 0xDB, 0xFF, + 0xB2, 0xF9, 0x5B, 0xFF, 0xB2, 0xFF, 0xC5, 0xFF, 0xD3, 0xFF, 0xDE, 0xFF, 0xB2, 0xFF, 0xEA, 0xFF, + 0xFB, 0xC1, 0x01, 0xD1, 0x64, 0xFF, 0x9B, 0x41, 0x72, 0xFA, 0x0B, 0xC3, 0x02, 0x01, 0x2E, 0x65, + 0x73, 0xFE, 0xAF, 0xFF, 0x7C, 0xF9, 0x2F, 0x41, 0x6B, 0xF9, 0x11, 0x21, 0x69, 0xFC, 0x21, 0x74, + 0xFD, 0x23, 0x62, 0x6E, 0x73, 0xE6, 0xEA, 0xFD, 0x43, 0x61, 0x76, 0xC3, 0xFE, 0xAA, 0xFC, 0x23, + 0xFA, 0x38, 0x41, 0xA5, 0xF9, 0x08, 0x42, 0x73, 0xC3, 0xF9, 0x87, 0xFF, 0xFC, 0x41, 0x6B, 0xF9, + 0x32, 0x21, 0x73, 0xFC, 0x41, 0x74, 0xF9, 0x03, 0xA1, 0x01, 0xD1, 0x72, 0xFC, 0x41, 0x70, 0xFA, + 0xBA, 0x43, 0x72, 0x75, 0xC3, 0xF8, 0xE9, 0xF9, 0x73, 0xFA, 0x0F, 0xC1, 0x01, 0xD1, 0x73, 0xF8, + 0xCD, 0x41, 0x73, 0xF8, 0xCD, 0x4C, 0x61, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6F, 0x73, 0x74, 0x75, + 0x76, 0x79, 0xFF, 0x9C, 0xFF, 0xBC, 0xFF, 0x03, 0xFF, 0xC3, 0xFF, 0xD1, 0xFF, 0xDC, 0xFF, 0xE3, + 0xFF, 0xE8, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, 0xFC, 0xFF, 0x2C, 0x41, 0x66, 0xFE, 0xDE, 0x42, 0xA5, + 0xB6, 0xF9, 0x0D, 0xFE, 0xF9, 0x45, 0x61, 0x69, 0x70, 0x73, 0xC3, 0xFF, 0xF5, 0xF9, 0x2F, 0xFE, + 0xA8, 0xFB, 0xEB, 0xFF, 0xF9, 0x41, 0x6F, 0xF8, 0xB0, 0x41, 0x66, 0xF8, 0x91, 0x41, 0x6B, 0xF8, + 0x8D, 0x23, 0x62, 0x6C, 0x72, 0xF4, 0xF8, 0xFC, 0xC1, 0x00, 0x71, 0x69, 0xF9, 0x42, 0x41, 0x6B, + 0xF8, 0x89, 0x41, 0x6C, 0xFB, 0xF8, 0x41, 0x6E, 0xF8, 0xFE, 0x21, 0xA4, 0xFC, 0x24, 0x61, 0x65, + 0x75, 0xC3, 0xF1, 0xF5, 0xF9, 0xFD, 0x41, 0x73, 0xF8, 0x7F, 0x43, 0x65, 0x69, 0x72, 0xFF, 0xFC, + 0xF8, 0x60, 0xFB, 0xA6, 0x41, 0x75, 0xF8, 0x8B, 0x41, 0x72, 0xF8, 0x46, 0x43, 0x69, 0x6B, 0x70, + 0xF9, 0x26, 0xFE, 0x7C, 0xFE, 0x7C, 0x41, 0x65, 0xF8, 0x51, 0x41, 0x74, 0xF9, 0x0C, 0x21, 0x73, + 0xFC, 0x41, 0x75, 0xF8, 0x27, 0x41, 0x69, 0xF8, 0x29, 0x21, 0x61, 0xDF, 0xC2, 0x02, 0x81, 0x6F, + 0x79, 0xF8, 0xB8, 0xF8, 0xB8, 0x41, 0x72, 0xFD, 0xE5, 0x21, 0x61, 0xFC, 0x48, 0x63, 0x69, 0x6A, + 0x6B, 0x6C, 0x70, 0x74, 0x76, 0xFF, 0xDA, 0xFF, 0xE2, 0xF8, 0xA8, 0xFF, 0xE5, 0xFF, 0xE9, 0xFF, + 0xED, 0xFF, 0xF0, 0xFF, 0xFD, 0x41, 0x65, 0xF8, 0x8F, 0x42, 0x6A, 0x72, 0xF8, 0x8B, 0xFF, 0xFC, + 0x41, 0xB6, 0xFE, 0x47, 0x4D, 0x61, 0x63, 0x64, 0x65, 0x67, 0x69, 0x6B, 0x6F, 0x73, 0x74, 0x75, + 0x79, 0xC3, 0xFF, 0x6D, 0xFF, 0x74, 0xFF, 0x89, 0xFF, 0x46, 0xFF, 0x96, 0xFF, 0xA0, 0xFF, 0xA4, + 0xFF, 0xA8, 0xFF, 0xD8, 0xFF, 0xF5, 0xFE, 0x4D, 0xFD, 0x76, 0xFF, 0xFC, 0xA0, 0x03, 0x42, 0xC2, + 0x01, 0xD1, 0x2E, 0x6E, 0xFD, 0xE5, 0xFF, 0xFD, 0x41, 0x61, 0xFF, 0x4E, 0x41, 0x74, 0xFF, 0x6C, + 0x21, 0x73, 0xFC, 0x41, 0x61, 0xFD, 0x77, 0x21, 0x72, 0xFC, 0x42, 0x61, 0x73, 0xFF, 0xFD, 0xFE, + 0xB3, 0x43, 0x63, 0x66, 0x73, 0xF7, 0xA9, 0xF7, 0xA9, 0xFD, 0x29, 0x21, 0x69, 0xF6, 0xC3, 0x00, + 0xD1, 0x65, 0x6C, 0x70, 0xF7, 0x9C, 0xF7, 0x8A, 0xF7, 0x90, 0x41, 0x73, 0xF7, 0x7E, 0x41, 0xA4, + 0xFD, 0x0C, 0x4A, 0x65, 0x69, 0x6C, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x79, 0xC3, 0xFF, 0xBD, 0xFD, + 0xDF, 0xFF, 0xC6, 0xFF, 0xCE, 0xFF, 0xD8, 0xFF, 0xE9, 0xFF, 0xEC, 0xFF, 0xF8, 0xFD, 0x08, 0xFF, + 0xFC, 0x41, 0x6E, 0xFD, 0x97, 0x41, 0x76, 0xFD, 0x93, 0xA2, 0x03, 0x61, 0x6E, 0x72, 0xF8, 0xFC, + 0xA0, 0x03, 0x61, 0xC3, 0x02, 0xF1, 0x69, 0x6F, 0x72, 0xFD, 0xF4, 0xFF, 0xFD, 0xFD, 0xF4, 0x42, + 0x72, 0x73, 0xF7, 0x4B, 0xF8, 0x6D, 0x41, 0x64, 0xFD, 0x72, 0x21, 0x6E, 0xFC, 0x41, 0x73, 0xF9, + 0x5C, 0x21, 0x65, 0xD0, 0xA1, 0x01, 0xD1, 0x67, 0xFD, 0x41, 0x6E, 0xF7, 0x25, 0x21, 0x61, 0xFC, + 0x43, 0x61, 0x6C, 0x72, 0xF7, 0x2A, 0xFF, 0xFD, 0xF7, 0x37, 0x42, 0x2E, 0x61, 0xFD, 0xBD, 0xF7, + 0xAD, 0xC1, 0x01, 0xD1, 0x62, 0xFD, 0x33, 0x42, 0x61, 0x6C, 0xF7, 0x2B, 0xF7, 0xD3, 0x43, 0x61, + 0x6B, 0x76, 0xFA, 0x1D, 0xFF, 0xF9, 0xFA, 0x1D, 0x41, 0x6F, 0xF7, 0x8C, 0x41, 0x70, 0xF7, 0xBE, + 0x43, 0x72, 0x73, 0x75, 0xFF, 0xF8, 0xFF, 0xFC, 0xFC, 0xFD, 0xC2, 0x03, 0x31, 0x72, 0x73, 0xFD, + 0x1E, 0xFC, 0x70, 0x41, 0x67, 0xF7, 0x71, 0x21, 0xA4, 0xFC, 0x21, 0xC3, 0xFD, 0x41, 0xA4, 0xFD, + 0x2A, 0x52, 0x61, 0x62, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6E, 0x6F, 0x70, 0x73, 0x74, + 0x75, 0x76, 0x79, 0xC3, 0xFF, 0x78, 0xFF, 0x82, 0xFF, 0x8E, 0xFF, 0x99, 0xFF, 0x9C, 0xFF, 0x7F, + 0xFF, 0xA3, 0xFF, 0xAF, 0xFD, 0x76, 0xFF, 0xB9, 0xFF, 0xC0, 0xFD, 0x76, 0xFF, 0xCD, 0xFF, 0xDF, + 0xFF, 0xE9, 0xFF, 0xF9, 0xFD, 0x26, 0xFF, 0xFC, 0x41, 0x65, 0xF6, 0xD7, 0x41, 0x74, 0xF7, 0x5E, + 0x21, 0x73, 0xFC, 0x41, 0x73, 0xF6, 0x97, 0x44, 0x69, 0x6F, 0x72, 0x75, 0xFF, 0xFC, 0xFC, 0xAD, + 0xFC, 0xC1, 0xF6, 0x93, 0x41, 0x67, 0xF6, 0x86, 0x42, 0x61, 0x69, 0xF6, 0x82, 0xFF, 0xFC, 0x41, + 0x70, 0xF6, 0xBC, 0x45, 0x6B, 0x6F, 0x70, 0x74, 0x75, 0xF9, 0x88, 0xFF, 0xFC, 0xF9, 0x88, 0xF9, + 0x88, 0xF7, 0x6C, 0xC1, 0x00, 0x71, 0x72, 0xFC, 0x53, 0x42, 0x6B, 0x6F, 0xFA, 0xF2, 0xF7, 0x21, + 0x42, 0x6B, 0x6C, 0xF6, 0x48, 0xFB, 0xDA, 0xC2, 0x00, 0x41, 0x61, 0x6F, 0xFF, 0xF9, 0xFA, 0x8A, + 0x41, 0xB6, 0xFA, 0xD7, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x72, 0xC3, 0xFF, 0xDF, 0xFA, 0xD3, 0xFF, + 0xE5, 0xF6, 0x46, 0xFF, 0xF3, 0xFF, 0xFC, 0xC3, 0x01, 0x91, 0x6C, 0x6E, 0x74, 0xFC, 0x61, 0xFE, + 0xEF, 0xF9, 0x44, 0xA0, 0x03, 0x72, 0x42, 0xA5, 0xB6, 0xFF, 0xFD, 0xFC, 0x3E, 0x4E, 0x61, 0x62, + 0x63, 0x68, 0x69, 0x6B, 0x6C, 0x70, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFE, 0xC3, 0xFF, 0x7B, + 0xFC, 0x74, 0xF9, 0x2E, 0xFF, 0x83, 0xFF, 0x8A, 0xF6, 0x2A, 0xFF, 0x9B, 0xFF, 0xA6, 0xFF, 0xD7, + 0xFF, 0xEA, 0xF6, 0x1D, 0xFC, 0x74, 0xFF, 0xF9, 0x41, 0x72, 0xF7, 0xBF, 0x41, 0xB6, 0xF5, 0xDC, + 0x21, 0xC3, 0xFC, 0x42, 0x6C, 0x72, 0xFC, 0x34, 0xFC, 0x84, 0xA0, 0x03, 0x92, 0x45, 0x61, 0x63, + 0x65, 0x6B, 0x73, 0xF5, 0xDD, 0xFC, 0x2A, 0xF5, 0xDD, 0xFB, 0x5D, 0xFC, 0x2A, 0xC1, 0x03, 0xB2, + 0x2E, 0xFB, 0xFB, 0xA4, 0x02, 0x01, 0x61, 0x65, 0x69, 0x6F, 0xE0, 0xE7, 0xEA, 0xFA, 0x41, 0x6B, + 0xFC, 0x30, 0x42, 0x63, 0x6B, 0xFB, 0x78, 0xF8, 0xC9, 0x44, 0x61, 0x6A, 0x6F, 0x73, 0xFF, 0xF9, + 0xF6, 0x3B, 0xFB, 0x45, 0xF5, 0xA5, 0x41, 0x67, 0xFB, 0xD2, 0x21, 0x6E, 0xFC, 0xC8, 0x01, 0xD1, + 0x61, 0x65, 0x69, 0x6A, 0x72, 0x73, 0x74, 0x75, 0xFF, 0xAB, 0xFB, 0x1D, 0xFF, 0xB3, 0xFB, 0xCB, + 0xFF, 0xD6, 0xFF, 0xE1, 0xFF, 0xEC, 0xFF, 0xFD, 0x41, 0x6D, 0xFB, 0x72, 0x43, 0x61, 0x6F, 0x73, + 0xF5, 0xBF, 0xFF, 0xFC, 0xF5, 0x6C, 0xC2, 0x03, 0x61, 0x62, 0x74, 0xF5, 0x81, 0xFF, 0xF6, 0xC1, + 0x03, 0xD2, 0x72, 0xFB, 0xC5, 0xA0, 0x03, 0xD2, 0xA0, 0x03, 0xF2, 0x21, 0xA4, 0xFD, 0x45, 0x61, + 0x67, 0x69, 0x72, 0xC3, 0xFF, 0xF1, 0xFB, 0x1C, 0xFF, 0xF7, 0xF5, 0x5C, 0xFF, 0xFD, 0x41, 0xA5, + 0xFA, 0xCC, 0x55, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xFA, 0xCB, 0xFA, 0xF1, 0xFB, 0x21, 0xFB, 0x34, + 0xFB, 0x52, 0xFB, 0x7C, 0xFB, 0x92, 0xFB, 0x98, 0xFB, 0x9F, 0xFB, 0xED, 0xFC, 0x73, 0xFC, 0xA3, + 0xFD, 0x52, 0xFB, 0x9F, 0xFD, 0xC0, 0xFE, 0x6F, 0xFF, 0x2B, 0xFF, 0xAB, 0xFF, 0xD4, 0xFF, 0xEC, + 0xFF, 0xFC, 0x43, 0x69, 0x6F, 0x73, 0xF5, 0x92, 0xF5, 0x92, 0xF5, 0x98, 0x43, 0x6C, 0x6F, 0x75, + 0xF4, 0xEC, 0xF5, 0x88, 0xF5, 0x88, 0x41, 0x6C, 0xF5, 0xB4, 0x21, 0x6B, 0xFC, 0x42, 0x61, 0x75, + 0xF5, 0x08, 0xF4, 0xFA, 0x21, 0x74, 0xF9, 0xA4, 0x03, 0x01, 0x64, 0x6B, 0x6E, 0x73, 0xDB, 0xE5, + 0xF3, 0xFD, 0x42, 0x6B, 0x74, 0xFB, 0x06, 0xFB, 0x32, 0xC1, 0x00, 0x71, 0x65, 0xFA, 0xD4, 0xC5, + 0x03, 0x31, 0x61, 0x62, 0x6C, 0x72, 0x73, 0xFF, 0xF3, 0xF4, 0xE6, 0xFF, 0xFA, 0xFE, 0xEB, 0xFD, + 0xC9, 0x41, 0x72, 0xF5, 0xCF, 0x42, 0xA5, 0xB6, 0xF4, 0xB5, 0xF4, 0xC2, 0x44, 0x64, 0x75, 0x79, + 0xC3, 0xFB, 0x4B, 0xF4, 0xBB, 0xF4, 0xAE, 0xFF, 0xF9, 0x42, 0x67, 0xC3, 0xF4, 0xD6, 0xFB, 0x99, + 0x41, 0xB6, 0xF7, 0xE0, 0x21, 0xC3, 0xFC, 0x42, 0x61, 0x65, 0xF5, 0x1D, 0xF5, 0x1D, 0xC4, 0x02, + 0x81, 0x6C, 0x73, 0x74, 0x75, 0xF4, 0x8C, 0xF4, 0x7A, 0xFF, 0xF9, 0xF4, 0x99, 0x42, 0x72, 0x76, + 0xF4, 0x7D, 0xF4, 0x7D, 0xCA, 0x03, 0x01, 0x64, 0x65, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0x74, 0x75, + 0x78, 0xFF, 0xBD, 0xF4, 0x83, 0xF7, 0xBC, 0xFF, 0xC8, 0xFF, 0xD5, 0xFF, 0xE0, 0xFF, 0xEA, 0xFF, + 0xF9, 0xF4, 0x76, 0xFB, 0x13, 0xA0, 0x04, 0x32, 0xA0, 0x04, 0x52, 0x41, 0x76, 0xFA, 0xEC, 0x42, + 0x6F, 0x76, 0xF4, 0x3F, 0xF4, 0x58, 0x21, 0x6B, 0xF9, 0xC3, 0x03, 0x01, 0x64, 0x6E, 0x73, 0xF5, + 0x57, 0xFF, 0xF2, 0xFF, 0xFD, 0x41, 0x65, 0xFA, 0x82, 0x42, 0x72, 0x74, 0xF9, 0xB1, 0xF9, 0xB1, + 0xA0, 0x04, 0x71, 0x21, 0x61, 0xFD, 0x44, 0x6D, 0x6E, 0x72, 0x73, 0xFB, 0x8F, 0xF9, 0xA4, 0xFF, + 0xFD, 0xFA, 0x14, 0x42, 0x72, 0x73, 0xFA, 0x07, 0xFA, 0x07, 0x22, 0x64, 0x6B, 0xE6, 0xF9, 0x23, + 0x61, 0x65, 0x69, 0xDA, 0xE7, 0xFB, 0x41, 0x6C, 0xF7, 0x15, 0x41, 0x75, 0xF7, 0x46, 0x41, 0x65, + 0xF4, 0xBC, 0x21, 0x74, 0xFC, 0xC1, 0x02, 0xA1, 0x6C, 0xF4, 0x1D, 0x43, 0x69, 0x72, 0x73, 0xF4, + 0x0A, 0xF4, 0x17, 0xFF, 0xFA, 0x22, 0x73, 0x74, 0xED, 0xF6, 0xC1, 0x02, 0x81, 0x63, 0xF4, 0x6A, + 0xC7, 0x03, 0x01, 0x67, 0x6B, 0x6D, 0x6E, 0x72, 0x73, 0x79, 0xF9, 0xDD, 0xFF, 0xD6, 0xFF, 0xDA, + 0xFF, 0xB0, 0xFF, 0xF5, 0xFF, 0xFA, 0xF3, 0xC8, 0x41, 0x69, 0xFF, 0x70, 0x43, 0x66, 0x72, 0x74, + 0xFF, 0x6C, 0xFA, 0x5B, 0xFF, 0xFC, 0x41, 0xB6, 0xF9, 0x34, 0x45, 0x2E, 0x61, 0x65, 0x73, 0xC3, + 0xF9, 0xA0, 0xF9, 0xFD, 0xF9, 0xA0, 0xF9, 0xA0, 0xFF, 0xFC, 0x21, 0x6B, 0xF0, 0x41, 0x74, 0xF3, + 0xB8, 0x21, 0x73, 0xFC, 0x41, 0xB6, 0xF9, 0x86, 0x45, 0x61, 0x69, 0x75, 0x79, 0xC3, 0xFF, 0xD4, + 0xFF, 0xF2, 0xFF, 0xF9, 0xF9, 0x82, 0xFF, 0xFC, 0x42, 0x65, 0x68, 0xF9, 0x02, 0xF3, 0x82, 0x41, + 0x61, 0xFC, 0x12, 0xC1, 0x00, 0xA1, 0x6A, 0xF9, 0xA5, 0xC5, 0x03, 0xF2, 0x63, 0x6C, 0x6F, 0x70, + 0x74, 0xFF, 0xEF, 0xFF, 0xF6, 0xFA, 0xC1, 0xF9, 0x9F, 0xFF, 0xFA, 0x41, 0x64, 0xF4, 0x50, 0xC2, + 0x03, 0x01, 0x6E, 0x73, 0xFF, 0xFC, 0xF5, 0xBB, 0xC1, 0x03, 0x01, 0x72, 0xF3, 0x5F, 0xC2, 0x03, + 0x01, 0x6E, 0x74, 0xF8, 0x30, 0xF5, 0xEB, 0xC1, 0x03, 0x01, 0x72, 0xF5, 0xE2, 0x43, 0xA5, 0xA4, + 0xB6, 0xFF, 0xF1, 0xF9, 0x2D, 0xFF, 0xFA, 0xD5, 0x04, 0x12, 0x61, 0x62, 0x64, 0x65, 0x66, 0x68, + 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFE, + 0x50, 0xFE, 0x68, 0xFD, 0xD1, 0xFE, 0xBD, 0xFE, 0xDE, 0xFE, 0xE1, 0xFE, 0xF2, 0xFE, 0xFE, 0xF9, + 0x80, 0xFF, 0x28, 0xFD, 0xD1, 0xFE, 0xDE, 0xFF, 0x59, 0xFD, 0xD1, 0xFF, 0xA1, 0xFF, 0xC2, 0xFD, + 0xD1, 0xFF, 0xD8, 0xF9, 0x80, 0xFF, 0xE1, 0xFF, 0xF6, 0x41, 0x70, 0xFE, 0xC7, 0x41, 0x74, 0xF3, + 0x15, 0x41, 0x72, 0xF8, 0xD9, 0x22, 0x73, 0x74, 0xF8, 0xFC, 0xC2, 0x04, 0x82, 0x6E, 0x75, 0xFF, + 0xFB, 0xFA, 0x7C, 0x41, 0x75, 0xF8, 0xC7, 0x41, 0x66, 0xF8, 0xC3, 0x41, 0x63, 0xFE, 0xA5, 0x41, + 0x72, 0xF4, 0xDE, 0x42, 0xA4, 0xB6, 0xF3, 0x96, 0xFF, 0xFC, 0x46, 0x2E, 0x61, 0x65, 0x6F, 0x74, + 0xC3, 0xF9, 0x5D, 0xFF, 0xE9, 0xFF, 0xED, 0xFF, 0xF1, 0xF9, 0x5D, 0xFF, 0xF9, 0x41, 0x70, 0xF2, + 0xE2, 0xC3, 0x02, 0xF1, 0x6C, 0x6D, 0x70, 0xF8, 0x01, 0xF3, 0x81, 0xFF, 0xFC, 0x41, 0x73, 0xF2, + 0xAA, 0xA1, 0x02, 0x81, 0x69, 0xFC, 0x43, 0x64, 0x67, 0x74, 0xFB, 0x2B, 0xF2, 0x94, 0xF8, 0xC2, + 0xA1, 0x01, 0x91, 0x72, 0xF6, 0xC1, 0x00, 0x71, 0x65, 0xF3, 0x0F, 0x41, 0x6C, 0xF5, 0xAE, 0x41, + 0xA4, 0xF2, 0x7B, 0x43, 0x61, 0x65, 0xC3, 0xF2, 0x77, 0xF2, 0x77, 0xFF, 0xFC, 0x42, 0xA4, 0xB6, + 0xF2, 0x7A, 0xF2, 0x6D, 0xCC, 0x03, 0x31, 0x61, 0x65, 0x69, 0x6A, 0x6C, 0x6E, 0x6F, 0x72, 0x73, + 0x75, 0x76, 0xC3, 0xF8, 0xB3, 0xF8, 0xB3, 0xF8, 0xB3, 0xF2, 0x73, 0xFF, 0xCD, 0xF2, 0x66, 0xFF, + 0xDC, 0xFF, 0xE1, 0xFF, 0xE7, 0xF5, 0x05, 0xFF, 0xEF, 0xFF, 0xF9, 0x41, 0x61, 0xF3, 0x17, 0x21, + 0x65, 0xFC, 0x42, 0x61, 0x6D, 0xF5, 0x49, 0xF2, 0xC2, 0xC1, 0x03, 0x31, 0x6F, 0xF4, 0xD0, 0xCB, + 0x02, 0x01, 0x61, 0x63, 0x65, 0x68, 0x69, 0x6B, 0x6C, 0x6F, 0x72, 0x73, 0x79, 0xFF, 0x3A, 0xF8, + 0x78, 0xFF, 0x4B, 0xFF, 0x6B, 0xFF, 0x82, 0xFF, 0xC5, 0xFF, 0xF0, 0xFF, 0xF3, 0xFF, 0xFA, 0xF8, + 0xC8, 0xF7, 0xEB, 0x41, 0x72, 0xF8, 0xA4, 0xC1, 0x03, 0x61, 0x6F, 0xFD, 0xD9, 0xC1, 0x04, 0x71, + 0xC3, 0xF8, 0xF5, 0x41, 0x2E, 0xF8, 0x94, 0x21, 0x64, 0xFC, 0xA0, 0x04, 0xA2, 0x42, 0x62, 0x74, + 0xF8, 0x8A, 0xF8, 0x8A, 0x42, 0x72, 0x74, 0xF2, 0x70, 0xF1, 0xD4, 0xCB, 0x03, 0x01, 0x2E, 0x64, + 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x74, 0x78, 0xFD, 0xB5, 0xFF, 0xD8, 0xF5, 0x04, 0xFF, + 0xDC, 0xF8, 0x5E, 0xFF, 0xE2, 0xFF, 0xEC, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF9, 0xF8, 0x7C, 0x41, + 0x6E, 0xF1, 0xE3, 0xA1, 0x03, 0x31, 0x65, 0xFC, 0x41, 0x73, 0xF7, 0xE0, 0x44, 0x61, 0x65, 0x69, + 0xC3, 0xF1, 0xAE, 0xF1, 0xAE, 0xF1, 0xAE, 0xFA, 0xD1, 0xC1, 0x00, 0xD1, 0x76, 0xF1, 0x95, 0xC6, + 0x03, 0x61, 0x64, 0x69, 0x6A, 0x6F, 0x72, 0x73, 0xF1, 0xB6, 0xF9, 0x37, 0xF7, 0xE8, 0xFF, 0xE9, + 0xFF, 0xED, 0xFF, 0xFA, 0xC1, 0x02, 0x82, 0x6F, 0xF8, 0x23, 0x41, 0x69, 0xF2, 0x40, 0x42, 0x61, + 0x74, 0xF2, 0x54, 0xFF, 0xFC, 0x43, 0x64, 0x73, 0x75, 0xF8, 0x12, 0xFF, 0xF9, 0xF8, 0x7F, 0x41, + 0xB6, 0xF2, 0xC4, 0x21, 0xC3, 0xFC, 0x42, 0x69, 0x6F, 0xFF, 0xFD, 0xF2, 0x3C, 0xA1, 0x00, 0x91, + 0x74, 0xF9, 0xC1, 0x00, 0xD1, 0x6C, 0xF1, 0x65, 0xC4, 0x04, 0x71, 0x6B, 0x6F, 0x73, 0x75, 0xF1, + 0xAD, 0xFF, 0xFA, 0xF4, 0xC8, 0xF1, 0xDC, 0xC3, 0x00, 0xD1, 0x65, 0x70, 0x74, 0xF1, 0x43, 0xF1, + 0x43, 0xF9, 0x0E, 0xC1, 0x00, 0x91, 0x72, 0xF1, 0x37, 0xC8, 0x04, 0xC2, 0x6B, 0x6C, 0x6E, 0x70, + 0x72, 0x73, 0x74, 0x78, 0xFF, 0xAB, 0xFF, 0xBC, 0xFF, 0xD4, 0xF2, 0x86, 0xFF, 0xDF, 0xFF, 0xEE, + 0xFF, 0xFA, 0xF7, 0xCE, 0x41, 0x72, 0xF4, 0x9F, 0x21, 0xB6, 0xFC, 0xA1, 0x03, 0xD2, 0xC3, 0xFD, + 0xC1, 0x03, 0xD2, 0x6C, 0xF7, 0x57, 0x41, 0x72, 0xF4, 0x15, 0x42, 0x61, 0x75, 0xF1, 0x00, 0xF1, + 0x0D, 0x41, 0x6F, 0xF1, 0x89, 0xC1, 0x03, 0x31, 0x70, 0xF9, 0x9B, 0x45, 0x66, 0x67, 0x72, 0x73, + 0x74, 0xF7, 0x8C, 0xFF, 0xF6, 0xF7, 0x8C, 0xFF, 0xFA, 0xF7, 0x8C, 0xC1, 0x02, 0x81, 0x64, 0xF1, + 0x69, 0x42, 0x6F, 0x72, 0xF0, 0xCD, 0xF1, 0x34, 0x41, 0x61, 0xF0, 0xC6, 0x42, 0x6F, 0x72, 0xF0, + 0xBC, 0xFF, 0xFC, 0x45, 0x63, 0x6A, 0x6B, 0x70, 0x74, 0xF0, 0xC7, 0xF1, 0x51, 0xFF, 0xEE, 0xF1, + 0x22, 0xFF, 0xF9, 0x41, 0x72, 0xF8, 0xB2, 0xC8, 0x03, 0x01, 0x61, 0x65, 0x67, 0x6B, 0x6E, 0x6F, + 0x73, 0x74, 0xF1, 0xA4, 0xF0, 0xCB, 0xFF, 0xAF, 0xFF, 0xB3, 0xFF, 0xC4, 0xFF, 0xD4, 0xFF, 0xEC, + 0xFF, 0xFC, 0x41, 0x72, 0xF6, 0xE5, 0x42, 0x70, 0x72, 0xF9, 0x96, 0xF6, 0x84, 0xA2, 0x03, 0x61, + 0x6F, 0x75, 0xF5, 0xF9, 0xA0, 0x04, 0xE3, 0xA0, 0x05, 0x12, 0xC1, 0x02, 0x01, 0x6F, 0xF0, 0x8D, + 0x42, 0x72, 0x73, 0xF7, 0x17, 0xF4, 0x4F, 0x41, 0x6B, 0xF0, 0xFD, 0x21, 0x61, 0xFC, 0x43, 0x64, + 0x69, 0x74, 0xFF, 0xE9, 0xF7, 0x09, 0xF7, 0x09, 0x41, 0x74, 0xF5, 0xE2, 0xC7, 0x03, 0x01, 0x6C, + 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x79, 0xFF, 0xDE, 0xFF, 0xE4, 0xFC, 0x34, 0xFF, 0xEF, 0xFF, 0xF2, + 0xFF, 0xFC, 0xF0, 0x5E, 0xA0, 0x05, 0x62, 0x41, 0x64, 0xF6, 0x90, 0x41, 0x62, 0xF5, 0xBF, 0x41, + 0x61, 0xF5, 0xBB, 0x21, 0x72, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x75, 0xF5, 0xAE, + 0x46, 0x64, 0x67, 0x6E, 0x72, 0x73, 0x74, 0xF6, 0x77, 0xFF, 0xE4, 0xFF, 0xE7, 0xFF, 0xEB, 0xFF, + 0xF9, 0xFF, 0xFC, 0x44, 0x6A, 0x6E, 0x73, 0x74, 0xF6, 0x07, 0xF6, 0x64, 0xFB, 0xED, 0xF6, 0x64, + 0x46, 0x63, 0x66, 0x67, 0x6B, 0x6E, 0x76, 0xF6, 0x38, 0xF5, 0xFA, 0xF6, 0x57, 0xFF, 0x87, 0xF6, + 0x57, 0xFF, 0xC4, 0x41, 0x70, 0xF5, 0xE7, 0xC1, 0x03, 0x31, 0x74, 0xF3, 0xDE, 0x44, 0x63, 0x70, + 0x72, 0x74, 0xF5, 0x6D, 0xFF, 0xF6, 0xF6, 0x3A, 0xFF, 0xFA, 0x42, 0x6E, 0x74, 0xF6, 0x2D, 0xF5, + 0xD0, 0x43, 0x6B, 0x6E, 0x74, 0xFF, 0xF9, 0xFB, 0xAF, 0xF6, 0x26, 0x42, 0xA4, 0xB6, 0xFF, 0xF6, + 0xF5, 0xBB, 0xA5, 0x05, 0x33, 0x61, 0x65, 0x69, 0x6F, 0xC3, 0x9E, 0xB1, 0xBE, 0xDB, 0xF9, 0xA0, + 0x05, 0x82, 0x21, 0x6E, 0xFD, 0x41, 0x68, 0xEF, 0xC2, 0xA1, 0x02, 0x01, 0x6E, 0xFC, 0xC1, 0x02, + 0x01, 0x6E, 0xF9, 0x15, 0x42, 0x66, 0x6E, 0xF5, 0xF3, 0xF0, 0x30, 0xC4, 0x01, 0x01, 0x65, 0x69, + 0x6C, 0x6E, 0xFF, 0xF9, 0xF5, 0xEC, 0xEF, 0x9F, 0xEF, 0xAC, 0x41, 0x74, 0xF0, 0x1A, 0xA1, 0x02, + 0x81, 0x6F, 0xFC, 0x41, 0xA5, 0xF5, 0xB5, 0x42, 0x6F, 0xC3, 0xF0, 0x0D, 0xFF, 0xFC, 0x41, 0xA5, + 0xEF, 0x89, 0x21, 0xC3, 0xFC, 0x42, 0x6C, 0x74, 0xEF, 0x75, 0xF4, 0xF5, 0xC1, 0x00, 0x41, 0x61, + 0xEF, 0xA3, 0x42, 0x67, 0x6E, 0xF4, 0xE8, 0xEF, 0x68, 0x41, 0x6F, 0xEF, 0x6E, 0x24, 0x61, 0x65, + 0x69, 0x72, 0xE8, 0xEF, 0xF5, 0xFC, 0x41, 0xB6, 0xF5, 0x6E, 0xD0, 0x03, 0x31, 0x61, 0x65, 0x66, + 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xFF, 0x98, 0xFF, + 0x9F, 0xF5, 0x7E, 0xFF, 0xA4, 0xF5, 0x6A, 0xFF, 0xB1, 0xFF, 0xC4, 0xFF, 0xCD, 0xFF, 0xD8, 0xF5, + 0x6A, 0xF5, 0x18, 0xF0, 0x66, 0xFF, 0xF3, 0xF5, 0x6A, 0xF2, 0x96, 0xFF, 0xFC, 0x41, 0x65, 0xEF, + 0x11, 0x21, 0x6C, 0xFC, 0x21, 0x62, 0xFD, 0x41, 0xB6, 0xEF, 0x20, 0xC3, 0x02, 0x81, 0x63, 0x74, + 0xC3, 0xF2, 0x20, 0xF7, 0x0A, 0xFF, 0xFC, 0xC5, 0x03, 0x01, 0x62, 0x70, 0x73, 0x74, 0x76, 0xFF, + 0xED, 0xF5, 0xA0, 0xFF, 0xF4, 0xF5, 0xA0, 0xF4, 0x5B, 0xC1, 0x03, 0xD2, 0x72, 0xF5, 0x3E, 0xC2, + 0x03, 0x01, 0x6B, 0x72, 0xF7, 0x29, 0xF7, 0xE9, 0x43, 0x67, 0x73, 0x74, 0xFA, 0xB8, 0xF7, 0x88, + 0xF5, 0x7F, 0x42, 0x67, 0x72, 0xF5, 0x75, 0xEF, 0xB0, 0x41, 0x73, 0xEF, 0xAC, 0xC4, 0x03, 0x01, + 0x64, 0x67, 0x70, 0x73, 0xFF, 0xFC, 0xF5, 0x6A, 0xF5, 0x6A, 0xFE, 0x6B, 0x44, 0xA5, 0xA4, 0xB6, + 0xA9, 0xFF, 0xDC, 0xFF, 0xE6, 0xFF, 0xF1, 0xEE, 0xD9, 0xD9, 0x02, 0xF1, 0x61, 0x62, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x79, 0x7A, 0xC3, 0xFC, 0xD2, 0xF7, 0x57, 0xFC, 0xFA, 0xFD, 0x16, 0xFD, 0x80, 0xFD, + 0xA2, 0xFD, 0xA7, 0xFA, 0x5F, 0xFD, 0xFE, 0xFE, 0x24, 0xFE, 0x2B, 0xFE, 0x2E, 0xF9, 0x4C, 0xF9, + 0x4C, 0xFE, 0x53, 0xF9, 0x4C, 0xFE, 0xE9, 0xFF, 0x61, 0xFA, 0x5C, 0xFF, 0xAE, 0xFF, 0xC0, 0xFA, + 0x5C, 0xFF, 0xC6, 0xF5, 0x4E, 0xFF, 0xF3, 0x41, 0x65, 0xF4, 0x7D, 0xC2, 0x01, 0xD1, 0x6B, 0x6D, + 0xFF, 0xFC, 0xF4, 0x8D, 0xC1, 0x02, 0x01, 0x69, 0xF4, 0x1E, 0xA1, 0x03, 0x31, 0x72, 0xFA, 0x41, + 0x66, 0xF1, 0x09, 0x21, 0x66, 0xFC, 0xA1, 0x01, 0x91, 0x69, 0xFD, 0xC3, 0x03, 0x31, 0x68, 0x69, + 0x6F, 0xFF, 0xFB, 0xEF, 0x30, 0xF4, 0x8C, 0x41, 0x6E, 0xFC, 0x81, 0x41, 0x75, 0xEE, 0x4A, 0x41, + 0x6C, 0xF3, 0xAB, 0x21, 0x61, 0xFC, 0xC1, 0x02, 0x81, 0x6F, 0xF1, 0x35, 0xA0, 0x05, 0xA2, 0x43, + 0x6B, 0x6C, 0x6F, 0xFF, 0xF7, 0xFF, 0xFD, 0xEE, 0x50, 0x41, 0xA5, 0xF4, 0x5E, 0xC7, 0x02, 0x01, + 0x61, 0x64, 0x69, 0x6A, 0x72, 0x73, 0xC3, 0xFF, 0xDA, 0xEE, 0x2B, 0xFF, 0xDE, 0xEE, 0x0D, 0xFF, + 0xE6, 0xFF, 0xF2, 0xFF, 0xFC, 0x42, 0x2E, 0x65, 0xF4, 0x23, 0xF4, 0x23, 0xC4, 0x01, 0xD1, 0x64, + 0x69, 0x6B, 0x6E, 0xF4, 0x08, 0xF4, 0x1C, 0xEE, 0x2F, 0xFF, 0xF9, 0x41, 0x65, 0xF3, 0xCF, 0xC2, + 0x01, 0xD1, 0x73, 0x74, 0xEE, 0x65, 0xFF, 0xFC, 0xC1, 0x01, 0x91, 0x6C, 0xEE, 0xAA, 0xC2, 0x00, + 0xD1, 0x61, 0x69, 0xF4, 0x19, 0xF3, 0x4C, 0x21, 0x72, 0xF7, 0xA1, 0x02, 0x81, 0x65, 0xFD, 0x42, + 0x6E, 0x74, 0xF3, 0xE9, 0xED, 0xC8, 0x41, 0x6E, 0xED, 0xB4, 0x43, 0x61, 0x69, 0x75, 0xFF, 0xF5, + 0xFF, 0xFC, 0xF3, 0x30, 0xC6, 0x01, 0xD1, 0x61, 0x65, 0x6C, 0x72, 0x73, 0xC3, 0xF3, 0xF3, 0xFF, + 0xD4, 0xFF, 0xE6, 0xFF, 0xF6, 0xED, 0x9A, 0xF8, 0x5A, 0xC1, 0x01, 0x01, 0x6E, 0xED, 0x9E, 0x42, + 0x68, 0x6B, 0xED, 0x98, 0xF3, 0xE2, 0x44, 0x67, 0x6B, 0x6E, 0x73, 0xFF, 0xF3, 0xF3, 0xD1, 0xF3, + 0xDB, 0xFF, 0xF9, 0x42, 0xA4, 0xB6, 0xF3, 0xC4, 0xF2, 0xF7, 0x43, 0x6F, 0x75, 0xC3, 0xF3, 0xC7, + 0xF3, 0xBD, 0xFF, 0xF9, 0xC2, 0x01, 0xD1, 0x61, 0x65, 0xED, 0xC7, 0xED, 0xF0, 0x42, 0x64, 0x72, + 0xEE, 0xE3, 0xED, 0x5D, 0xA1, 0x01, 0xD1, 0x72, 0xF9, 0x41, 0x2E, 0xED, 0xDB, 0x21, 0x74, 0xFC, + 0x42, 0x65, 0x6F, 0xFF, 0xFD, 0xED, 0x57, 0x42, 0x65, 0x69, 0xF3, 0x9A, 0xFA, 0xA6, 0x41, 0xA4, + 0xF3, 0x93, 0x4A, 0x61, 0x69, 0x6C, 0x6E, 0x6F, 0x72, 0x75, 0x76, 0x79, 0xC3, 0xF3, 0x85, 0xF3, + 0x8F, 0xFF, 0xD2, 0xED, 0x38, 0xFF, 0xE2, 0xFF, 0xEE, 0xF3, 0x8F, 0xFF, 0xF5, 0xF3, 0x8F, 0xFF, + 0xFC, 0x41, 0x74, 0xF0, 0x5F, 0x41, 0x62, 0xED, 0x9F, 0xA2, 0x01, 0xD1, 0x6B, 0x72, 0xF8, 0xFC, + 0x41, 0x2E, 0xF2, 0xFA, 0x41, 0x69, 0xED, 0xD2, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x42, 0x62, + 0x6B, 0xEE, 0x77, 0xFF, 0xFD, 0x41, 0x72, 0xF3, 0x69, 0x41, 0x6B, 0xF4, 0xB8, 0x43, 0x69, 0x73, + 0xC3, 0xEC, 0xED, 0xEC, 0xE1, 0xFA, 0x72, 0xC1, 0x01, 0xD1, 0x62, 0xF3, 0x11, 0x41, 0x69, 0xEF, + 0xEE, 0x42, 0x67, 0x76, 0xEC, 0xE6, 0xF2, 0x59, 0x41, 0xA4, 0xF7, 0x6D, 0x4C, 0x61, 0x64, 0x65, + 0x66, 0x69, 0x6B, 0x6C, 0x6F, 0x70, 0x73, 0x75, 0xC3, 0xFF, 0xBD, 0xFF, 0xC4, 0xFF, 0xD2, 0xFF, + 0xD9, 0xFF, 0xDD, 0xED, 0xE4, 0xFF, 0xE1, 0xFF, 0xEB, 0xEC, 0xCE, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, + 0xFC, 0x41, 0x73, 0xF2, 0x29, 0x41, 0x73, 0xEF, 0x8B, 0x42, 0x6E, 0x72, 0xFF, 0xFC, 0xF3, 0xAB, + 0x41, 0x65, 0xEC, 0x88, 0x21, 0x6C, 0xFC, 0xC4, 0x01, 0xD1, 0x61, 0x69, 0x6F, 0x70, 0xFC, 0x31, + 0xFF, 0xEA, 0xFF, 0xF2, 0xFF, 0xFD, 0x42, 0x72, 0x76, 0xF3, 0x8E, 0xF2, 0x9E, 0x41, 0xB6, 0xEC, + 0x71, 0x21, 0xC3, 0xFC, 0x43, 0x64, 0x6B, 0x72, 0xF2, 0xA4, 0xF2, 0xA4, 0xFF, 0xFD, 0x41, 0xA4, + 0xEC, 0x79, 0x44, 0x6C, 0x6E, 0x72, 0xC3, 0xFD, 0x07, 0xEC, 0x68, 0xEC, 0x75, 0xFF, 0xFC, 0xC1, + 0x02, 0x61, 0x73, 0xEE, 0x87, 0x21, 0x67, 0xFA, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x69, + 0xEF, 0x38, 0x41, 0xA5, 0xEC, 0xD2, 0x47, 0x69, 0x6B, 0x6D, 0x70, 0x74, 0x76, 0xC3, 0xEC, 0x44, + 0xFF, 0xAA, 0xED, 0x1C, 0xEF, 0x55, 0xEE, 0xBE, 0xFF, 0xF8, 0xFF, 0xFC, 0x42, 0x2E, 0x69, 0xEC, + 0x49, 0xEC, 0x22, 0x21, 0x74, 0xF9, 0xC3, 0x01, 0xC2, 0x61, 0x69, 0x75, 0xFF, 0xFD, 0xEC, 0x18, + 0xEC, 0x12, 0x42, 0x68, 0x72, 0xF2, 0x29, 0xFF, 0xF4, 0x4C, 0x61, 0x63, 0x65, 0x6A, 0x6B, 0x6E, + 0x6F, 0x73, 0x74, 0x75, 0x79, 0xC3, 0xFF, 0x8D, 0xF4, 0x0C, 0xFF, 0x9B, 0xF4, 0xB7, 0xFF, 0xA9, + 0xFF, 0xC2, 0xED, 0xDA, 0xFF, 0xCD, 0xFF, 0xF9, 0xFF, 0x68, 0xF4, 0xB7, 0xFF, 0x3F, 0xC3, 0x01, + 0xD1, 0x67, 0x69, 0x72, 0xF2, 0x06, 0xEC, 0x76, 0xF1, 0x6C, 0x42, 0x61, 0x65, 0xF2, 0x2D, 0xEB, + 0xED, 0xC1, 0x05, 0xC2, 0x74, 0xEC, 0x99, 0x21, 0x73, 0xFA, 0x41, 0x62, 0xEB, 0xC4, 0xC9, 0x03, + 0x61, 0x65, 0x69, 0x6C, 0x70, 0x72, 0x73, 0x74, 0x75, 0xC3, 0xF2, 0x23, 0xF2, 0x23, 0xFF, 0xEC, + 0xFF, 0xF9, 0xF2, 0x23, 0xEB, 0xCC, 0xF1, 0xCF, 0xFF, 0xFC, 0xFD, 0xBB, 0x42, 0x6B, 0x73, 0xEE, + 0xF4, 0xF2, 0x4B, 0x41, 0xA5, 0xF1, 0x3B, 0x43, 0x6C, 0x75, 0xC3, 0xEB, 0xBE, 0xEC, 0x2D, 0xFF, + 0xFC, 0x42, 0x66, 0x67, 0xF1, 0xC7, 0xEF, 0x84, 0xC3, 0x01, 0xD1, 0x62, 0x6E, 0x73, 0xF1, 0x12, + 0xFF, 0xF9, 0xF2, 0x76, 0x41, 0x6C, 0xFF, 0x3A, 0x41, 0xA4, 0xED, 0x95, 0x21, 0xC3, 0xFC, 0x41, + 0x61, 0xEB, 0x88, 0xC2, 0x01, 0xD1, 0x62, 0x6D, 0xF1, 0xC4, 0xF1, 0x91, 0x41, 0x65, 0xEE, 0x5A, + 0xC1, 0x00, 0x71, 0x65, 0xEB, 0x85, 0x43, 0x61, 0x65, 0x72, 0xEB, 0xEE, 0xEE, 0x75, 0xEB, 0x64, + 0x45, 0x63, 0x6B, 0x6C, 0x74, 0x76, 0xEB, 0x67, 0xFF, 0xEC, 0xFF, 0xF0, 0xFF, 0xF6, 0xEB, 0x5A, + 0xC1, 0x01, 0xD1, 0x6D, 0xF0, 0xCA, 0x42, 0xA5, 0xA4, 0xF1, 0x30, 0xF1, 0x9B, 0x4E, 0x61, 0x64, + 0x67, 0x69, 0x6B, 0x6C, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x79, 0xC3, 0xFF, 0x8F, 0xEC, 0x53, + 0xFF, 0x9A, 0xFF, 0xAB, 0xFF, 0xB7, 0xFF, 0xBF, 0xFF, 0xC2, 0xFF, 0xC6, 0xEB, 0x58, 0xEE, 0x6C, + 0xFF, 0xE3, 0xFF, 0xF3, 0xF1, 0x8A, 0xFF, 0xF9, 0xC1, 0x02, 0x01, 0x6C, 0xEB, 0x1F, 0x41, 0x6D, + 0xEB, 0x0C, 0x22, 0x6C, 0x72, 0xF6, 0xFC, 0x41, 0x69, 0xEB, 0x8D, 0x42, 0x6E, 0x72, 0xF1, 0x2D, + 0xEB, 0x0C, 0x42, 0x6E, 0x72, 0xF5, 0x18, 0xEA, 0xF8, 0xC3, 0x00, 0xD1, 0x61, 0x65, 0x75, 0xFF, + 0xF2, 0xFF, 0xF9, 0xEB, 0x09, 0xC1, 0x01, 0xD1, 0x61, 0xF0, 0x91, 0xC1, 0x01, 0xD1, 0x6C, 0xEA, + 0xDF, 0x41, 0xA4, 0xEA, 0xC7, 0x21, 0xC3, 0xFC, 0x43, 0x6B, 0x6C, 0x74, 0xED, 0xE3, 0xFF, 0xFD, + 0xED, 0xE3, 0x41, 0x6C, 0xF1, 0x15, 0x41, 0x2E, 0xEA, 0xD1, 0x21, 0x6E, 0xFC, 0x42, 0x67, 0x6E, + 0xEF, 0x3C, 0xEA, 0xBD, 0x21, 0x72, 0xF2, 0x41, 0x72, 0xEA, 0xA1, 0x41, 0xB6, 0xEA, 0xCA, 0xC3, + 0x00, 0x41, 0x65, 0x75, 0xC3, 0xFF, 0xF8, 0xF0, 0xF8, 0xFF, 0xFC, 0x46, 0x61, 0x65, 0x69, 0x6F, + 0x72, 0xC3, 0xFF, 0xD7, 0xFF, 0xDF, 0xFF, 0xE2, 0xFF, 0xE9, 0xFF, 0xF4, 0xFC, 0x8E, 0x4D, 0x61, + 0x63, 0x65, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x70, 0x73, 0x74, 0x75, 0x79, 0xFF, 0x84, 0xF0, 0xE3, + 0xF3, 0x32, 0xFF, 0x89, 0xFC, 0x5D, 0xFF, 0x9B, 0xFF, 0xA7, 0xFD, 0xAF, 0xFF, 0xAD, 0xFF, 0xBA, + 0xFF, 0xED, 0xED, 0x9D, 0xF0, 0xE3, 0xC1, 0x00, 0xE1, 0xC3, 0xFD, 0xE7, 0x42, 0x2E, 0x73, 0xEF, + 0xDE, 0xEA, 0x5E, 0x42, 0x69, 0xC3, 0xFF, 0xF9, 0xF2, 0xCB, 0x41, 0x64, 0xEB, 0x28, 0xC6, 0x00, + 0xD1, 0x61, 0x6B, 0x6C, 0x6D, 0x70, 0x76, 0xFF, 0xFC, 0xF1, 0xFA, 0xEA, 0xA7, 0xEA, 0x40, 0xEC, + 0x19, 0xEA, 0xD6, 0x41, 0x69, 0xEA, 0x52, 0xA1, 0x01, 0xD1, 0x72, 0xFC, 0x42, 0x61, 0x75, 0xEA, + 0xB8, 0xEA, 0x3B, 0xC1, 0x01, 0xD1, 0x63, 0xEC, 0x3A, 0x21, 0xA4, 0xFA, 0x4B, 0x61, 0x69, 0x6F, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0x7A, 0xC3, 0xFE, 0x4E, 0xFF, 0xBA, 0xF0, 0x75, 0xFF, 0xC7, + 0xFF, 0xD2, 0xF1, 0xCC, 0xFF, 0xEB, 0xFF, 0xF0, 0xF0, 0x75, 0xF2, 0xC4, 0xFF, 0xFD, 0x41, 0x2E, + 0xEF, 0x7C, 0xA0, 0x05, 0xE2, 0x41, 0x65, 0xFC, 0xA4, 0x41, 0x72, 0xFA, 0x90, 0x46, 0x6B, 0x6D, + 0x70, 0x72, 0x73, 0x74, 0xEA, 0x08, 0xFF, 0xF1, 0xFF, 0xF5, 0xF0, 0x8A, 0xFF, 0xF8, 0xFF, 0xFC, + 0x41, 0x6D, 0xF0, 0x08, 0x21, 0x72, 0xFC, 0xC3, 0x01, 0xD1, 0x61, 0x6A, 0x72, 0xFF, 0xFD, 0xF0, + 0x01, 0xE9, 0xD3, 0x42, 0x70, 0x74, 0xEF, 0xB7, 0xE9, 0xE5, 0x41, 0xA4, 0xF0, 0x5D, 0x56, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x78, 0xC3, 0xFB, 0x5D, 0xFB, 0x6C, 0xFB, 0x7D, 0xFB, 0xAF, 0xFB, 0xCE, 0xFB, + 0xE1, 0xFC, 0x16, 0xFC, 0x38, 0xFC, 0x4C, 0xFC, 0x84, 0xFC, 0xEE, 0xFD, 0x29, 0xFD, 0xAB, 0xFD, + 0xD0, 0xFD, 0xF0, 0xFE, 0x7F, 0xFF, 0x30, 0xFF, 0x9E, 0xFF, 0xCF, 0xFF, 0xE9, 0xFF, 0xF5, 0xFF, + 0xFC, 0xC1, 0x00, 0xA1, 0x6B, 0xFC, 0xDF, 0x41, 0x73, 0xF0, 0x10, 0x41, 0x62, 0xF0, 0x0C, 0x42, + 0x68, 0x74, 0xE9, 0x6B, 0xE9, 0xF5, 0x41, 0xB6, 0xE9, 0xEE, 0x21, 0xC3, 0xFC, 0xC6, 0x03, 0x01, + 0x63, 0x6E, 0x72, 0x73, 0x74, 0x76, 0xFF, 0xE4, 0xFF, 0xEA, 0xFF, 0xEE, 0xFF, 0xF2, 0xFF, 0xFD, + 0xEF, 0xFA, 0x42, 0x61, 0x6F, 0xE9, 0x36, 0xE9, 0x48, 0x21, 0x74, 0xF9, 0xC4, 0x03, 0x01, 0x66, + 0x6C, 0x73, 0x78, 0xEF, 0xDB, 0xEC, 0x4F, 0xFF, 0xFD, 0xEF, 0xDB, 0x41, 0x72, 0xF5, 0x33, 0x42, + 0xA4, 0xB6, 0xEF, 0x78, 0xFF, 0xFC, 0xC7, 0x03, 0xD2, 0x66, 0x6C, 0x6E, 0x6F, 0x72, 0x73, 0xC3, + 0xE9, 0x3F, 0xE9, 0x24, 0xE9, 0x24, 0xEF, 0x71, 0xE9, 0x24, 0xE9, 0x3F, 0xFF, 0xF9, 0xA0, 0x04, + 0xF2, 0x41, 0x67, 0xE8, 0xF7, 0xA1, 0x00, 0xD1, 0x69, 0xFC, 0x21, 0x72, 0xFB, 0x41, 0x6D, 0xE8, + 0xEB, 0x21, 0x73, 0xFC, 0x42, 0x61, 0x72, 0xEE, 0xBE, 0xE9, 0x1E, 0x21, 0x6B, 0xF9, 0xC5, 0x03, + 0x01, 0x62, 0x6C, 0x6E, 0x73, 0x74, 0xFF, 0xEC, 0xEF, 0x6B, 0xFF, 0xF3, 0xFF, 0xFD, 0xFC, 0x0F, + 0x41, 0x64, 0xEE, 0xEB, 0xA1, 0x02, 0xF1, 0x6F, 0xFC, 0xC1, 0x03, 0xF2, 0x61, 0xE8, 0xEC, 0xC1, + 0x01, 0x01, 0x6B, 0xEB, 0xB1, 0x41, 0x61, 0xEB, 0xD6, 0x41, 0x6D, 0xF0, 0x2C, 0x41, 0x74, 0xEA, + 0x38, 0xC6, 0x02, 0xF1, 0x66, 0x6C, 0x6D, 0x6E, 0x72, 0x73, 0xEF, 0x56, 0xFF, 0xEE, 0xF4, 0x67, + 0xFF, 0xF4, 0xFF, 0xF8, 0xFF, 0xFC, 0xC1, 0x01, 0x01, 0x70, 0xE9, 0x64, 0x21, 0x6D, 0xFA, 0xC1, + 0x00, 0x91, 0x69, 0xF4, 0x71, 0x42, 0x72, 0x73, 0xFF, 0xFA, 0xE8, 0xB0, 0x43, 0x2E, 0x73, 0x74, + 0xEE, 0xBC, 0xE9, 0x83, 0xF4, 0x64, 0x41, 0x61, 0xE9, 0x44, 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, + 0x41, 0x74, 0xFD, 0xC7, 0x21, 0x73, 0xFC, 0x41, 0xA5, 0xE9, 0xCF, 0x26, 0x61, 0x65, 0x69, 0x6F, + 0x75, 0xC3, 0xD1, 0xDA, 0xE1, 0xF2, 0xF9, 0xFC, 0xC7, 0x04, 0x32, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, + 0x74, 0x76, 0xEB, 0x73, 0xEE, 0x90, 0xEE, 0x90, 0xEE, 0x90, 0xEE, 0x90, 0xEE, 0x90, 0xEE, 0x90, + 0xC4, 0x01, 0x91, 0x66, 0x6B, 0x70, 0x72, 0xEE, 0x78, 0xEE, 0x78, 0xEE, 0x78, 0xFE, 0x70, 0x42, + 0x72, 0x78, 0xE8, 0x56, 0xEE, 0x69, 0xC1, 0x01, 0x91, 0x6E, 0xEE, 0x62, 0x46, 0x61, 0x65, 0x69, + 0x6A, 0x74, 0x76, 0xEB, 0x3F, 0xF8, 0x9E, 0xE8, 0x6F, 0xE8, 0xB8, 0xE8, 0x49, 0xFD, 0x59, 0xC8, + 0x03, 0x61, 0x61, 0x65, 0x69, 0x6F, 0x72, 0x73, 0x74, 0x76, 0xFF, 0xD1, 0xFF, 0xE0, 0xFF, 0xE7, + 0xEE, 0x68, 0xF2, 0x3B, 0xFF, 0xED, 0xE8, 0x28, 0xEB, 0x61, 0x42, 0x73, 0x75, 0xF4, 0x08, 0xE8, + 0x0D, 0x41, 0x75, 0xE7, 0xF9, 0xA2, 0x03, 0x01, 0x72, 0x74, 0xF5, 0xFC, 0xC1, 0x04, 0x71, 0x6D, + 0xEE, 0x62, 0x41, 0x74, 0xFB, 0x6F, 0x41, 0x74, 0xF6, 0x64, 0xA1, 0x02, 0xF1, 0x73, 0xFC, 0x41, + 0x6E, 0xE8, 0xB3, 0x42, 0x6D, 0x75, 0xE7, 0xFF, 0xE7, 0xFF, 0x41, 0xB6, 0xEA, 0xB6, 0x47, 0x61, + 0x65, 0x69, 0x6B, 0x73, 0x74, 0xC3, 0xE8, 0xA4, 0xFF, 0xF1, 0xE8, 0xA4, 0xE7, 0xF4, 0xFF, 0xF5, + 0xE9, 0xDF, 0xFF, 0xFC, 0xA1, 0x03, 0x01, 0x72, 0xEA, 0x23, 0xA5, 0xA4, 0xB6, 0xC9, 0xD1, 0xFB, + 0xD4, 0x02, 0x01, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFE, 0x4D, 0xF2, 0x48, 0xED, 0xF7, 0xFE, 0x6C, 0xFE, + 0x86, 0xFE, 0x9E, 0xED, 0x2A, 0xFE, 0xBE, 0xF3, 0x55, 0xFE, 0xD4, 0xFE, 0xD9, 0xFE, 0xF1, 0xF2, + 0x48, 0xFF, 0x3B, 0xFF, 0x48, 0xFF, 0x8F, 0xFF, 0xB5, 0xEE, 0x47, 0xFF, 0xBC, 0xFF, 0xF9, 0xA0, + 0x04, 0xC2, 0x41, 0x74, 0xEE, 0x05, 0xC2, 0x04, 0xC2, 0x6C, 0xC3, 0xEA, 0x89, 0xFA, 0xF8, 0x44, + 0x62, 0x6D, 0x6F, 0x74, 0xF0, 0x01, 0xED, 0xF8, 0xEA, 0x6C, 0xED, 0xF8, 0x42, 0x74, 0x75, 0xE7, + 0xD8, 0xE7, 0xD8, 0xA0, 0x06, 0x02, 0x21, 0x69, 0xFD, 0xC1, 0x00, 0x91, 0x73, 0xEC, 0xFE, 0x4B, + 0x62, 0x67, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0x74, 0x75, 0x76, 0x78, 0xEB, 0xC8, 0xFF, 0xD0, 0xFF, + 0xD3, 0xFF, 0xD7, 0xF0, 0x39, 0xFF, 0xE0, 0xFF, 0xED, 0xFF, 0xF7, 0xE8, 0x2C, 0xFF, 0xFA, 0xED, + 0xD8, 0xA0, 0x03, 0xB2, 0xA1, 0x03, 0x61, 0x72, 0xFD, 0xA0, 0x06, 0x22, 0xC1, 0x02, 0x01, 0x73, + 0xEC, 0xFE, 0xC1, 0x03, 0x01, 0x6E, 0xE6, 0xF6, 0x21, 0xA4, 0xFA, 0x44, 0x69, 0x73, 0x79, 0xC3, + 0xFF, 0xF1, 0xE8, 0x21, 0xE6, 0xED, 0xFF, 0xFD, 0x41, 0x70, 0xE7, 0x7C, 0x41, 0x6D, 0xE8, 0x47, + 0x42, 0x61, 0x65, 0xE7, 0x05, 0xED, 0x18, 0xA0, 0x04, 0x12, 0x4B, 0x61, 0x67, 0x69, 0x6A, 0x6D, + 0x6F, 0x73, 0x74, 0x76, 0x79, 0xC3, 0xFF, 0xEE, 0xE6, 0xCE, 0xFF, 0x75, 0xEC, 0xD0, 0xED, 0x7D, + 0xFF, 0xF2, 0xFF, 0xF6, 0xFF, 0xFD, 0xED, 0x7D, 0xE6, 0xED, 0xEC, 0xD3, 0xC1, 0x04, 0x12, 0x72, + 0xFD, 0x4F, 0x41, 0x6E, 0xE7, 0x78, 0x43, 0x61, 0x69, 0x73, 0xFF, 0xF6, 0xFF, 0x49, 0xFF, 0xFC, + 0x41, 0x65, 0xF2, 0x80, 0x43, 0x6F, 0x73, 0x74, 0xE7, 0x30, 0xE6, 0x94, 0xFF, 0x5F, 0x4B, 0x61, + 0x62, 0x66, 0x6A, 0x6C, 0x6D, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0xE9, 0xAD, 0xE6, 0xA6, 0xEF, 0x42, + 0xFF, 0x8B, 0xFF, 0x9D, 0xEC, 0xCA, 0xFF, 0xBC, 0xE9, 0xAD, 0xFF, 0xE8, 0xFF, 0xF2, 0xFF, 0xF6, + 0xC1, 0x01, 0xD1, 0x70, 0xEC, 0xA8, 0x44, 0x6C, 0x6D, 0x74, 0x76, 0xEC, 0x85, 0xE6, 0x8F, 0xE7, + 0x8A, 0xE7, 0x8A, 0xC7, 0x03, 0x61, 0x61, 0x65, 0x67, 0x6C, 0x6F, 0x73, 0x75, 0xFF, 0xED, 0xEC, + 0xBE, 0xE6, 0x74, 0xE9, 0xAD, 0xF4, 0xB5, 0xFF, 0xF3, 0xEC, 0x95, 0xC1, 0x03, 0x61, 0x74, 0xEE, + 0x4A, 0xA0, 0x06, 0x42, 0x42, 0x6C, 0x74, 0xE6, 0x53, 0xFF, 0xFD, 0xA1, 0x00, 0xA1, 0x61, 0xF9, + 0x42, 0x65, 0x72, 0xE6, 0xC4, 0xFF, 0xFB, 0x42, 0x6E, 0x74, 0xE6, 0x40, 0xFF, 0xF9, 0x41, 0xB6, + 0xE9, 0x3D, 0xC4, 0x04, 0x12, 0x6F, 0x73, 0x76, 0xC3, 0xE9, 0x6E, 0xFF, 0xF5, 0xEC, 0x18, 0xFF, + 0xFC, 0xC1, 0x02, 0x01, 0x72, 0xEC, 0x09, 0x42, 0x6F, 0xC3, 0xFF, 0xFA, 0xEF, 0x36, 0x41, 0xB6, + 0xF1, 0xE2, 0xC3, 0x02, 0x01, 0x65, 0x6B, 0xC3, 0xF1, 0xDE, 0xE6, 0xC8, 0xFF, 0xFC, 0x43, 0x6E, + 0x72, 0x73, 0xF4, 0x4A, 0xEC, 0x53, 0xFF, 0xF4, 0x42, 0x67, 0x6D, 0xEC, 0x20, 0xF1, 0xC8, 0x21, + 0xB6, 0xF9, 0xC4, 0x03, 0x61, 0x61, 0x69, 0x6A, 0xC3, 0xFF, 0xEC, 0xEE, 0xA4, 0xEC, 0x85, 0xFF, + 0xFD, 0xC2, 0x03, 0xD2, 0x61, 0x6F, 0xF0, 0x35, 0xEB, 0xF3, 0x42, 0x73, 0x74, 0xE6, 0x60, 0xEC, + 0x6D, 0x43, 0x64, 0x6D, 0x74, 0xEE, 0x6F, 0xEC, 0x66, 0xEC, 0x66, 0xC5, 0x04, 0x12, 0x2E, 0x6C, + 0x6E, 0x72, 0x73, 0xEB, 0xAF, 0xEB, 0xAF, 0xFF, 0xEF, 0xFF, 0xF6, 0xEB, 0x87, 0xA0, 0x06, 0x82, + 0x41, 0x73, 0xF1, 0x80, 0x45, 0x66, 0x6C, 0x6D, 0x6E, 0x73, 0xEB, 0x96, 0xF1, 0x7C, 0xFF, 0xF9, + 0xFF, 0xFC, 0xEC, 0x43, 0x41, 0x74, 0xF1, 0x6C, 0x42, 0x63, 0x74, 0xF1, 0x68, 0xFF, 0xFC, 0x44, + 0x67, 0x6B, 0x70, 0x73, 0xEB, 0xD8, 0xF5, 0x08, 0xF1, 0x61, 0xEB, 0x7B, 0x42, 0x69, 0x76, 0xEA, + 0xFE, 0xE6, 0x56, 0x42, 0x6D, 0x73, 0xEC, 0x14, 0xEE, 0xD9, 0xC1, 0x00, 0x91, 0x2E, 0xF1, 0x46, + 0xC1, 0x00, 0xC1, 0x73, 0xF1, 0x40, 0x21, 0x6E, 0xFA, 0x22, 0xA5, 0xA4, 0xF1, 0xFD, 0xA6, 0x06, + 0x62, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xB6, 0xCA, 0xD1, 0xDE, 0xE5, 0xFB, 0x42, 0x6E, 0x74, + 0xE8, 0x93, 0xF4, 0xEB, 0x41, 0x64, 0xE5, 0x61, 0x41, 0x75, 0xEB, 0x8F, 0x41, 0x66, 0xEA, 0xBE, + 0x45, 0x61, 0x69, 0x6E, 0x6F, 0x79, 0xFF, 0xFC, 0xE5, 0xC4, 0xE5, 0x3A, 0xF5, 0xAA, 0xEB, 0x87, + 0xC1, 0x02, 0x81, 0x65, 0xEB, 0x58, 0xC2, 0x00, 0x71, 0x69, 0xC3, 0xE5, 0xAE, 0xFB, 0xC0, 0xC1, + 0x00, 0x71, 0x72, 0xE5, 0xA5, 0x44, 0x6C, 0x6F, 0x72, 0x75, 0xE5, 0x15, 0xFF, 0xFA, 0xEC, 0xD1, + 0xE5, 0x15, 0x41, 0x6B, 0xEB, 0x55, 0x43, 0x6B, 0x6C, 0x72, 0xE5, 0x04, 0xE5, 0x04, 0xFF, 0xFC, + 0x42, 0x66, 0x6C, 0xEB, 0x47, 0xEA, 0xC2, 0x42, 0x61, 0x65, 0xE4, 0xF3, 0xE5, 0x0E, 0x41, 0x6D, + 0xEB, 0x39, 0x21, 0xA4, 0xFC, 0x47, 0x61, 0x65, 0x69, 0x6A, 0x6F, 0x72, 0xC3, 0xFF, 0xE1, 0xEC, + 0x71, 0xFF, 0xEB, 0xE4, 0xE5, 0xFA, 0x1D, 0xFF, 0xF2, 0xFF, 0xFD, 0xC3, 0x02, 0x81, 0x65, 0x69, + 0xC3, 0xEE, 0xEF, 0xE4, 0xCF, 0xEB, 0xC7, 0x41, 0xA4, 0xE8, 0x09, 0xD1, 0x03, 0x62, 0x61, 0x65, + 0x66, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFF, + 0x72, 0xF9, 0xF7, 0xEA, 0xED, 0xFF, 0x79, 0xFF, 0x7D, 0xFF, 0x85, 0xE8, 0x05, 0xFF, 0x95, 0xFF, + 0x9B, 0xEA, 0xE1, 0xFF, 0xAA, 0xE4, 0xBF, 0xFF, 0xDA, 0xEF, 0x1B, 0xFF, 0xF0, 0xE5, 0x20, 0xFF, + 0xFC, 0xC1, 0x00, 0xB1, 0x61, 0xE4, 0xA4, 0x42, 0x64, 0xC3, 0xFF, 0xFA, 0xFA, 0xC3, 0x41, 0x75, + 0xE4, 0x89, 0xC1, 0x03, 0x61, 0x62, 0xEA, 0xC5, 0xC7, 0x04, 0x12, 0x67, 0x6C, 0x6D, 0x70, 0x72, + 0x73, 0x74, 0xEB, 0x0F, 0xFF, 0xEF, 0xFB, 0xAD, 0xEB, 0x0F, 0xFF, 0xF6, 0xE7, 0x93, 0xFF, 0xFA, + 0x41, 0x6A, 0xE4, 0x82, 0x21, 0x74, 0xFC, 0xA1, 0x03, 0x01, 0x74, 0xFD, 0x41, 0x73, 0xE7, 0x0C, + 0x42, 0x64, 0x69, 0xFF, 0xFC, 0xEF, 0xF8, 0xA1, 0x04, 0x12, 0x72, 0xF9, 0x43, 0x6C, 0x72, 0x73, + 0xEA, 0x6C, 0xEA, 0x58, 0xE4, 0x59, 0x42, 0x6F, 0x74, 0xE4, 0x22, 0xEF, 0xE2, 0xC2, 0x04, 0x12, + 0x67, 0x72, 0xEA, 0xCA, 0xFF, 0xF9, 0x23, 0xA5, 0xA4, 0xB6, 0xE1, 0xE6, 0xF7, 0x57, 0x61, 0x62, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x79, 0xC3, 0xFC, 0xE2, 0xEC, 0xC3, 0xFD, 0x07, 0xFD, 0x81, 0xEE, 0xB8, 0xFD, + 0xB6, 0xFD, 0xCE, 0xFD, 0xF5, 0xFE, 0x0A, 0xF3, 0x97, 0xFE, 0x35, 0xEE, 0xB8, 0xFE, 0x44, 0xFE, + 0x5E, 0xEE, 0xB8, 0xFE, 0xC1, 0xFF, 0x5E, 0xF5, 0x2C, 0xFF, 0xAB, 0xEE, 0xB8, 0xEA, 0xBA, 0xFF, + 0xCA, 0xFF, 0xF9, 0x41, 0xA5, 0xE3, 0xFF, 0x21, 0xC3, 0xFC, 0x43, 0x69, 0x73, 0x76, 0xE4, 0x5A, + 0xEB, 0x3B, 0xFF, 0xFD, 0x41, 0x73, 0xE5, 0x54, 0x21, 0x6C, 0xFC, 0x41, 0x6C, 0xE5, 0xD2, 0x22, + 0x65, 0x73, 0xF9, 0xFC, 0xA0, 0x06, 0xA2, 0x21, 0x61, 0xFD, 0x22, 0x64, 0x67, 0xF5, 0xFD, 0xC1, + 0x00, 0x41, 0x6C, 0xE4, 0x6B, 0x43, 0x63, 0x70, 0x74, 0xE4, 0x2F, 0xFF, 0xFA, 0xE3, 0x99, 0xC1, + 0x01, 0x01, 0x73, 0xE3, 0xC3, 0x48, 0x62, 0x67, 0x6C, 0x6D, 0x6E, 0x72, 0x73, 0x76, 0xE9, 0x5D, + 0xEB, 0x51, 0xFF, 0xC5, 0xEF, 0xFC, 0xFF, 0xE5, 0xF8, 0x1A, 0xFF, 0xF0, 0xFF, 0xFA, 0x41, 0xC3, + 0xE4, 0xA2, 0xC2, 0x02, 0x71, 0x74, 0x76, 0xE3, 0xA0, 0xE3, 0xA0, 0x21, 0x73, 0xF7, 0x44, 0x61, + 0x66, 0x6C, 0x74, 0xF3, 0xDC, 0xE9, 0x6F, 0xFF, 0xF0, 0xFF, 0xFD, 0x41, 0x73, 0xE7, 0x34, 0xC3, + 0x02, 0x01, 0x72, 0x73, 0x74, 0xE9, 0xF8, 0xFF, 0xFC, 0xE3, 0xE5, 0x41, 0x6C, 0xE4, 0x1E, 0x21, + 0xA4, 0xFC, 0x21, 0xC3, 0xFD, 0xC1, 0x03, 0x61, 0x65, 0xE9, 0x73, 0x41, 0x74, 0xE9, 0x59, 0xC2, + 0x03, 0x31, 0x61, 0x69, 0xE9, 0x55, 0xFF, 0xFC, 0x41, 0x6C, 0xE7, 0x0F, 0x42, 0x70, 0x73, 0xFF, + 0xFC, 0xE3, 0x22, 0x42, 0x70, 0x74, 0xE3, 0x1B, 0xE5, 0x46, 0x44, 0x6E, 0x70, 0x73, 0x77, 0xFE, + 0xA4, 0xFF, 0xF2, 0xFF, 0xF9, 0xE9, 0xBD, 0xC1, 0x00, 0xD1, 0x74, 0xE5, 0x62, 0x21, 0x73, 0xFA, + 0xC2, 0x00, 0xA1, 0x61, 0x65, 0xE2, 0xF8, 0xE4, 0x5F, 0x21, 0x74, 0xF7, 0x42, 0x67, 0x73, 0xF8, + 0x4B, 0xFF, 0xFD, 0xA0, 0x06, 0xC2, 0x21, 0x73, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x72, 0xFD, 0xC1, + 0x02, 0xA1, 0x73, 0xE7, 0xD9, 0x41, 0x6F, 0xE6, 0xCD, 0x22, 0x6C, 0x73, 0xF6, 0xFC, 0x41, 0x61, + 0xE2, 0xFA, 0x44, 0x6C, 0x72, 0x73, 0x76, 0xFF, 0xF7, 0xF8, 0xA1, 0xE2, 0xF3, 0xFF, 0xFC, 0x41, + 0x6E, 0xE2, 0xB9, 0xC2, 0x01, 0x01, 0x65, 0x72, 0xFF, 0xFC, 0xE3, 0x87, 0x41, 0x6E, 0xE4, 0x44, + 0xC1, 0x00, 0xA1, 0x74, 0xE5, 0xAA, 0xA3, 0x02, 0x01, 0x67, 0x72, 0x73, 0xED, 0xF6, 0xFA, 0x23, + 0xA5, 0xA4, 0xB6, 0xBD, 0xD3, 0xF7, 0xD3, 0x02, 0xF1, 0x61, 0x63, 0x64, 0x65, 0x67, 0x69, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x79, 0xC3, 0xFF, 0x0F, 0xE8, 0x24, + 0xE9, 0x41, 0xFF, 0x38, 0xE8, 0xF1, 0xFF, 0x49, 0xFF, 0x5C, 0xE8, 0xFB, 0xFF, 0x5F, 0xEB, 0x4A, + 0xFF, 0x69, 0xFF, 0x84, 0xE8, 0xF1, 0xF2, 0x76, 0xE8, 0xFB, 0xEB, 0x4A, 0xFF, 0x97, 0xFF, 0xA6, + 0xFF, 0xF9, 0xC5, 0x01, 0xD1, 0x66, 0x67, 0x6C, 0x73, 0x74, 0xE3, 0xBD, 0xE2, 0x68, 0xE7, 0xC0, + 0xF1, 0xF5, 0xE3, 0xBD, 0xA0, 0x06, 0xE2, 0x41, 0x69, 0xF4, 0x28, 0x22, 0x6C, 0x72, 0xF9, 0xFC, + 0x41, 0x2E, 0xE8, 0x78, 0x41, 0x64, 0xE2, 0xD0, 0x21, 0x72, 0xFC, 0x42, 0x6F, 0x75, 0xFF, 0xFD, + 0xE2, 0xCF, 0x45, 0x61, 0x65, 0x68, 0x6B, 0x6F, 0xE8, 0x85, 0xFF, 0xEE, 0xF1, 0xFD, 0xFF, 0xF9, + 0xE7, 0xB8, 0x41, 0x6E, 0xE5, 0x4D, 0xC1, 0x02, 0x81, 0x6F, 0xE2, 0x24, 0x42, 0xA5, 0xB6, 0xE8, + 0x6B, 0xE8, 0x4C, 0xC7, 0x02, 0x01, 0x65, 0x67, 0x6F, 0x72, 0x73, 0x75, 0xC3, 0xFF, 0xEF, 0xEA, + 0x12, 0xF8, 0x3D, 0xFF, 0xF3, 0xF0, 0x76, 0xEA, 0xAE, 0xFF, 0xF9, 0x41, 0x6E, 0xE7, 0xEF, 0x21, + 0xA5, 0xFC, 0xA1, 0x01, 0xD1, 0xC3, 0xFD, 0x43, 0x65, 0x6E, 0x72, 0xE2, 0xE4, 0xE7, 0x73, 0xFF, + 0xFB, 0x41, 0x6E, 0xE8, 0x86, 0x42, 0x62, 0x6A, 0xE7, 0xFF, 0xE1, 0xF2, 0xC4, 0x01, 0xD1, 0x61, + 0x65, 0x6C, 0x6E, 0xFF, 0xF5, 0xFF, 0xF9, 0xE5, 0x24, 0xE8, 0x08, 0x41, 0x72, 0xE8, 0x29, 0x41, + 0xA4, 0xE2, 0x35, 0x42, 0x6F, 0xC3, 0xE8, 0x14, 0xFF, 0xFC, 0x43, 0x62, 0x66, 0x73, 0xE7, 0x40, + 0xE1, 0xCD, 0xE1, 0xCD, 0x42, 0x65, 0x6F, 0xE1, 0xB6, 0xFF, 0xF6, 0xA0, 0x07, 0x02, 0x41, 0x6F, + 0xE6, 0x39, 0x22, 0x68, 0x6B, 0xF9, 0xFC, 0x43, 0x6C, 0x6D, 0x74, 0xE7, 0xF0, 0xE7, 0xF0, 0xE1, + 0xAD, 0x42, 0xA5, 0xB6, 0xE7, 0xE6, 0xE7, 0xE6, 0x4B, 0x61, 0x69, 0x6C, 0x6E, 0x6F, 0x72, 0x73, + 0x75, 0x76, 0x79, 0xC3, 0xFF, 0xC3, 0xE7, 0xE9, 0xFF, 0xCB, 0xE1, 0x92, 0xE7, 0xE9, 0xFF, 0xDC, + 0xFF, 0xEA, 0xFF, 0xEF, 0xE4, 0xD8, 0xE7, 0xDF, 0xFF, 0xF9, 0x41, 0x6E, 0xE7, 0x8A, 0x41, 0xA4, + 0xE5, 0xF9, 0x45, 0x61, 0x69, 0x73, 0x75, 0xC3, 0xE8, 0xEC, 0xF0, 0xF5, 0xE3, 0x4C, 0xE7, 0x58, + 0xFF, 0xFC, 0x41, 0x6E, 0xE4, 0x69, 0x21, 0x69, 0xFC, 0x41, 0x6A, 0xE4, 0x62, 0x41, 0x74, 0xFC, + 0xFF, 0xC2, 0x01, 0x91, 0x66, 0x70, 0xFF, 0xFC, 0xE7, 0x77, 0x4B, 0x61, 0x64, 0x69, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6F, 0x74, 0x75, 0xC3, 0xE7, 0x97, 0xE9, 0x7E, 0xFF, 0xD0, 0xEC, 0xAA, 0xE1, 0x4D, + 0xFF, 0xD8, 0xFF, 0xEC, 0xE7, 0x97, 0xFF, 0xEF, 0xFF, 0xF7, 0xE9, 0xB4, 0x41, 0x72, 0xE2, 0x4D, + 0x42, 0x6D, 0x6F, 0xE1, 0x27, 0xE1, 0xA4, 0x43, 0xA5, 0xA4, 0xB6, 0xE6, 0x93, 0xE7, 0x60, 0xE6, + 0x93, 0x44, 0x62, 0x73, 0x75, 0xC3, 0xFF, 0xEB, 0xFF, 0xEF, 0xE7, 0x60, 0xFF, 0xF6, 0x41, 0x75, + 0xE7, 0x2A, 0x42, 0x6B, 0x74, 0xF6, 0x23, 0xE3, 0x17, 0x42, 0x73, 0x75, 0xFF, 0xF9, 0xE6, 0xB1, + 0x41, 0x2E, 0xE1, 0x05, 0x42, 0x2E, 0x73, 0xE1, 0x70, 0xFF, 0xFC, 0x41, 0x61, 0xEE, 0x42, 0x45, + 0x61, 0x65, 0x69, 0x6C, 0x73, 0xE1, 0x65, 0xFF, 0xF5, 0xE9, 0x4B, 0xFF, 0xFC, 0xEC, 0xE3, 0xC1, + 0x01, 0x91, 0x66, 0xE6, 0xF9, 0x41, 0x76, 0xE8, 0x81, 0x41, 0x68, 0xE6, 0x81, 0x21, 0x65, 0xFC, + 0x43, 0x65, 0x69, 0xC3, 0xEC, 0x90, 0xE6, 0x7A, 0xE6, 0xAD, 0x41, 0x6D, 0xE1, 0x3A, 0x41, 0xC3, + 0xEE, 0x31, 0x21, 0x72, 0xFC, 0x41, 0x6E, 0xEC, 0x7B, 0xA1, 0x02, 0xF1, 0x6C, 0xFC, 0x42, 0x65, + 0x72, 0xFF, 0xFB, 0xE0, 0xA9, 0x41, 0x6B, 0xE6, 0x85, 0x21, 0x6E, 0xFC, 0x43, 0x69, 0x72, 0xC3, + 0xFF, 0xFD, 0xED, 0xB7, 0xF4, 0x46, 0x47, 0x65, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x74, 0xFF, 0xD4, + 0xFF, 0xDC, 0xE0, 0x84, 0xE0, 0x9F, 0xE0, 0x84, 0xFF, 0xE8, 0xFF, 0xF6, 0xC1, 0x02, 0xF1, 0x61, + 0xE0, 0xF8, 0x43, 0x65, 0x72, 0x73, 0xE0, 0x75, 0xFF, 0xFA, 0xE0, 0x5C, 0xC1, 0x01, 0xD1, 0x6E, + 0xE6, 0x8C, 0x4D, 0x61, 0x64, 0x67, 0x69, 0x6A, 0x6B, 0x6E, 0x72, 0x73, 0x74, 0x75, 0x79, 0xC3, + 0xFF, 0x5C, 0xFF, 0x67, 0xFF, 0x7D, 0xFF, 0x8D, 0xE0, 0x65, 0xFF, 0x93, 0xFF, 0x9B, 0xFF, 0x9E, + 0xFF, 0xD4, 0xFF, 0xF0, 0xFF, 0xFA, 0xF5, 0x4C, 0xF3, 0x1C, 0xC1, 0x01, 0x91, 0x72, 0xE0, 0xBA, + 0xC1, 0x01, 0xD1, 0x69, 0xE0, 0x37, 0xC2, 0x01, 0x01, 0x6F, 0x73, 0xE6, 0x71, 0xE0, 0x4C, 0x4A, + 0x63, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0xE5, 0x9B, 0xE6, 0x72, 0xFF, 0xEB, + 0xFF, 0xF1, 0xE5, 0x9B, 0xFF, 0xF7, 0xE6, 0x72, 0xE6, 0x72, 0xE6, 0x72, 0xE6, 0x72, 0x41, 0x69, + 0xE0, 0x09, 0x44, 0x65, 0x69, 0x6F, 0x70, 0xE6, 0x4F, 0xE6, 0x4F, 0xE0, 0xE9, 0xFF, 0xFC, 0xC1, + 0x01, 0xD1, 0x70, 0xDF, 0xDF, 0x46, 0x61, 0x65, 0x69, 0x6B, 0x6F, 0x75, 0xE6, 0x32, 0xEE, 0x33, + 0xE6, 0x3C, 0xDF, 0xD6, 0xFF, 0xFA, 0xE6, 0x3C, 0x41, 0x65, 0xE5, 0x52, 0x41, 0x70, 0xDF, 0xDB, + 0x41, 0x70, 0xE2, 0xF9, 0xC1, 0x00, 0x71, 0x6E, 0xE0, 0x50, 0xC5, 0x02, 0x01, 0x65, 0x6E, 0x6F, + 0x75, 0x79, 0xFF, 0xF2, 0xE1, 0x8D, 0xFF, 0xF6, 0xFF, 0xFA, 0xDF, 0xC0, 0x41, 0x6D, 0xE5, 0x2E, + 0x41, 0xA4, 0xF1, 0xF6, 0x22, 0x61, 0xC3, 0xF8, 0xFC, 0xC1, 0x06, 0xE2, 0x72, 0xF4, 0x9E, 0xA0, + 0x07, 0x22, 0x41, 0xA4, 0xDF, 0x8C, 0x22, 0x72, 0xC3, 0xF9, 0xFC, 0x44, 0x61, 0x65, 0x6E, 0x74, + 0xDF, 0x8F, 0xDF, 0x8F, 0xDF, 0x7D, 0xFF, 0xFB, 0x41, 0x73, 0xE5, 0xD9, 0x43, 0x6C, 0x6E, 0x74, + 0xE5, 0xD5, 0xFF, 0xFC, 0xE0, 0xA0, 0x42, 0x2E, 0x74, 0xDF, 0x81, 0xE5, 0xCB, 0x42, 0x6E, 0x73, + 0xFF, 0xF9, 0xDF, 0xF7, 0x41, 0x67, 0xDF, 0x73, 0x42, 0x67, 0x6E, 0xDF, 0x62, 0xFF, 0xFC, 0x42, + 0x2E, 0x65, 0xDF, 0x68, 0xDF, 0x68, 0x21, 0x72, 0xF9, 0x41, 0xA5, 0xE1, 0x64, 0x46, 0x61, 0x65, + 0x69, 0x6F, 0x72, 0xC3, 0xFF, 0xCF, 0xFF, 0xE0, 0xFF, 0xEB, 0xFF, 0xF9, 0xE3, 0x2A, 0xFF, 0xFC, + 0x4D, 0x63, 0x65, 0x68, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x76, 0x79, 0xC3, 0xFF, 0x68, + 0xE3, 0x87, 0xE2, 0x4B, 0xFF, 0x7A, 0xFF, 0x94, 0xDF, 0x3A, 0xDF, 0x3A, 0xFF, 0x99, 0xFF, 0xAB, + 0xFF, 0xED, 0xDF, 0x47, 0xE5, 0x87, 0xEF, 0xB3, 0x42, 0x69, 0x78, 0xE5, 0x40, 0xE5, 0x40, 0x42, + 0x6E, 0x73, 0xE4, 0x8B, 0xE5, 0x58, 0x42, 0x61, 0x69, 0xDF, 0x6E, 0xFF, 0xF9, 0x43, 0x6F, 0x72, + 0x75, 0xE8, 0x82, 0xFF, 0x62, 0xDF, 0x87, 0x41, 0xA5, 0xE8, 0xCC, 0xC6, 0x01, 0xD1, 0x63, 0x65, + 0x6A, 0x72, 0x74, 0xC3, 0xDE, 0xFC, 0xFF, 0xDD, 0xE5, 0x1D, 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xFC, + 0xC1, 0x01, 0xD1, 0x6B, 0xE4, 0xF4, 0x41, 0xA5, 0xE5, 0x2B, 0x45, 0x61, 0x69, 0x72, 0x73, 0xC3, + 0xFF, 0xF6, 0xFE, 0x72, 0xDE, 0xD0, 0xE1, 0xE1, 0xFF, 0xFC, 0xC1, 0x00, 0xD1, 0x75, 0xDE, 0xCD, + 0x21, 0x74, 0xFA, 0x41, 0xB6, 0xE5, 0x0E, 0x55, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, + 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x78, 0xC3, 0xFC, 0x4B, 0xFC, + 0x64, 0xFC, 0x7B, 0xFC, 0x9C, 0xE9, 0x4E, 0xFC, 0xC0, 0xFC, 0xD5, 0xE5, 0x00, 0xFD, 0x21, 0xFD, + 0x73, 0xFD, 0xAA, 0xFE, 0x5B, 0xFE, 0x98, 0xFE, 0xBB, 0xFE, 0xCE, 0xFF, 0x79, 0xFF, 0xC4, 0xEC, + 0xC3, 0xFF, 0xE3, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xF1, 0x69, 0xC3, 0x02, 0xF1, 0x6B, 0x72, + 0x73, 0xFF, 0xFC, 0xF5, 0x00, 0xDF, 0x64, 0x41, 0x79, 0xDE, 0xED, 0x21, 0x74, 0xFC, 0xC3, 0x03, + 0x61, 0x72, 0x73, 0x75, 0xDE, 0x5C, 0xFF, 0xFD, 0xE4, 0x8A, 0x41, 0x74, 0xF2, 0xD5, 0xC3, 0x02, + 0xF1, 0x61, 0x66, 0x73, 0xE1, 0x5D, 0xEA, 0x22, 0xFF, 0xFC, 0xC1, 0x02, 0xF1, 0x6E, 0xE4, 0xDD, + 0xC3, 0x03, 0x31, 0x6C, 0x6F, 0x76, 0xE4, 0x68, 0xE4, 0x87, 0xDE, 0x3A, 0x42, 0x6A, 0xC3, 0xE4, + 0xCB, 0xEF, 0x1B, 0xA0, 0x07, 0x42, 0x42, 0x69, 0x73, 0xE1, 0x10, 0xFF, 0xFD, 0x21, 0x64, 0xF9, + 0xC5, 0x02, 0x01, 0x62, 0x6B, 0x6C, 0x72, 0x75, 0xE4, 0x0A, 0xE4, 0x1D, 0xFF, 0xEC, 0xFF, 0xFD, + 0xE4, 0x0A, 0xC1, 0x03, 0x31, 0x6C, 0xE3, 0x88, 0xC4, 0x03, 0xD2, 0x61, 0x6B, 0x6D, 0x74, 0xE3, + 0x82, 0xE4, 0x30, 0xE5, 0xFD, 0xE5, 0xFD, 0xC1, 0x03, 0x61, 0x73, 0xDE, 0x0E, 0x42, 0x6F, 0x73, + 0xE3, 0x81, 0xDD, 0xE1, 0xC1, 0x00, 0xA1, 0x61, 0xDD, 0xD4, 0x43, 0x6B, 0x73, 0x74, 0xF3, 0x2D, + 0xDE, 0x15, 0xFF, 0xFA, 0x41, 0x74, 0xE3, 0xE7, 0x21, 0x73, 0xFC, 0xC5, 0x03, 0x62, 0x6B, 0x6C, + 0x72, 0x73, 0x74, 0xFF, 0xE2, 0xE3, 0x67, 0xE4, 0x6C, 0xFF, 0xEF, 0xFF, 0xFD, 0x41, 0x72, 0xE2, + 0xA5, 0x41, 0x61, 0xDD, 0xEE, 0x43, 0x6F, 0x70, 0x73, 0xDD, 0xA3, 0xFF, 0xF8, 0xFF, 0xFC, 0x41, + 0x6B, 0xDE, 0x77, 0x21, 0x73, 0xFC, 0x42, 0x6E, 0x73, 0xFF, 0xFD, 0xDE, 0x64, 0x22, 0x6C, 0x72, + 0xE8, 0xF9, 0x42, 0x72, 0x73, 0xE0, 0x37, 0xE0, 0x3E, 0x43, 0xA4, 0xB6, 0xA9, 0xFF, 0xF4, 0xFF, + 0xF9, 0xE9, 0x67, 0x53, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, + 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0xC3, 0xFF, 0x18, 0xE6, 0x2D, 0xFF, 0x2B, 0xFF, 0x3B, 0xE8, + 0x22, 0xE8, 0x25, 0xE4, 0x24, 0xFF, 0x47, 0xFF, 0x4D, 0xE8, 0x22, 0xE6, 0x2D, 0xE8, 0x22, 0xFF, + 0x6D, 0xFF, 0x7F, 0xE9, 0x32, 0xFF, 0x85, 0xFF, 0x94, 0xFF, 0xB8, 0xFF, 0xF6, 0x43, 0x2E, 0x65, + 0x72, 0xE3, 0x3D, 0xF0, 0x43, 0xDD, 0xD7, 0x41, 0xA4, 0xE9, 0x19, 0xA1, 0x03, 0x61, 0xC3, 0xFC, + 0x41, 0x69, 0xE3, 0x87, 0xA1, 0x03, 0x61, 0x74, 0xFC, 0x42, 0x6F, 0x73, 0xDD, 0x4C, 0xDD, 0x59, + 0x43, 0x66, 0x67, 0x6C, 0xE3, 0xC7, 0xE3, 0xC7, 0xFF, 0xF9, 0x42, 0x70, 0x72, 0xE3, 0x10, 0xE3, + 0x10, 0x43, 0x2E, 0x64, 0x6F, 0xE3, 0x09, 0xEB, 0x22, 0xE8, 0xEF, 0x41, 0x65, 0xFC, 0x6A, 0xC5, + 0x03, 0x61, 0x69, 0x6C, 0x70, 0x73, 0x74, 0xE2, 0xFB, 0xF1, 0x90, 0xDD, 0x29, 0xE6, 0x6D, 0xFF, + 0xFC, 0xC1, 0x03, 0x31, 0x65, 0xEC, 0x97, 0x41, 0x61, 0xE2, 0xE3, 0xC1, 0x03, 0x31, 0x73, 0xDD, + 0xAF, 0x42, 0x2E, 0x65, 0xE3, 0x86, 0xE3, 0x86, 0x49, 0x2E, 0x61, 0x62, 0x69, 0x6B, 0x6E, 0x70, + 0x74, 0x76, 0xE2, 0xD2, 0xDC, 0xE2, 0xFF, 0xE9, 0xDC, 0xEF, 0xE3, 0x7F, 0xFF, 0xEF, 0xFF, 0xF3, + 0xFF, 0xF9, 0xE3, 0x7F, 0xC2, 0x03, 0x01, 0x6B, 0x74, 0xDD, 0x50, 0xE4, 0x91, 0x42, 0x2E, 0x73, + 0xE2, 0xAD, 0xEF, 0xB3, 0xCE, 0x04, 0x12, 0x2E, 0x64, 0x66, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x70, 0x72, 0x73, 0x74, 0x75, 0xE2, 0xA6, 0xFF, 0x69, 0xFF, 0x77, 0xDC, 0xB6, 0xF8, 0x3A, 0xFF, + 0x80, 0xFF, 0x8C, 0xFF, 0x96, 0xFF, 0x9D, 0xFF, 0xAB, 0xFF, 0xD4, 0xFF, 0xF0, 0xFF, 0xF9, 0xE3, + 0x53, 0x41, 0x6D, 0xDC, 0xBE, 0xA1, 0x07, 0x63, 0x6F, 0xFC, 0x44, 0x2E, 0x61, 0x72, 0x73, 0xE5, + 0x26, 0xE5, 0x26, 0xDC, 0x80, 0xDC, 0x9B, 0x41, 0x75, 0xE5, 0x19, 0x42, 0x64, 0x73, 0xE3, 0x0C, + 0xDC, 0xA4, 0x41, 0x65, 0xE3, 0x05, 0x42, 0x73, 0xC3, 0xFF, 0xFC, 0xEC, 0xE8, 0xC1, 0x00, 0xD1, + 0x73, 0xDC, 0x51, 0xCA, 0x04, 0x12, 0x2E, 0x64, 0x65, 0x66, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x78, + 0xE2, 0x47, 0xFF, 0xD7, 0xE2, 0x5A, 0xFF, 0xE4, 0xFF, 0xE8, 0xFF, 0xF3, 0xE4, 0xFD, 0xFA, 0x58, + 0xFF, 0xFA, 0xE2, 0xF4, 0x41, 0x69, 0xDC, 0x5E, 0xA1, 0x01, 0xE1, 0x72, 0xFC, 0x21, 0xB6, 0xFB, + 0xA1, 0x03, 0xD2, 0xC3, 0xFD, 0xA0, 0x07, 0x93, 0x41, 0xA5, 0xE7, 0xF8, 0x21, 0xC3, 0xFC, 0x45, + 0x67, 0x6E, 0x73, 0x74, 0x75, 0xDD, 0x96, 0xFF, 0xB3, 0xE2, 0xB8, 0xE4, 0xC1, 0xDC, 0xA5, 0x49, + 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x74, 0x76, 0xE3, 0x32, 0xE2, 0xA8, 0xE1, 0xCB, 0xE3, + 0xA7, 0xDE, 0xB1, 0xFF, 0xED, 0xFF, 0xF0, 0xE2, 0x0E, 0xE3, 0x32, 0x41, 0x6C, 0xE7, 0xC5, 0xC2, + 0x03, 0x31, 0x6F, 0xC3, 0xFF, 0xFC, 0xE5, 0x0E, 0x42, 0x61, 0x69, 0xE1, 0xFC, 0xDB, 0xFD, 0x41, + 0x67, 0xEE, 0xD1, 0x41, 0x73, 0xE1, 0xC7, 0x46, 0x67, 0x6D, 0x6E, 0x73, 0x74, 0x76, 0xE1, 0x93, + 0xFF, 0xF1, 0xFF, 0xF8, 0xFF, 0xFC, 0xE4, 0x79, 0xE7, 0xA9, 0xC1, 0x03, 0x61, 0x6A, 0xE1, 0xDA, + 0xA0, 0x07, 0xC2, 0x21, 0x72, 0xFD, 0xA1, 0x02, 0x01, 0x74, 0xFD, 0x44, 0x67, 0x6D, 0x70, 0x73, + 0xE4, 0x55, 0xE1, 0xC9, 0xE1, 0x9F, 0xFF, 0xFB, 0x42, 0x2E, 0x61, 0xE1, 0x22, 0xE7, 0x78, 0x21, + 0x6B, 0xF9, 0x42, 0x63, 0x73, 0xFF, 0xFD, 0xE1, 0x88, 0x42, 0x62, 0x6B, 0xE1, 0x51, 0xE2, 0x2E, + 0x42, 0x64, 0x67, 0xE1, 0x4A, 0xE7, 0x35, 0x21, 0xA4, 0xF9, 0xA6, 0x00, 0xD1, 0x61, 0x65, 0x69, + 0x6F, 0x75, 0xC3, 0xAD, 0xC0, 0xD1, 0xE8, 0xEF, 0xFD, 0x42, 0x6C, 0x70, 0xE1, 0x9F, 0xEB, 0x2B, + 0x42, 0x70, 0x76, 0xE7, 0x40, 0xEB, 0x24, 0xC1, 0x00, 0x91, 0x6B, 0xE0, 0xE3, 0xC4, 0x03, 0x62, + 0x61, 0x69, 0x75, 0x79, 0xFF, 0xEC, 0xFF, 0xF3, 0xEB, 0x17, 0xFF, 0xFA, 0x42, 0x65, 0x6C, 0xDB, + 0x5B, 0xE0, 0xCE, 0x43, 0x6E, 0x72, 0x73, 0xDB, 0x3B, 0xDB, 0xD1, 0xE3, 0x79, 0x41, 0x2E, 0xE7, + 0x13, 0x45, 0x61, 0x69, 0x6A, 0x74, 0x76, 0xE1, 0xD6, 0xF9, 0xE1, 0xE1, 0xD6, 0xF0, 0x86, 0xFF, + 0xFC, 0xC4, 0x02, 0xF1, 0x6D, 0x70, 0x72, 0x73, 0xE1, 0x19, 0xE6, 0xFF, 0xE6, 0xD4, 0xDB, 0x44, + 0x41, 0x74, 0xE1, 0x0A, 0xC4, 0x02, 0xF1, 0x66, 0x73, 0x74, 0x75, 0xE1, 0x06, 0xFF, 0xFC, 0xE1, + 0x06, 0xDB, 0x16, 0xC1, 0x03, 0x61, 0x65, 0xFF, 0x30, 0x41, 0x72, 0xE6, 0xDA, 0x21, 0x65, 0xFC, + 0xC4, 0x02, 0xF1, 0x67, 0x69, 0x72, 0x74, 0xE0, 0xEA, 0xDA, 0xFA, 0xFF, 0xF3, 0xFF, 0xFD, 0xC1, + 0x03, 0x01, 0x72, 0xDB, 0x75, 0x43, 0x6B, 0x6C, 0x74, 0xDA, 0xEF, 0xDA, 0xE5, 0xFF, 0xFA, 0xC9, + 0x02, 0x01, 0x63, 0x64, 0x67, 0x6C, 0x6D, 0x6E, 0x72, 0x73, 0x75, 0xE1, 0x78, 0xFF, 0x8D, 0xFF, + 0x94, 0xFF, 0xA2, 0xFF, 0xB2, 0xFF, 0xC5, 0xFF, 0xE1, 0xFF, 0xF6, 0xE5, 0x79, 0xA0, 0x07, 0xE2, + 0x21, 0x65, 0xFD, 0x41, 0x67, 0xE1, 0x54, 0x43, 0x66, 0x67, 0x6E, 0xE0, 0xA3, 0xFF, 0xF9, 0xFF, + 0xFC, 0x42, 0x72, 0x74, 0xE6, 0x82, 0xE3, 0x70, 0xA0, 0x08, 0x02, 0x21, 0x73, 0xFD, 0xC1, 0x02, + 0xF1, 0x73, 0xDB, 0x68, 0xA0, 0x08, 0x21, 0x21, 0x6B, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x67, 0xFD, + 0x44, 0x64, 0x67, 0x6E, 0x76, 0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xFD, 0xE1, 0x27, 0xC1, 0x03, 0x01, + 0x73, 0xDD, 0x3B, 0x21, 0x70, 0xFA, 0x21, 0x70, 0xFD, 0x41, 0x64, 0xDA, 0x5F, 0x21, 0x61, 0xFC, + 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x67, 0xE0, 0xB1, 0x42, 0x6B, 0x70, 0xDB, 0xE6, 0xFC, + 0xA7, 0x41, 0x64, 0xF0, 0x5D, 0x41, 0x6C, 0xDB, 0xAE, 0x44, 0x64, 0x6B, 0x6C, 0x74, 0xFF, 0xF8, + 0xFF, 0xFC, 0xE0, 0xEE, 0xE0, 0x9E, 0x22, 0xA5, 0xA4, 0xE4, 0xF3, 0xC7, 0x06, 0x62, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0x8C, 0xFF, 0x96, 0xFF, 0xB5, 0xFF, 0xCB, 0xFF, 0xD8, 0xFF, + 0xDB, 0xFF, 0xFB, 0x42, 0x6B, 0x73, 0xDB, 0xAD, 0xE4, 0x00, 0x41, 0x68, 0xDA, 0x20, 0x42, 0xA4, + 0xB6, 0xE0, 0x69, 0xE0, 0x69, 0xC4, 0x08, 0x32, 0x6C, 0x72, 0x76, 0xC3, 0xDA, 0x15, 0xEE, 0x9A, + 0xDA, 0x22, 0xFF, 0xF9, 0x41, 0x67, 0xF0, 0x0A, 0x43, 0x61, 0x69, 0x79, 0xFF, 0xFC, 0xDD, 0x13, + 0xDF, 0x82, 0x41, 0x76, 0xDD, 0xE3, 0xC2, 0x08, 0x52, 0x61, 0x69, 0xFF, 0xFC, 0xDB, 0x36, 0xC9, + 0x03, 0x61, 0x61, 0x63, 0x65, 0x6B, 0x6C, 0x6F, 0x70, 0x74, 0x75, 0xFF, 0xC4, 0xFF, 0xCB, 0xE0, + 0x1C, 0xFF, 0xD6, 0xFF, 0xE9, 0xE0, 0x05, 0xDF, 0xB3, 0xFF, 0xF7, 0xE0, 0x05, 0x41, 0x78, 0xDF, + 0xFB, 0x42, 0x6E, 0x73, 0xDF, 0xF7, 0xDF, 0xE3, 0x42, 0x67, 0x6E, 0xED, 0x28, 0xFF, 0xF9, 0x42, + 0x64, 0x6D, 0xDF, 0xD5, 0xDF, 0xD5, 0x43, 0x64, 0x67, 0x6C, 0xDF, 0x34, 0xD9, 0xC1, 0xD9, 0xB4, + 0x41, 0xA4, 0xE0, 0xB4, 0xC4, 0x02, 0x01, 0x65, 0x69, 0x6F, 0xC3, 0xD9, 0xA6, 0xEB, 0xF2, 0xFF, + 0xF2, 0xFF, 0xFC, 0x41, 0x74, 0xDC, 0xA8, 0x42, 0x67, 0x74, 0xDF, 0xC1, 0xDF, 0xAD, 0x41, 0x6C, + 0xDF, 0xBA, 0x21, 0xA4, 0xFC, 0xC8, 0x04, 0xA2, 0x65, 0x69, 0x6F, 0x72, 0x73, 0x74, 0x75, 0xC3, + 0xFF, 0xB8, 0xFF, 0xC3, 0xFF, 0xCA, 0xFF, 0xDF, 0xFF, 0xEE, 0xD9, 0x8F, 0xFF, 0xF2, 0xFF, 0xFD, + 0xC1, 0x03, 0x31, 0x6E, 0xDE, 0xEA, 0x43, 0x61, 0x64, 0xC3, 0xD9, 0x71, 0xE0, 0x01, 0xE5, 0xCE, + 0x41, 0x70, 0xD9, 0x4B, 0x42, 0x67, 0x6E, 0xDA, 0xDC, 0xE5, 0x2C, 0x41, 0x65, 0xE0, 0xDB, 0x42, + 0x6F, 0x73, 0xD9, 0xD5, 0xDF, 0x3B, 0xC1, 0x03, 0x01, 0x74, 0xDC, 0xCD, 0x27, 0x67, 0x6C, 0x6D, + 0x6E, 0x70, 0x72, 0x73, 0xD4, 0xDA, 0xE4, 0xE8, 0xEF, 0xF3, 0xFA, 0x41, 0x69, 0xDF, 0x1F, 0x44, + 0x65, 0x6E, 0x73, 0x74, 0xDE, 0xAB, 0xDF, 0x1B, 0xD9, 0x53, 0xDF, 0x1B, 0x41, 0x6F, 0xDF, 0x4C, + 0x23, 0x6C, 0x72, 0x74, 0xEB, 0xEF, 0xFC, 0x41, 0x65, 0xE1, 0xB9, 0x21, 0x74, 0xFC, 0xA1, 0x02, + 0x01, 0x6E, 0xFD, 0x41, 0x6E, 0xDE, 0xC7, 0x42, 0x6E, 0x72, 0xFF, 0xFC, 0xF2, 0x59, 0x41, 0x6C, + 0xDE, 0xEC, 0x42, 0x6C, 0x72, 0xFF, 0xFC, 0xDF, 0x4F, 0x42, 0xA5, 0xA4, 0xDF, 0x48, 0xFF, 0xF9, + 0xC5, 0x00, 0x41, 0x61, 0x65, 0x69, 0x6F, 0xC3, 0xFF, 0xD0, 0xFF, 0xDE, 0xFF, 0xE7, 0xDF, 0x18, + 0xFF, 0xF9, 0x41, 0x69, 0xE0, 0x64, 0x43, 0x64, 0x6C, 0x72, 0xD9, 0xA6, 0xFF, 0xFC, 0xF8, 0x3F, + 0x42, 0x6E, 0x73, 0xE4, 0xA0, 0xDE, 0xBA, 0x44, 0x6C, 0x6D, 0x6E, 0x72, 0xFC, 0x3A, 0xE5, 0xD2, + 0xFF, 0xF9, 0xFF, 0xC7, 0x41, 0x74, 0xDA, 0x0F, 0x21, 0x73, 0xFC, 0x42, 0x6C, 0x73, 0xD8, 0xE4, + 0xDC, 0xFC, 0x44, 0x67, 0x6B, 0x70, 0x72, 0xDF, 0x45, 0xFF, 0xF6, 0xFD, 0x6B, 0xFF, 0xF9, 0x22, + 0xA4, 0xB6, 0xD8, 0xF3, 0x57, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFB, 0xE0, 0xE1, 0x3C, + 0xDF, 0x33, 0xFC, 0x11, 0xFC, 0x3F, 0xFC, 0x6C, 0xFC, 0x71, 0xF1, 0x85, 0xFC, 0x8B, 0xFC, 0xAB, + 0xE4, 0x41, 0xFD, 0x16, 0xE3, 0x31, 0xFD, 0x39, 0xFD, 0xBB, 0xE3, 0x31, 0xFE, 0x57, 0xFE, 0xAB, + 0xFF, 0x11, 0xFF, 0x58, 0xFF, 0xAC, 0xFF, 0xC2, 0xFF, 0xFB, 0x41, 0x2E, 0xDE, 0x10, 0x43, 0x65, + 0x6D, 0x72, 0xFF, 0xFC, 0xE0, 0xF2, 0xDE, 0xE9, 0x44, 0x64, 0x69, 0x6D, 0x72, 0xEB, 0x38, 0xDF, + 0xCE, 0xE4, 0x18, 0xD8, 0x36, 0x41, 0x6E, 0xFC, 0xF8, 0x41, 0x69, 0xFC, 0x5A, 0xC3, 0x03, 0x61, + 0x61, 0x69, 0x74, 0xFF, 0xF8, 0xE4, 0x03, 0xFF, 0xFC, 0x42, 0x65, 0x69, 0xEB, 0x17, 0xD8, 0x3C, + 0x21, 0x64, 0xF9, 0x41, 0x61, 0xE0, 0xBD, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x70, 0xFD, + 0x44, 0x62, 0x6E, 0x73, 0x74, 0xE0, 0xB0, 0xDD, 0xCA, 0xD7, 0xF8, 0xDE, 0xA7, 0xC1, 0x00, 0x41, + 0x65, 0xFC, 0xC0, 0x43, 0x68, 0x6B, 0x74, 0xD7, 0xEB, 0xDE, 0x94, 0xFF, 0xFA, 0x41, 0x69, 0xDF, + 0x89, 0x43, 0x2E, 0x72, 0x74, 0xDD, 0xA9, 0xD7, 0xF6, 0xFF, 0xFC, 0xC1, 0x03, 0x61, 0x75, 0xD7, + 0xEC, 0x4D, 0x2E, 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0xDD, + 0xC9, 0xFF, 0x8D, 0xE0, 0x7F, 0xFF, 0x97, 0xFF, 0xAC, 0xE8, 0x78, 0xFF, 0xBF, 0xFF, 0xCC, 0xFF, + 0xCF, 0xFF, 0xE2, 0xFF, 0xF0, 0xE0, 0x7F, 0xFF, 0xFA, 0x41, 0x72, 0xD8, 0xA2, 0x42, 0x61, 0x79, + 0xD7, 0xAD, 0xDD, 0x2D, 0x41, 0x61, 0xDA, 0x92, 0xC4, 0x03, 0x62, 0x65, 0x6A, 0x72, 0x73, 0xFF, + 0xF1, 0xDD, 0xEF, 0xFF, 0xF5, 0xFF, 0xFC, 0x42, 0x6C, 0x76, 0xDA, 0xA4, 0xD8, 0x1D, 0x41, 0xC3, + 0xEC, 0xDD, 0x21, 0x73, 0xFC, 0x41, 0x6C, 0xE3, 0x33, 0x41, 0x74, 0xE8, 0x64, 0x44, 0x2E, 0x6B, + 0x6E, 0x73, 0xDD, 0x6D, 0xD7, 0x6B, 0xDD, 0x6D, 0xFF, 0xFC, 0x41, 0x6C, 0xF5, 0xC1, 0xCC, 0x02, + 0xF1, 0x2E, 0x67, 0x69, 0x6B, 0x6D, 0x6E, 0x72, 0x73, 0x74, 0x75, 0x76, 0x78, 0xDD, 0x5C, 0xED, + 0x77, 0xF8, 0x06, 0xFF, 0xD9, 0xFF, 0xE4, 0xFF, 0xE7, 0xFF, 0xEF, 0xFF, 0xFC, 0xE7, 0xF4, 0xEC, + 0x60, 0xDC, 0xC4, 0xE0, 0x12, 0xC1, 0x00, 0xA1, 0x74, 0xD7, 0x52, 0xC3, 0x03, 0xD2, 0x6A, 0x6C, + 0x73, 0xDD, 0x59, 0xEE, 0x33, 0xFF, 0xFA, 0xC2, 0x03, 0x01, 0x6F, 0x73, 0xD7, 0xBD, 0xD7, 0x27, + 0xC1, 0x01, 0x01, 0x69, 0xD7, 0x37, 0x42, 0x6C, 0x73, 0xFF, 0xFA, 0xD7, 0x18, 0x41, 0x72, 0xE2, + 0xF3, 0x21, 0x65, 0xFC, 0x41, 0x2E, 0xDF, 0xBC, 0x44, 0x61, 0x6F, 0x72, 0x73, 0xFF, 0xFC, 0xD7, + 0xD2, 0xDD, 0xAF, 0xFE, 0x5C, 0x45, 0x64, 0x67, 0x69, 0x6A, 0x74, 0xDF, 0xAB, 0xFF, 0xF3, 0xDF, + 0xAB, 0xE2, 0xDB, 0xDF, 0xAB, 0x41, 0xB6, 0xD6, 0xF5, 0x21, 0xC3, 0xFC, 0x42, 0x61, 0x72, 0xD7, + 0x78, 0xFF, 0xFD, 0x42, 0x63, 0x74, 0xD6, 0xDB, 0xFF, 0xF9, 0x42, 0x6F, 0x73, 0xD7, 0x6A, 0xD9, + 0xC6, 0xCA, 0x02, 0xF1, 0x63, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x6F, 0x70, 0x73, 0x76, 0xE7, 0x24, + 0xDC, 0xC9, 0xFF, 0xA6, 0xFF, 0xB5, 0xFF, 0xC0, 0xFF, 0xD4, 0xDA, 0x1F, 0xDF, 0x7F, 0xFF, 0xF2, + 0xFF, 0xF9, 0x41, 0x6B, 0xDC, 0xD2, 0x43, 0xA5, 0xA4, 0xB6, 0xDC, 0x34, 0xDD, 0x0B, 0xE6, 0x1C, + 0x44, 0x61, 0x6F, 0x75, 0xC3, 0xFF, 0xF2, 0xE4, 0xBA, 0xDC, 0x6A, 0xFF, 0xF6, 0xC2, 0x02, 0x01, + 0x61, 0xC3, 0xF7, 0x37, 0xDF, 0x11, 0xC1, 0x02, 0x01, 0x61, 0xEF, 0x7B, 0xC5, 0x03, 0xD2, 0x65, + 0x6A, 0x6C, 0x72, 0x74, 0xDC, 0xDB, 0xE6, 0x60, 0xFF, 0xF1, 0xFF, 0xFA, 0xD6, 0x8E, 0xC1, 0x00, + 0xA1, 0x72, 0xD6, 0x7C, 0x41, 0x62, 0xD6, 0x64, 0x41, 0x76, 0xD7, 0x32, 0xA1, 0x01, 0xD1, 0x6B, + 0xFC, 0x45, 0x61, 0x65, 0x6B, 0x6F, 0x76, 0xDD, 0xF5, 0xD9, 0x7A, 0xD9, 0x98, 0xD9, 0x7A, 0xDC, + 0xDD, 0x42, 0x69, 0x72, 0xDF, 0x72, 0xD6, 0x59, 0x41, 0x61, 0xD7, 0x1E, 0x21, 0x72, 0xFC, 0x21, + 0x74, 0xFD, 0xC2, 0x01, 0xD1, 0x6E, 0x73, 0xD6, 0x55, 0xFF, 0xFD, 0x41, 0x67, 0xD8, 0xCF, 0x21, + 0x67, 0xFC, 0xC1, 0x02, 0x11, 0x74, 0xD6, 0x45, 0x42, 0x64, 0x72, 0xDB, 0xB2, 0xFF, 0xFA, 0x22, + 0xA4, 0xB6, 0xF0, 0xF9, 0x4D, 0x61, 0x64, 0x65, 0x6B, 0x6C, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x76, + 0x79, 0xC3, 0xDC, 0x7D, 0xFF, 0xAA, 0xFF, 0xB0, 0xD6, 0x26, 0xD9, 0x6C, 0xFF, 0xB8, 0xD6, 0x26, + 0xFF, 0xBD, 0xFF, 0xCD, 0xFF, 0xDE, 0xD6, 0x33, 0xDB, 0xA6, 0xFF, 0xFB, 0xC2, 0x04, 0xA2, 0x6F, + 0x73, 0xFC, 0x72, 0xD6, 0x5F, 0x41, 0x66, 0xD9, 0x06, 0x41, 0x6B, 0xE7, 0xC2, 0x43, 0x6C, 0x6F, + 0x73, 0xDC, 0x8A, 0xD6, 0x77, 0xD6, 0x70, 0x41, 0x2E, 0xD8, 0xF4, 0x21, 0x65, 0xFC, 0x41, 0x6A, + 0xDE, 0x82, 0x43, 0x64, 0x67, 0x73, 0xDC, 0x75, 0xDE, 0x7E, 0xD5, 0xF3, 0x48, 0x61, 0x63, 0x64, + 0x67, 0x6C, 0x6D, 0x72, 0x76, 0xFF, 0xD9, 0xFF, 0xDD, 0xFF, 0xE1, 0xFF, 0xEF, 0xFF, 0xF2, 0xDE, + 0x74, 0xFF, 0xF6, 0xDE, 0xCC, 0x41, 0x6B, 0xDB, 0xE3, 0x42, 0x61, 0xC3, 0xDB, 0x31, 0xE6, 0x9E, + 0x45, 0x61, 0x65, 0x69, 0x6C, 0x73, 0xFF, 0xF5, 0xDC, 0x01, 0xDC, 0x01, 0xFF, 0xF9, 0xE6, 0x1A, + 0x41, 0xA4, 0xDB, 0xC8, 0x42, 0x6F, 0xC3, 0xDB, 0xC4, 0xFF, 0xFC, 0x41, 0x73, 0xDE, 0x2A, 0x21, + 0x6E, 0xFC, 0x41, 0xC3, 0xDE, 0xAB, 0xC1, 0x01, 0x91, 0x70, 0xD7, 0x97, 0x42, 0xA5, 0xA4, 0xFF, + 0xFA, 0xDB, 0xCB, 0xC7, 0x02, 0x01, 0x65, 0x69, 0x6E, 0x6F, 0x72, 0x79, 0xC3, 0xFF, 0xEC, 0xDB, + 0xC4, 0xD5, 0x6B, 0xE5, 0x15, 0xFF, 0xEF, 0xDB, 0xC4, 0xFF, 0xF9, 0xC1, 0x00, 0x91, 0x6F, 0xD5, + 0xE9, 0x41, 0x79, 0xDC, 0x63, 0xC3, 0x02, 0x01, 0x65, 0x6C, 0x6F, 0xDB, 0xA2, 0xD5, 0x55, 0xDB, + 0x1D, 0xC1, 0x01, 0x91, 0x6B, 0xDB, 0x77, 0xC1, 0x00, 0x41, 0x72, 0xD5, 0x50, 0xC1, 0x01, 0x91, + 0x67, 0xDB, 0x6B, 0x43, 0xA5, 0xA4, 0xB6, 0xDB, 0x84, 0xDB, 0x84, 0xDA, 0xB7, 0xC5, 0x02, 0x01, + 0x61, 0x65, 0x6F, 0x79, 0xC3, 0xFF, 0xE4, 0xFF, 0xEA, 0xDB, 0x7A, 0xFF, 0xF0, 0xFF, 0xF6, 0xC1, + 0x02, 0x01, 0x72, 0xDA, 0x9B, 0xC1, 0x00, 0x91, 0x64, 0xD5, 0x22, 0xA1, 0x02, 0x01, 0x69, 0xFA, + 0xCC, 0x03, 0xD2, 0x63, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xDB, + 0x24, 0xFF, 0x74, 0xFF, 0x93, 0xEF, 0xC8, 0xFF, 0xAB, 0xFF, 0xB1, 0xFF, 0xB5, 0xD5, 0x17, 0xFF, + 0xDD, 0xFF, 0xEF, 0xFF, 0xFB, 0xE5, 0x83, 0x41, 0x75, 0xD5, 0xBB, 0x21, 0x74, 0xFC, 0x42, 0x66, + 0x68, 0xDB, 0x0A, 0xDB, 0x0A, 0x41, 0x74, 0xF4, 0xD9, 0x41, 0xB6, 0xDA, 0xFF, 0x43, 0x61, 0x65, + 0xC3, 0xFF, 0xF8, 0xDB, 0x27, 0xFF, 0xFC, 0xC7, 0x04, 0xA2, 0x61, 0x65, 0x69, 0x6F, 0x72, 0x73, + 0x75, 0xFF, 0xE4, 0xDC, 0x13, 0xFF, 0xE7, 0xE3, 0x11, 0xFF, 0xF6, 0xDC, 0x47, 0xD4, 0xDE, 0x41, + 0x74, 0xDC, 0x16, 0x41, 0x6C, 0xE2, 0xB0, 0xC1, 0x03, 0x61, 0x67, 0xD4, 0xCB, 0x42, 0x70, 0x75, + 0xDD, 0x43, 0xD5, 0x27, 0x41, 0x70, 0xEE, 0xE9, 0x42, 0x73, 0x74, 0xFF, 0xFC, 0xF9, 0x55, 0x42, + 0x62, 0x73, 0xDB, 0x28, 0xDB, 0x28, 0x47, 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x73, 0x74, 0xD4, 0x91, + 0xFF, 0xD9, 0xFF, 0xDD, 0xFF, 0xE1, 0xFF, 0xE7, 0xFF, 0xF2, 0xFF, 0xF9, 0xC1, 0x01, 0xD1, 0x72, + 0xEA, 0x94, 0x41, 0x72, 0xD8, 0x1A, 0xC1, 0x01, 0xD1, 0x76, 0xD6, 0x77, 0x21, 0xA4, 0xFA, 0xC7, + 0x03, 0x61, 0x61, 0x65, 0x69, 0x6F, 0x72, 0x73, 0xC3, 0xFF, 0xED, 0xFF, 0xF3, 0xDA, 0xB2, 0xE2, + 0xA9, 0xDA, 0xDA, 0xDA, 0x14, 0xFF, 0xFD, 0x41, 0x6B, 0xFB, 0x91, 0x43, 0x61, 0x72, 0x73, 0xE4, + 0xAF, 0xD4, 0x33, 0xD6, 0x23, 0x43, 0x65, 0x6E, 0x72, 0xDA, 0x25, 0xE0, 0x0B, 0xD4, 0xBF, 0x21, + 0x74, 0xF6, 0x23, 0x63, 0x67, 0x73, 0xE5, 0xE9, 0xFD, 0x41, 0x67, 0xF9, 0x85, 0x41, 0xA5, 0xD4, + 0x38, 0x42, 0x65, 0xC3, 0xD4, 0xD9, 0xFF, 0xFC, 0x42, 0x6B, 0x74, 0xD4, 0x2D, 0xFF, 0xF9, 0x44, + 0x2E, 0x67, 0x6E, 0x73, 0xDC, 0xB1, 0xD7, 0x30, 0xFF, 0xEA, 0xFF, 0xF9, 0x41, 0x73, 0xD9, 0xBE, + 0x41, 0x64, 0xFC, 0xE1, 0x22, 0x64, 0x67, 0xF8, 0xFC, 0x42, 0x61, 0x70, 0xD6, 0xDD, 0xD4, 0x0C, + 0x45, 0x63, 0x67, 0x6E, 0x73, 0x74, 0xDA, 0x87, 0xD6, 0x39, 0xFF, 0xF4, 0xFF, 0xF9, 0xD7, 0xE3, + 0x41, 0x65, 0xE0, 0xF1, 0x46, 0x6C, 0x6D, 0x6E, 0x72, 0x73, 0x76, 0xDA, 0x73, 0xDA, 0x73, 0xD9, + 0xC6, 0xFF, 0xFC, 0xD9, 0x96, 0xD5, 0x90, 0x44, 0xA5, 0xA4, 0xB6, 0xA9, 0xFF, 0xB8, 0xFF, 0xD9, + 0xFF, 0xED, 0xD9, 0xB3, 0x57, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFB, 0xDD, 0xEC, 0xA5, + 0xE1, 0xC6, 0xFC, 0x14, 0xFC, 0x4A, 0xDE, 0x51, 0xFC, 0x77, 0xDC, 0x5C, 0xFC, 0xDD, 0xFD, 0x0C, + 0xFD, 0x28, 0xFD, 0x90, 0xFD, 0xB8, 0xDA, 0x0D, 0xFD, 0xE8, 0xFE, 0x0C, 0xDE, 0x54, 0xFE, 0xAC, + 0xFE, 0xF3, 0xFF, 0x32, 0xFF, 0x5B, 0xFF, 0x8E, 0xFF, 0xF3, 0x42, 0x6E, 0x73, 0xD3, 0x5E, 0xD3, + 0x5E, 0xA0, 0x08, 0x72, 0x42, 0x61, 0x6F, 0xD5, 0x47, 0xFF, 0xFD, 0xC1, 0x00, 0x81, 0x6B, 0xD6, + 0x45, 0x43, 0x64, 0x67, 0x69, 0xD3, 0x8E, 0xFF, 0xF3, 0xFF, 0xFA, 0xA0, 0x08, 0x92, 0x21, 0x69, + 0xFD, 0x41, 0x6F, 0xD3, 0x71, 0x43, 0x6F, 0x72, 0xC3, 0xD3, 0x39, 0xFF, 0xFC, 0xE4, 0x32, 0x44, + 0x68, 0x6B, 0x74, 0x76, 0xD3, 0xFB, 0xFF, 0xF6, 0xDB, 0x06, 0xD3, 0x56, 0xC8, 0x02, 0xF1, 0x66, + 0x67, 0x6A, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0xD7, 0xBB, 0xFF, 0xBE, 0xEE, 0xB2, 0xDA, 0xDC, 0xD9, + 0xAD, 0xFF, 0xD5, 0xFF, 0xE2, 0xFF, 0xF3, 0xC1, 0x03, 0x61, 0x73, 0xE2, 0xA0, 0xC4, 0x03, 0x61, + 0x61, 0x69, 0x6F, 0x72, 0xD9, 0x67, 0xD9, 0x3B, 0xD9, 0x3B, 0xEB, 0xF4, 0x42, 0x73, 0x75, 0xDB, + 0xA4, 0xD3, 0x88, 0x42, 0x6C, 0x6F, 0xD3, 0x81, 0xD3, 0x81, 0x43, 0x64, 0x6F, 0x75, 0xD9, 0x8D, + 0xD3, 0xB0, 0xD2, 0xFD, 0x43, 0x6B, 0x75, 0x79, 0xD2, 0xD4, 0xD2, 0xF3, 0xD2, 0xF3, 0x41, 0x6F, + 0xD3, 0xB4, 0x21, 0x6B, 0xFC, 0x21, 0x73, 0xFD, 0xC1, 0x00, 0xA1, 0x69, 0xD2, 0xD2, 0x42, 0x6F, + 0x74, 0xD3, 0x56, 0xFF, 0xFA, 0x42, 0x69, 0x6F, 0xD3, 0x2F, 0xD2, 0xB9, 0x42, 0x61, 0x72, 0xDB, + 0x64, 0xFF, 0xF9, 0xC9, 0x02, 0xF1, 0x64, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0x74, 0x75, 0x78, 0xFF, + 0xB9, 0xFF, 0xC0, 0xFF, 0xC7, 0xFF, 0xD1, 0xFF, 0xE2, 0xFF, 0xEB, 0xFF, 0xF9, 0xD2, 0xD2, 0xDB, + 0x5D, 0xC2, 0x03, 0xD2, 0x65, 0x6E, 0xE0, 0xE7, 0xD8, 0xC7, 0xA0, 0x08, 0xB3, 0x41, 0x6F, 0xD2, + 0xC2, 0xC1, 0x03, 0x31, 0x72, 0xD2, 0xA4, 0x45, 0x64, 0x67, 0x6B, 0x76, 0xC3, 0xDB, 0x29, 0xFF, + 0xF6, 0xFF, 0xFA, 0xD9, 0x20, 0xFB, 0x8E, 0xC3, 0x01, 0x01, 0x66, 0x70, 0x73, 0xD2, 0x80, 0xD2, + 0xFD, 0xDF, 0x86, 0x43, 0x65, 0x72, 0x74, 0xE5, 0x16, 0xD2, 0xF1, 0xD5, 0x8C, 0xC5, 0x02, 0xF1, + 0x64, 0x6C, 0x6E, 0x73, 0x74, 0xD9, 0x84, 0xD7, 0xB5, 0xFF, 0xDA, 0xFF, 0xEA, 0xFF, 0xF6, 0xC3, + 0x08, 0xE3, 0x65, 0x73, 0x75, 0xF7, 0xDA, 0xD3, 0x61, 0xF8, 0xBF, 0x43, 0x6D, 0x73, 0x76, 0xD8, + 0x59, 0xDF, 0x52, 0xD8, 0x6D, 0x41, 0xA5, 0xEF, 0xDF, 0xC6, 0x03, 0xD2, 0x61, 0x64, 0x67, 0x73, + 0x74, 0xC3, 0xFF, 0xF2, 0xD6, 0xBE, 0xD3, 0x47, 0xD9, 0x3B, 0xED, 0xB5, 0xFF, 0xFC, 0x41, 0x72, + 0xDA, 0xC2, 0x44, 0x61, 0x69, 0x75, 0x76, 0xD5, 0x29, 0xD9, 0xA4, 0xD2, 0xA2, 0xD2, 0x0C, 0xC6, + 0x02, 0xF1, 0x64, 0x69, 0x6D, 0x6E, 0x73, 0x74, 0xD8, 0x39, 0xD2, 0x95, 0xFF, 0xEF, 0xD5, 0x3A, + 0xD7, 0xA3, 0xFF, 0xF3, 0x42, 0x6B, 0x72, 0xD8, 0x10, 0xD8, 0x50, 0x43, 0x64, 0x6E, 0x74, 0xD1, + 0xEF, 0xF2, 0x1D, 0xD1, 0xEF, 0xC2, 0x02, 0x01, 0x61, 0x69, 0xFF, 0xF6, 0xE4, 0x31, 0x42, 0x6B, + 0x70, 0xD1, 0xF7, 0xD1, 0xE9, 0xC6, 0x03, 0xD2, 0x61, 0x6C, 0x6F, 0x70, 0x73, 0xC3, 0xFF, 0xDF, + 0xFF, 0xF0, 0xD2, 0xC6, 0xD1, 0xDF, 0xFF, 0xF9, 0xE2, 0x4E, 0x41, 0x65, 0xD7, 0xEE, 0x42, 0x61, + 0xC3, 0xF7, 0x58, 0xDF, 0x41, 0xC1, 0x02, 0x01, 0x69, 0xDA, 0x84, 0x43, 0x65, 0x6F, 0x72, 0xD3, + 0x90, 0xD8, 0x06, 0xFF, 0xFA, 0x41, 0xA4, 0xF1, 0xD7, 0xC6, 0x03, 0x31, 0x61, 0x6B, 0x6C, 0x6D, + 0x74, 0xC3, 0xF8, 0x15, 0xFF, 0xE1, 0xFF, 0xE5, 0xD4, 0xB2, 0xFF, 0xF2, 0xFF, 0xFC, 0x42, 0x6C, + 0x74, 0xD2, 0x5B, 0xD3, 0xDB, 0xA0, 0x09, 0x13, 0x42, 0x67, 0x6B, 0xFF, 0xFD, 0xD8, 0xED, 0x42, + 0x69, 0x74, 0xD7, 0x6B, 0xD4, 0xAA, 0x41, 0x6C, 0xD1, 0x8F, 0x21, 0x73, 0xFC, 0x47, 0x64, 0x6C, + 0x6D, 0x6E, 0x72, 0x73, 0x74, 0xD8, 0x0A, 0xFF, 0xE1, 0xDD, 0x43, 0xFF, 0xEB, 0xD7, 0x5D, 0xFF, + 0xF2, 0xFF, 0xFD, 0x41, 0x74, 0xD3, 0xD1, 0x42, 0x6E, 0x73, 0xD7, 0x13, 0xFF, 0xFC, 0x41, 0x74, + 0xF6, 0x0F, 0x42, 0x2E, 0x65, 0xD7, 0x08, 0xFF, 0xFC, 0x45, 0x67, 0x6C, 0x6E, 0x72, 0x73, 0xD1, + 0x76, 0xFF, 0xF9, 0xF6, 0x04, 0xD7, 0xDE, 0xD1, 0x9C, 0xC1, 0x02, 0xF1, 0x73, 0xD3, 0x44, 0xC1, + 0x02, 0xF1, 0x74, 0xE8, 0xA7, 0x46, 0x67, 0x6B, 0x6E, 0x72, 0x73, 0x74, 0xD7, 0xC2, 0xD1, 0x4D, + 0xFF, 0xF4, 0xF0, 0x90, 0xFF, 0xFA, 0xD6, 0xE5, 0x41, 0x6E, 0xF0, 0x28, 0x21, 0x65, 0xFC, 0x41, + 0x6B, 0xD5, 0x58, 0x46, 0x62, 0x67, 0x6A, 0x72, 0x73, 0x76, 0xFF, 0x93, 0xFF, 0xF9, 0xD6, 0xF7, + 0xFF, 0xFC, 0xD6, 0xF7, 0xD7, 0xA4, 0x23, 0xA5, 0xA4, 0xB6, 0xB3, 0xCF, 0xED, 0x56, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x79, 0xC3, 0xFD, 0xBF, 0xFD, 0xDA, 0xD9, 0x93, 0xFD, 0xE0, 0xFE, 0x36, 0xFE, 0x54, + 0xFE, 0x5D, 0xD9, 0x93, 0xFE, 0x90, 0xDB, 0x88, 0xDC, 0x98, 0xFE, 0xA2, 0xFE, 0xBC, 0xFE, 0xE2, + 0xFF, 0x18, 0xDB, 0x88, 0xFF, 0x4C, 0xDB, 0x88, 0xFF, 0x80, 0xDC, 0x98, 0xFF, 0x9A, 0xFF, 0xF9, + 0xC1, 0x03, 0x01, 0x73, 0xD0, 0xD2, 0xC1, 0x03, 0x01, 0x6F, 0xD0, 0xA4, 0xC3, 0x03, 0x61, 0x6F, + 0x72, 0x74, 0xD6, 0x8E, 0xFF, 0xFA, 0xD6, 0xF5, 0xC1, 0x03, 0x61, 0x6C, 0xDC, 0x68, 0x44, 0x66, + 0x67, 0x73, 0x75, 0xFF, 0xFA, 0xD7, 0x29, 0xD0, 0x80, 0xD3, 0x9D, 0x42, 0x62, 0x6E, 0xD5, 0xFF, + 0xDC, 0x55, 0x42, 0x64, 0x76, 0xDE, 0x81, 0xD7, 0x15, 0x45, 0x61, 0x62, 0x6B, 0x6D, 0x74, 0xE9, + 0x7F, 0xD9, 0x17, 0xD9, 0x17, 0xD7, 0x0E, 0xD9, 0x17, 0xCA, 0x02, 0xF1, 0x2E, 0x64, 0x66, 0x67, + 0x6B, 0x6C, 0x6D, 0x6E, 0x72, 0x73, 0xD6, 0x51, 0xFF, 0xB7, 0xD9, 0x07, 0xD4, 0xEE, 0xFF, 0xC3, + 0xFF, 0xD5, 0xFF, 0xE2, 0xFF, 0xE9, 0xFF, 0xF0, 0xE7, 0xA4, 0xC3, 0x02, 0x01, 0x61, 0x65, 0x69, + 0xD6, 0x8D, 0xD6, 0x8D, 0xD6, 0x8D, 0x41, 0x6B, 0xD0, 0x5F, 0xC3, 0x03, 0xD2, 0x68, 0x69, 0x79, + 0xFF, 0xF0, 0xD1, 0x21, 0xFF, 0xFC, 0x42, 0x6B, 0x76, 0xD6, 0x52, 0xD6, 0x52, 0xC1, 0x00, 0xE1, + 0x73, 0xD0, 0x11, 0x43, 0x6B, 0x73, 0x74, 0xD0, 0x24, 0xD0, 0x24, 0xD0, 0x17, 0xC1, 0x00, 0x71, + 0x6C, 0xD5, 0x8D, 0x42, 0x70, 0x73, 0xD6, 0x54, 0xD0, 0x14, 0x23, 0x61, 0x69, 0x6F, 0xE9, 0xF3, + 0xF9, 0x41, 0x61, 0xD0, 0x11, 0x44, 0x6B, 0x6E, 0x6F, 0x76, 0xFF, 0xFC, 0xCF, 0xF5, 0xD7, 0xA3, + 0xE5, 0x20, 0x41, 0xA5, 0xDD, 0x4B, 0xC7, 0x03, 0x61, 0x61, 0x64, 0x65, 0x69, 0x72, 0x73, 0xC3, + 0xFF, 0xC0, 0xD0, 0xFA, 0xFF, 0xC7, 0xF6, 0x58, 0xFF, 0xE4, 0xFF, 0xEF, 0xFF, 0xFC, 0x45, 0x69, + 0x6C, 0x72, 0x73, 0xC3, 0xD0, 0x56, 0xDB, 0xA2, 0xF0, 0x31, 0xCF, 0xC0, 0xE6, 0x68, 0xC1, 0x00, + 0xD1, 0x65, 0xDD, 0x1F, 0x21, 0x72, 0xFA, 0x41, 0x79, 0xCF, 0xC0, 0x41, 0x6C, 0xD6, 0x4C, 0x42, + 0x61, 0x69, 0xD0, 0x35, 0xCF, 0xB5, 0x42, 0x73, 0x74, 0xD0, 0x34, 0xFF, 0xF9, 0x41, 0x65, 0xCF, + 0x9D, 0x21, 0x72, 0xFC, 0xCB, 0x02, 0xF1, 0x2E, 0x64, 0x67, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0x74, + 0x75, 0x78, 0xD5, 0x86, 0xFF, 0xCA, 0xFF, 0xE0, 0xDD, 0x9F, 0xFF, 0xE3, 0xFF, 0xE7, 0xE6, 0x9D, + 0xFF, 0xF2, 0xFF, 0xFD, 0xE2, 0x7D, 0xD8, 0x3C, 0x41, 0x6C, 0xEB, 0x24, 0x21, 0x6C, 0xFC, 0xC2, + 0x08, 0xB3, 0x61, 0x69, 0xFF, 0xFD, 0xD2, 0x90, 0x41, 0x69, 0xD5, 0x90, 0x44, 0x2E, 0x64, 0x73, + 0x74, 0xD5, 0x8C, 0xD5, 0x8C, 0xD5, 0x8C, 0xFF, 0xFC, 0x43, 0x6E, 0x72, 0x74, 0xFF, 0xF3, 0xE5, + 0x77, 0xD5, 0x7F, 0xC2, 0x02, 0x01, 0x67, 0x73, 0xCF, 0x47, 0xCF, 0x62, 0xC2, 0x02, 0x01, 0x6D, + 0x72, 0xCF, 0x4B, 0xCF, 0x3E, 0x41, 0x64, 0xCF, 0x35, 0x42, 0x61, 0xC3, 0xFF, 0xFC, 0xFA, 0x60, + 0x41, 0x69, 0xE6, 0x2D, 0x41, 0x61, 0xD3, 0x5D, 0x45, 0x65, 0x6B, 0x70, 0x74, 0x76, 0xD5, 0x25, + 0xCF, 0x7D, 0xDE, 0x50, 0xFF, 0xF8, 0xFF, 0xFC, 0x41, 0xB6, 0xD7, 0xCE, 0xC8, 0x09, 0x42, 0x61, + 0x65, 0x69, 0x6C, 0x6F, 0x72, 0x73, 0xC3, 0xD5, 0x3F, 0xFF, 0xBD, 0xFF, 0xC7, 0xF0, 0xAB, 0xFF, + 0xD0, 0xFF, 0xDD, 0xFF, 0xEC, 0xFF, 0xFC, 0x41, 0x63, 0xCF, 0x00, 0xC2, 0x01, 0x51, 0x6B, 0x76, + 0xCF, 0xBB, 0xD0, 0x48, 0x21, 0x73, 0xF7, 0xA1, 0x03, 0x01, 0x67, 0xFD, 0xC1, 0x03, 0x31, 0x70, + 0xCE, 0xFC, 0x41, 0x72, 0xEE, 0xDC, 0x42, 0x65, 0x72, 0xFF, 0xFC, 0xD2, 0x5D, 0x21, 0x74, 0xF9, + 0x42, 0x6B, 0x74, 0xCE, 0xB8, 0xCE, 0xB8, 0x21, 0x73, 0xF9, 0xCA, 0x04, 0x12, 0x64, 0x65, 0x6B, + 0x6C, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x76, 0xD5, 0x5D, 0xFF, 0xCD, 0xE3, 0xBD, 0xEE, 0xC4, 0xFF, + 0xDD, 0xD2, 0x06, 0xFF, 0xE2, 0xFF, 0xF3, 0xDA, 0x07, 0xFF, 0xFD, 0x42, 0x6E, 0x73, 0xD4, 0xEC, + 0xCE, 0xBA, 0xC3, 0x03, 0xD2, 0x61, 0x75, 0xC3, 0xD4, 0xC9, 0xFF, 0xF9, 0xD7, 0xBB, 0xC1, 0x01, + 0xD1, 0x73, 0xEC, 0x42, 0x41, 0x79, 0xD4, 0xD3, 0x41, 0x74, 0xD1, 0x12, 0x21, 0x66, 0xFC, 0x44, + 0x61, 0x69, 0x6F, 0x75, 0xFF, 0xFD, 0xCE, 0x7B, 0xD4, 0xD2, 0xD2, 0x2D, 0x42, 0x6C, 0x70, 0xCE, + 0x7B, 0xCE, 0x6E, 0xC9, 0x03, 0x61, 0x61, 0x69, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0x75, 0xC3, 0xEC, + 0x98, 0xFF, 0xDB, 0xFF, 0xE1, 0xD4, 0xBE, 0xFF, 0xEC, 0xFF, 0xF9, 0xF5, 0x8F, 0xD4, 0xBE, 0xEF, + 0xB0, 0x41, 0x74, 0xCF, 0x5F, 0xC5, 0x03, 0xD2, 0x64, 0x65, 0x69, 0x6B, 0x73, 0xCE, 0x45, 0xD4, + 0x92, 0xCF, 0x36, 0xCE, 0x45, 0xFF, 0xFC, 0x41, 0x6C, 0xD6, 0xD9, 0x42, 0x72, 0x73, 0xD6, 0xD5, + 0xCE, 0xBF, 0x42, 0x64, 0x67, 0xD6, 0xCE, 0xD6, 0xCE, 0x42, 0x61, 0x63, 0xCE, 0x2E, 0xCE, 0x2E, + 0xC7, 0x02, 0xF1, 0x64, 0x6B, 0x6C, 0x6D, 0x72, 0x73, 0x74, 0xFF, 0xE7, 0xCE, 0xA7, 0xF8, 0x3E, + 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xF9, 0xD3, 0x72, 0xC2, 0x00, 0xD1, 0x6E, 0x73, 0xD4, 0x5C, 0xE0, + 0x0D, 0xC3, 0x00, 0xD1, 0x61, 0x69, 0x6F, 0xE0, 0x45, 0xD4, 0x50, 0xCF, 0x4E, 0x42, 0x67, 0x73, + 0xE9, 0x9F, 0xCD, 0xFA, 0x21, 0x61, 0xF9, 0x41, 0x69, 0xD4, 0xED, 0x41, 0x63, 0xD4, 0x0D, 0x43, + 0x69, 0x6C, 0x6F, 0xD4, 0x32, 0xCD, 0xDB, 0xCD, 0xDB, 0x41, 0x73, 0xD4, 0x1E, 0x42, 0x6E, 0x70, + 0xFF, 0xFC, 0xD4, 0x1A, 0x42, 0x66, 0x67, 0xD4, 0x13, 0xCD, 0xC6, 0x42, 0x61, 0x69, 0xCE, 0x49, + 0xF7, 0x07, 0x41, 0x69, 0xCD, 0xA9, 0x47, 0x61, 0x65, 0x69, 0x72, 0x75, 0x76, 0x79, 0xFF, 0xE7, + 0xCE, 0x41, 0xFF, 0xEE, 0xFF, 0xF5, 0xCE, 0xA9, 0xFF, 0xFC, 0xE6, 0x9B, 0x41, 0xA4, 0xCE, 0xF3, + 0x43, 0x61, 0x69, 0xC3, 0xD3, 0xF1, 0xCD, 0x9A, 0xFF, 0xFC, 0xC9, 0x03, 0x31, 0x69, 0x6B, 0x6C, + 0x6D, 0x6F, 0x70, 0x73, 0x74, 0x76, 0xFF, 0x8E, 0xFF, 0x97, 0xFF, 0xAA, 0xFF, 0xAD, 0xFF, 0xB1, + 0xFF, 0xB5, 0xCE, 0xA6, 0xFF, 0xDC, 0xFF, 0xF6, 0x41, 0x72, 0xF7, 0xBD, 0xA0, 0x09, 0x62, 0x21, + 0x73, 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x6E, 0xD4, 0xB5, 0x41, 0x62, 0xD7, 0x3A, 0x21, 0x6D, 0xFC, + 0x42, 0x61, 0x69, 0xD2, 0xDA, 0xCD, 0x5A, 0x45, 0x64, 0x68, 0x6B, 0x6C, 0x6D, 0xCD, 0x53, 0xD3, + 0xA0, 0xD3, 0xA0, 0xFF, 0xF9, 0xD2, 0xD3, 0x42, 0x70, 0x72, 0xCD, 0x43, 0xD3, 0x90, 0x43, 0x61, + 0x6E, 0x73, 0xCD, 0x3C, 0xCD, 0x3C, 0xCD, 0x3C, 0x41, 0x69, 0xD5, 0xC9, 0x42, 0x70, 0x72, 0xFF, + 0xFC, 0xD3, 0x5C, 0x45, 0x61, 0x65, 0x69, 0x6F, 0xC3, 0xFF, 0xD4, 0xFF, 0xE4, 0xFF, 0xEB, 0xFF, + 0xF9, 0xF8, 0x56, 0x41, 0x6F, 0xE2, 0x64, 0x44, 0x63, 0x65, 0x6B, 0x74, 0xCD, 0x07, 0xCD, 0x9D, + 0xFF, 0xFC, 0xD3, 0x16, 0x41, 0xC3, 0xDE, 0xF5, 0xC8, 0x03, 0xD2, 0x61, 0x65, 0x69, 0x6F, 0x72, + 0x73, 0x75, 0x76, 0xFF, 0x90, 0xFF, 0x9A, 0xFF, 0x9D, 0xFF, 0xA5, 0xFF, 0xDB, 0xFF, 0xEF, 0xD3, + 0x5C, 0xFF, 0xFC, 0x41, 0x74, 0xD0, 0x99, 0xC1, 0x03, 0x01, 0x73, 0xCD, 0xA3, 0xC1, 0x03, 0x61, + 0x70, 0xD3, 0x2A, 0x41, 0x62, 0xD5, 0x7D, 0x46, 0x66, 0x67, 0x69, 0x6D, 0x70, 0x74, 0xFF, 0xEC, + 0xF3, 0x69, 0xD2, 0xC3, 0xFF, 0xF0, 0xFF, 0xF6, 0xFF, 0xFC, 0x41, 0x67, 0xD1, 0x4D, 0x44, 0x2E, + 0x63, 0x6D, 0x74, 0xD3, 0x59, 0xD3, 0x59, 0xD2, 0xAC, 0xD2, 0xAC, 0xA0, 0x09, 0x82, 0xC1, 0x03, + 0x01, 0x61, 0xCD, 0x84, 0xC1, 0x00, 0xC1, 0x6B, 0xD8, 0xAA, 0x45, 0x67, 0x6A, 0x6B, 0x6C, 0x73, + 0xFF, 0xF1, 0xFF, 0xF4, 0xE1, 0xED, 0xD3, 0x3D, 0xFF, 0xFA, 0x43, 0xA5, 0xA4, 0xB6, 0xDA, 0x89, + 0xFF, 0xD4, 0xFF, 0xF0, 0x58, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0x7A, 0xC3, 0xFC, 0x25, 0xE5, + 0x75, 0xFC, 0x56, 0xFC, 0xA2, 0xFC, 0xF0, 0xFD, 0x1B, 0xFD, 0x78, 0xD5, 0x2C, 0xFD, 0xC6, 0xFD, + 0xEE, 0xFE, 0x1F, 0xD7, 0x21, 0xD7, 0x21, 0xFE, 0x41, 0xFE, 0x6C, 0xD7, 0x21, 0xD7, 0x21, 0xFE, + 0xF6, 0xFF, 0x84, 0xFF, 0xB3, 0xD7, 0x21, 0xFF, 0xC6, 0xD2, 0x06, 0xFF, 0xF6, 0x41, 0x64, 0xD2, + 0x57, 0xA1, 0x01, 0xD1, 0x72, 0xFC, 0xA0, 0x09, 0xA2, 0xC3, 0x02, 0x01, 0x6A, 0x6C, 0x79, 0xFF, + 0xFD, 0xFC, 0xCF, 0xCC, 0x4C, 0xA0, 0x09, 0xC2, 0x43, 0x72, 0x73, 0x75, 0xFF, 0xFD, 0xD1, 0xBA, + 0xCC, 0x22, 0x21, 0x6B, 0xF6, 0x41, 0x69, 0xD1, 0xD5, 0x42, 0x61, 0x6F, 0xDD, 0xE6, 0xD2, 0x5E, + 0xC2, 0x00, 0xD1, 0x69, 0x75, 0xCC, 0x25, 0xCC, 0x17, 0x42, 0x6B, 0x74, 0xCC, 0x1C, 0xFF, 0xF7, + 0xC6, 0x02, 0x01, 0x65, 0x69, 0x6C, 0x72, 0x73, 0xC3, 0xCF, 0x1B, 0xD2, 0x0B, 0xFF, 0xE5, 0xFF, + 0xE9, 0xFF, 0xF9, 0xD5, 0x1D, 0xA0, 0x09, 0xE1, 0x21, 0xA5, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6D, + 0xFD, 0x21, 0x72, 0xFD, 0x21, 0xB6, 0xFD, 0x44, 0x66, 0x6C, 0x72, 0xC3, 0xD5, 0x39, 0xD2, 0x01, + 0xD2, 0x20, 0xFF, 0xFD, 0x42, 0x6F, 0x79, 0xD1, 0x46, 0xD1, 0x46, 0xC1, 0x02, 0x11, 0x74, 0xCC, + 0x7F, 0x21, 0x73, 0xFA, 0x21, 0x6F, 0xFD, 0x42, 0x69, 0xC3, 0xD1, 0xE1, 0xF6, 0xE2, 0x42, 0x65, + 0x74, 0xCB, 0xAC, 0xEC, 0x11, 0xC8, 0x01, 0xD1, 0x61, 0x67, 0x6A, 0x6C, 0x6E, 0x72, 0x73, 0xC3, + 0xD4, 0x40, 0xCC, 0xFA, 0xD1, 0xD3, 0xFF, 0xDF, 0xFF, 0xEF, 0xFF, 0xF2, 0xFF, 0xF9, 0xF6, 0x0B, + 0xC1, 0x01, 0xD1, 0x6C, 0xD1, 0xB8, 0x42, 0x6C, 0x75, 0xCB, 0x84, 0xCC, 0x0E, 0xC4, 0x01, 0xD1, + 0x6C, 0x6E, 0x73, 0x76, 0xFC, 0x1B, 0xCB, 0x7D, 0xFF, 0xF9, 0xD1, 0x97, 0xC2, 0x01, 0xD1, 0x6B, + 0x75, 0xD0, 0xEE, 0xCB, 0x7B, 0x41, 0xB6, 0xCD, 0x78, 0x21, 0xC3, 0xFC, 0x44, 0x61, 0x72, 0x75, + 0xC3, 0xCB, 0x5E, 0xCB, 0x52, 0xCB, 0xE8, 0xCC, 0x84, 0x42, 0x69, 0x6C, 0xCB, 0x6C, 0xE0, 0x7C, + 0x42, 0x73, 0xC3, 0xFF, 0xF9, 0xD8, 0xCF, 0x41, 0x76, 0xCB, 0x31, 0xA1, 0x01, 0xD1, 0x6B, 0xFC, + 0x41, 0xC3, 0xDE, 0xF2, 0xC2, 0x01, 0xD1, 0x67, 0x72, 0xD1, 0x64, 0xD1, 0x64, 0x42, 0xA5, 0xA4, + 0xCF, 0xBA, 0xD1, 0x84, 0x4D, 0x61, 0x66, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x73, 0x74, 0x75, + 0x79, 0xC3, 0xFF, 0xB8, 0xFF, 0xC5, 0xD0, 0xE6, 0xFF, 0xC8, 0xFF, 0xDC, 0xD2, 0xC2, 0xD2, 0x4D, + 0xFF, 0xE7, 0xD1, 0x9A, 0xFF, 0xEC, 0xFF, 0xF0, 0xD1, 0x7D, 0xFF, 0xF9, 0x41, 0x72, 0xE7, 0x82, + 0x41, 0x6E, 0xD1, 0x47, 0x41, 0x72, 0xD3, 0xB2, 0x42, 0x61, 0x6C, 0xCA, 0xFF, 0xCA, 0xF2, 0xC1, + 0x02, 0xF1, 0x61, 0xD1, 0x88, 0x41, 0xA4, 0xEF, 0xCB, 0x43, 0x6B, 0x74, 0xC3, 0xCE, 0x27, 0xD2, + 0xDC, 0xFF, 0xFC, 0x42, 0x72, 0x76, 0xCB, 0x61, 0xCA, 0xD7, 0x27, 0x62, 0x65, 0x6F, 0x70, 0x72, + 0x73, 0x74, 0xD2, 0xD6, 0xDA, 0xDE, 0xE5, 0xEF, 0xF9, 0x43, 0x69, 0x72, 0x73, 0xCA, 0xCE, 0xCE, + 0x07, 0xCF, 0x0E, 0x42, 0x6C, 0x72, 0xCA, 0xC4, 0xCA, 0xD2, 0x21, 0x65, 0xF9, 0x44, 0x63, 0x69, + 0x6D, 0x74, 0xCA, 0xA1, 0xCD, 0xD2, 0xCA, 0xA1, 0xFF, 0xFD, 0x42, 0x61, 0x65, 0xD4, 0x09, 0xCB, + 0x2A, 0x42, 0x72, 0x73, 0xFF, 0xF9, 0xCA, 0xB4, 0x43, 0xA5, 0xA4, 0xB6, 0xCA, 0x9F, 0xD0, 0xE9, + 0xD0, 0xDF, 0x49, 0x63, 0x67, 0x69, 0x6A, 0x6B, 0x73, 0x74, 0x79, 0xC3, 0xCA, 0x88, 0xFF, 0xC7, + 0xDC, 0x55, 0xCA, 0x88, 0xCD, 0xB4, 0xFF, 0xDB, 0xFF, 0xEF, 0xD0, 0xDF, 0xFF, 0xF6, 0x43, 0x64, + 0x6D, 0x72, 0xCA, 0xF6, 0xD2, 0xDC, 0xD0, 0xB9, 0x41, 0x6E, 0xEA, 0x90, 0x21, 0x69, 0xFC, 0xC1, + 0x00, 0x41, 0x65, 0xCA, 0x4F, 0x43, 0x6C, 0x72, 0x75, 0xFF, 0xFA, 0xCA, 0xDF, 0xCA, 0xDF, 0x47, + 0x65, 0x69, 0x6C, 0x70, 0x72, 0x73, 0xC3, 0xD0, 0xA2, 0xD0, 0xA2, 0xFF, 0xED, 0xFF, 0xF6, 0xED, + 0x11, 0xDA, 0xBB, 0xDC, 0x3A, 0x41, 0x67, 0xEC, 0x0E, 0xC3, 0x03, 0x31, 0x6B, 0x6E, 0x70, 0xD0, + 0x7E, 0xFF, 0xFC, 0xD0, 0x4B, 0x41, 0xA4, 0xFE, 0x00, 0x42, 0x6E, 0xC3, 0xCF, 0xE1, 0xFF, 0xFC, + 0x41, 0x68, 0xD0, 0x48, 0x41, 0x69, 0xD5, 0xEC, 0xA1, 0x02, 0xF1, 0x6E, 0xFC, 0x43, 0x61, 0x72, + 0xC3, 0xFF, 0xFB, 0xCA, 0x97, 0xDD, 0xC5, 0x41, 0x6E, 0xEE, 0xE9, 0xA1, 0x01, 0xD1, 0x65, 0xFC, + 0x42, 0x75, 0xC3, 0xCA, 0x84, 0xCB, 0x20, 0x42, 0x6C, 0x72, 0xCA, 0x7D, 0xCA, 0x00, 0x41, 0x69, + 0xE0, 0xE3, 0x42, 0x70, 0x74, 0xD0, 0x5C, 0xFF, 0xFC, 0xC2, 0x00, 0xD1, 0x65, 0x6F, 0xC9, 0xD5, + 0xC9, 0xD5, 0x42, 0x65, 0x72, 0xC9, 0xE5, 0xFF, 0xF7, 0x42, 0xA4, 0xB6, 0xD0, 0x28, 0xD0, 0x28, + 0x4F, 0x61, 0x64, 0x65, 0x67, 0x69, 0x6B, 0x6D, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x79, 0xC3, + 0xFF, 0x99, 0xFF, 0xA9, 0xFF, 0xB0, 0xFF, 0xBD, 0xFF, 0xCB, 0xD0, 0x67, 0xE0, 0x6A, 0xFF, 0xD0, + 0xD0, 0x21, 0xFF, 0xD7, 0xFF, 0xE2, 0xFF, 0xF2, 0xD0, 0x21, 0xD0, 0x17, 0xFF, 0xF9, 0x41, 0xA4, + 0xE0, 0x90, 0x21, 0xC3, 0xFC, 0x41, 0x70, 0xCF, 0xEC, 0x42, 0x6C, 0x6F, 0xC9, 0xAC, 0xFF, 0xFC, + 0x41, 0x69, 0xEA, 0x24, 0xA0, 0x09, 0xF2, 0x42, 0x6B, 0x74, 0xCA, 0x0D, 0xFF, 0xFD, 0x43, 0x67, + 0x6B, 0x6C, 0xC9, 0x7C, 0xCE, 0x0D, 0xDB, 0x51, 0xC2, 0x00, 0x41, 0x6F, 0xC3, 0xE6, 0xA7, 0xD3, + 0x84, 0x43, 0x69, 0x72, 0x75, 0xFF, 0xED, 0xFF, 0xF7, 0xCF, 0x31, 0x46, 0x66, 0x6B, 0x6F, 0x70, + 0x73, 0x74, 0xFF, 0xC7, 0xFF, 0xCE, 0xE4, 0x73, 0xFF, 0xD5, 0xFF, 0xDC, 0xFF, 0xF6, 0x41, 0x61, + 0xE1, 0x71, 0x21, 0x6C, 0xFC, 0x43, 0x2E, 0x65, 0x6E, 0xCF, 0xE2, 0xCF, 0xE2, 0xCF, 0xE2, 0x42, + 0x6F, 0x75, 0xCE, 0xBB, 0xC9, 0xA5, 0x41, 0x76, 0xC9, 0x34, 0x43, 0x69, 0x72, 0x73, 0xC9, 0x3D, + 0xE5, 0xB4, 0xCA, 0x08, 0xC1, 0x01, 0xD1, 0x6E, 0xCE, 0xA6, 0x41, 0x6E, 0xCB, 0x33, 0x21, 0x69, + 0xFC, 0x42, 0xA5, 0xA4, 0xCE, 0x99, 0xCF, 0x66, 0x4A, 0x61, 0x69, 0x6F, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x79, 0xC3, 0xFF, 0xCA, 0xFF, 0xCD, 0xCF, 0x69, 0xFF, 0xD7, 0xFF, 0xDE, 0xFF, 0xE2, 0xFF, + 0xEC, 0xFF, 0xF6, 0xCF, 0x69, 0xFF, 0xF9, 0xC1, 0x03, 0x31, 0xC3, 0xF1, 0xFE, 0xA0, 0x0A, 0x12, + 0x21, 0x74, 0xFD, 0x45, 0x6E, 0x70, 0x72, 0x73, 0x74, 0xC9, 0x02, 0xC9, 0x02, 0xFF, 0xF4, 0xC9, + 0x71, 0xFF, 0xFD, 0xC1, 0x01, 0xD1, 0x6E, 0xC8, 0xF2, 0x43, 0x69, 0x6C, 0x6D, 0xC9, 0x5B, 0xC8, + 0xD1, 0xCB, 0x4B, 0x45, 0x61, 0x69, 0x72, 0x73, 0xC3, 0xFF, 0xF0, 0xCF, 0x1E, 0xC8, 0xC7, 0xFF, + 0xF6, 0xDB, 0x8B, 0x43, 0xA5, 0xA4, 0xB6, 0xCF, 0x04, 0xFD, 0xBD, 0xCF, 0x04, 0x59, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0xC3, 0xFC, 0x74, 0xFC, 0x7C, 0xFC, 0x95, 0xFC, 0xB3, 0xCF, + 0x04, 0xFC, 0xDA, 0xFD, 0x08, 0xFD, 0x23, 0xCF, 0x04, 0xFD, 0x30, 0xFD, 0x87, 0xFD, 0xDD, 0xFE, + 0x25, 0xFE, 0x41, 0xFE, 0x62, 0xCE, 0x2D, 0xFE, 0xE3, 0xFF, 0x4E, 0xFF, 0x9B, 0xFF, 0xC6, 0xFF, + 0xE6, 0xD4, 0x78, 0xC8, 0xBA, 0xCB, 0xBE, 0xFF, 0xF6, 0xC1, 0x00, 0xB1, 0x73, 0xCA, 0x74, 0x21, + 0x69, 0xFA, 0x43, 0x61, 0x73, 0xC3, 0xC8, 0x65, 0xC9, 0x18, 0xDB, 0xEC, 0x42, 0x64, 0x74, 0xD6, + 0x57, 0xC8, 0x6C, 0x43, 0x62, 0x6D, 0x73, 0xD0, 0xED, 0xCE, 0xE4, 0xC8, 0x3B, 0xC1, 0x03, 0x61, + 0x69, 0xC8, 0x4A, 0x42, 0x6B, 0x74, 0xFF, 0xFA, 0xCB, 0x48, 0xCA, 0x02, 0xF1, 0x66, 0x67, 0x6B, + 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x75, 0xCE, 0xCD, 0xC8, 0x4B, 0xFF, 0xD5, 0xFF, 0xD8, 0xFF, + 0xE2, 0xCE, 0x20, 0xFF, 0xE9, 0xFF, 0xF9, 0xD5, 0x47, 0xC8, 0x30, 0xA0, 0x07, 0x63, 0x42, 0x69, + 0x6F, 0xC7, 0xFA, 0xC8, 0x34, 0x21, 0x72, 0xF9, 0x21, 0x74, 0xFD, 0x41, 0x69, 0xC8, 0x8F, 0x42, + 0x64, 0x73, 0xCE, 0x98, 0xFF, 0xFC, 0x43, 0x67, 0x6E, 0x74, 0xD3, 0xCA, 0xCD, 0xE4, 0xCA, 0x43, + 0x41, 0x6F, 0xCD, 0xDA, 0xC1, 0x03, 0x01, 0x6C, 0xC8, 0x01, 0x43, 0x62, 0x69, 0x73, 0xF6, 0x6C, + 0xFF, 0xF6, 0xFF, 0xFA, 0x47, 0x6B, 0x6C, 0x6D, 0x6E, 0x72, 0x74, 0x78, 0xFF, 0xD4, 0xFF, 0xDB, + 0xCE, 0x73, 0xFF, 0xE2, 0xFF, 0xF6, 0xDD, 0xE5, 0xCE, 0x73, 0x42, 0x61, 0x6F, 0xC7, 0xCD, 0xC7, + 0xCD, 0x42, 0x6F, 0x74, 0xC7, 0xC6, 0xFF, 0xF9, 0x45, 0x65, 0x67, 0x6F, 0x70, 0x73, 0xC8, 0x3F, + 0xCD, 0x72, 0xCA, 0xF8, 0xCD, 0xA2, 0xFF, 0xF9, 0x41, 0xA4, 0xEB, 0xCB, 0xA1, 0x02, 0x01, 0xC3, + 0xFC, 0xA0, 0x0A, 0x33, 0x42, 0x2E, 0x65, 0xD3, 0x6C, 0xCD, 0xC4, 0x43, 0x63, 0x6E, 0x72, 0xCD, + 0xBD, 0xFF, 0xF9, 0xFC, 0x95, 0x42, 0x76, 0x78, 0xCD, 0xD2, 0xCD, 0x75, 0x43, 0x63, 0x6B, 0x74, + 0xCD, 0x6E, 0xCD, 0x3E, 0xCE, 0x1B, 0x42, 0x6D, 0x70, 0xCD, 0x34, 0xCD, 0xC1, 0xC5, 0x02, 0x01, + 0x61, 0x65, 0x69, 0x6A, 0x6F, 0xFF, 0xDE, 0xFF, 0xE8, 0xFF, 0xEF, 0xCD, 0xBA, 0xFF, 0xF9, 0xC1, + 0x04, 0xA2, 0x69, 0xCD, 0xA8, 0x43, 0x69, 0x6A, 0x79, 0xEC, 0x3B, 0xCD, 0xF2, 0xC7, 0x7D, 0x41, + 0x67, 0xCF, 0xF1, 0xC1, 0x03, 0x01, 0x74, 0xC9, 0x24, 0xC1, 0x03, 0x31, 0xC3, 0xC8, 0x67, 0xC8, + 0x02, 0xF1, 0x61, 0x63, 0x66, 0x69, 0x6C, 0x72, 0x73, 0x76, 0xD3, 0x11, 0xCF, 0xE1, 0xCF, 0xE1, + 0xCA, 0x4C, 0xFF, 0xE6, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xFA, 0x43, 0x72, 0x73, 0x78, 0xCD, 0x7A, + 0xCE, 0xA4, 0xCD, 0x4E, 0xC1, 0x01, 0xD1, 0x6E, 0xD5, 0x64, 0x42, 0x6E, 0x73, 0xC7, 0x10, 0xC7, + 0x1D, 0x42, 0x6E, 0x74, 0xC7, 0x09, 0xC7, 0x09, 0x43, 0xA5, 0xA4, 0xB6, 0xFF, 0xF9, 0xC7, 0x02, + 0xC7, 0x02, 0xC5, 0x02, 0x81, 0x61, 0x69, 0x75, 0x79, 0xC3, 0xC6, 0xF8, 0xFF, 0xE8, 0xC7, 0x05, + 0xC6, 0xF8, 0xFF, 0xF6, 0x42, 0x63, 0x66, 0xC6, 0xF3, 0xC6, 0xE6, 0xC5, 0x02, 0x81, 0x61, 0x69, + 0x75, 0x79, 0xC3, 0xC6, 0xDF, 0xC6, 0xDF, 0xC6, 0xDF, 0xC6, 0xDF, 0xD4, 0x64, 0xCC, 0x03, 0x61, + 0x61, 0x65, 0x69, 0x6A, 0x6C, 0x6F, 0x70, 0x72, 0x74, 0x75, 0x79, 0xC3, 0xFF, 0xAD, 0xCD, 0x1A, + 0xFF, 0xB7, 0xC6, 0xCD, 0xFF, 0xD5, 0xFF, 0xE7, 0xC7, 0xE3, 0xFF, 0xEE, 0xC8, 0x22, 0xCC, 0xE7, + 0xCC, 0x4D, 0xCD, 0xC5, 0xC1, 0x03, 0x61, 0x78, 0xD2, 0x7C, 0xC2, 0x00, 0xC1, 0x61, 0x6F, 0xC6, + 0x94, 0xC7, 0x60, 0xA1, 0x02, 0xF1, 0x74, 0xF7, 0x21, 0x73, 0xFB, 0x42, 0x63, 0x67, 0xCC, 0x7F, + 0xC7, 0x5B, 0x45, 0x67, 0x6B, 0x6C, 0x6E, 0x6F, 0xCC, 0xD5, 0xCC, 0xD5, 0xD2, 0x5E, 0xFF, 0xF9, + 0xD8, 0x5D, 0xC2, 0x03, 0x01, 0x67, 0x72, 0xC6, 0x6C, 0xCC, 0xC5, 0x41, 0x76, 0xCC, 0x5F, 0x43, + 0xA5, 0xA4, 0xB6, 0xCD, 0x08, 0xEA, 0x94, 0xFF, 0xFC, 0xA5, 0x05, 0x42, 0x61, 0x65, 0x69, 0x6F, + 0xC3, 0xBB, 0xCF, 0xD9, 0xE9, 0xF6, 0xC1, 0x02, 0x01, 0x6D, 0xCC, 0x44, 0x21, 0x6C, 0xFA, 0xA0, + 0x0A, 0x62, 0x21, 0x6F, 0xFD, 0x41, 0x61, 0xCC, 0x92, 0xC1, 0x03, 0x31, 0x64, 0xCC, 0x8E, 0x41, + 0xA4, 0xCE, 0xD6, 0xC2, 0x08, 0x52, 0x61, 0xC3, 0xF0, 0x82, 0xFF, 0xFC, 0xC8, 0x03, 0x61, 0x61, + 0x68, 0x6B, 0x6C, 0x6E, 0x6F, 0x74, 0xC3, 0xFF, 0xE0, 0xFF, 0xE6, 0xEC, 0xDF, 0xD9, 0x51, 0xFF, + 0xE9, 0xFF, 0xED, 0xFF, 0xF7, 0xD6, 0xDA, 0xC1, 0x03, 0xD2, 0x72, 0xE8, 0xD9, 0x41, 0x76, 0xE0, + 0x60, 0x21, 0x6F, 0xFC, 0x42, 0x6C, 0x74, 0xFF, 0xFD, 0xDB, 0x53, 0x42, 0x67, 0x6B, 0xD1, 0xD5, + 0xCB, 0xEF, 0xC4, 0x02, 0xF1, 0x67, 0x6C, 0x6E, 0x73, 0xCC, 0x95, 0xFF, 0xF2, 0xFF, 0xF9, 0xC6, + 0x7B, 0x41, 0x64, 0xCD, 0x97, 0x42, 0x6C, 0x72, 0xCB, 0xD5, 0xC9, 0xC2, 0x44, 0xA5, 0xA4, 0xB6, + 0xA9, 0xFF, 0xF5, 0xFF, 0xF9, 0xD3, 0xD7, 0xCB, 0xCE, 0x56, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0xC3, + 0xFD, 0xA1, 0xDE, 0xC0, 0xCE, 0x77, 0xFD, 0xC2, 0xFD, 0xFB, 0xD0, 0x6C, 0xD0, 0x6F, 0xCE, 0x77, + 0xFE, 0x1F, 0xFE, 0x33, 0xFE, 0x38, 0xFE, 0x64, 0xD0, 0x6C, 0xFE, 0x76, 0xFE, 0x96, 0xFF, 0x04, + 0xFF, 0x70, 0xFF, 0xA3, 0xFF, 0xBE, 0xFF, 0xD9, 0xD0, 0x6C, 0xFF, 0xF3, 0xC1, 0x00, 0x41, 0x65, + 0xCB, 0x7E, 0x21, 0x75, 0xFA, 0x41, 0x69, 0xC8, 0x2B, 0x43, 0x65, 0x72, 0x75, 0xD8, 0x77, 0xCC, + 0x1E, 0xC6, 0x0B, 0xA0, 0x0A, 0x82, 0x41, 0xA4, 0xCE, 0x1A, 0x21, 0xC3, 0xFC, 0x42, 0x66, 0x72, + 0xFF, 0xFD, 0xC5, 0x6D, 0x41, 0x6F, 0xC8, 0x77, 0xC1, 0x00, 0x91, 0x2E, 0xCB, 0x52, 0xA0, 0x03, + 0x33, 0x43, 0x65, 0x72, 0x73, 0xFF, 0xF7, 0xFF, 0xFD, 0xC5, 0x81, 0x41, 0x76, 0xCD, 0xF5, 0x22, + 0x64, 0x73, 0xF2, 0xFC, 0xC1, 0x03, 0x31, 0x61, 0xCB, 0x93, 0xCA, 0x03, 0x01, 0x2E, 0x62, 0x65, + 0x67, 0x6B, 0x6D, 0x6E, 0x73, 0x74, 0xC3, 0xCB, 0x6E, 0xCB, 0xDD, 0xCB, 0x6E, 0xCB, 0xDD, 0xCB, + 0x6E, 0xCB, 0xDD, 0xCB, 0x6E, 0xCB, 0x6E, 0xFF, 0xFA, 0xD6, 0x2D, 0xC1, 0x00, 0xD1, 0x65, 0xD8, + 0x15, 0xC2, 0x00, 0x91, 0x68, 0x74, 0xC5, 0x0D, 0xFF, 0xFA, 0x42, 0x69, 0x72, 0xFE, 0xA1, 0xCB, + 0x8F, 0xCF, 0x02, 0xF1, 0x2E, 0x63, 0x64, 0x65, 0x66, 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x72, 0x73, + 0x74, 0x76, 0x79, 0xCA, 0xF9, 0xFF, 0x84, 0xFF, 0x88, 0xFF, 0x92, 0xFF, 0x9C, 0xD2, 0x6C, 0xCD, + 0xAF, 0xFF, 0xA3, 0xFF, 0xBE, 0xCA, 0xD1, 0xFF, 0xC9, 0xFF, 0xF0, 0xFF, 0xF9, 0xCD, 0xAF, 0xD5, + 0xA8, 0x42, 0x66, 0x67, 0xCB, 0x07, 0xDA, 0x26, 0x41, 0x61, 0xFD, 0xC9, 0x43, 0xA5, 0xA4, 0xB6, + 0xCB, 0x1B, 0xCB, 0x1B, 0xD3, 0x1C, 0xC6, 0x03, 0x62, 0x61, 0x6A, 0x6F, 0x72, 0x75, 0xC3, 0xFF, + 0xEB, 0xCB, 0x11, 0xD3, 0x12, 0xFF, 0xF2, 0xEB, 0x38, 0xFF, 0xF6, 0x41, 0x75, 0xDB, 0x3C, 0xC1, + 0x03, 0x31, 0x6F, 0xD0, 0x81, 0x42, 0x72, 0x75, 0xC5, 0xBB, 0xC4, 0xB2, 0x42, 0x67, 0x6E, 0xCB, + 0x3B, 0xD7, 0x94, 0x41, 0x72, 0xC6, 0xB6, 0x42, 0x72, 0x74, 0xC7, 0xD9, 0xFF, 0xFC, 0x42, 0x64, + 0x75, 0xCB, 0x29, 0xC4, 0x8C, 0x42, 0x70, 0x73, 0xC4, 0x73, 0xCA, 0x75, 0x45, 0x2E, 0x69, 0x6B, + 0x6C, 0xC3, 0xCA, 0xAC, 0xCD, 0x24, 0xCD, 0x24, 0xCD, 0x24, 0xED, 0x89, 0x42, 0x2E, 0x6E, 0xCA, + 0x5E, 0xCA, 0x5E, 0x41, 0x6B, 0xC5, 0x27, 0x41, 0x69, 0xC7, 0xA9, 0x43, 0x6F, 0x73, 0x74, 0xCA, + 0x4F, 0xFF, 0xF8, 0xFF, 0xFC, 0x45, 0x2E, 0x65, 0x72, 0x79, 0xC3, 0xCA, 0x45, 0xDC, 0xED, 0xC9, + 0x59, 0xCA, 0xF2, 0xD4, 0xD9, 0x41, 0x69, 0xFC, 0xFA, 0xD1, 0x02, 0xF1, 0x2E, 0x61, 0x62, 0x63, + 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0x76, 0x78, 0xCA, 0x31, 0xFF, + 0x92, 0xC4, 0x41, 0xFF, 0x96, 0xFF, 0x9C, 0xDC, 0xD9, 0xFF, 0xA3, 0xFF, 0xAE, 0xFF, 0xB5, 0xFF, + 0xBC, 0xFF, 0xC3, 0xC4, 0x41, 0xFF, 0xD3, 0xFF, 0xE2, 0xFF, 0xEC, 0xFF, 0xFC, 0xCA, 0xDE, 0x41, + 0x72, 0xE7, 0xD5, 0x21, 0xB6, 0xFC, 0xA1, 0x03, 0xD2, 0xC3, 0xFD, 0x41, 0x74, 0xC4, 0xF8, 0xC2, + 0x03, 0xD2, 0x67, 0x73, 0xD5, 0xDD, 0xFF, 0xFC, 0x41, 0x64, 0xDF, 0xA4, 0x21, 0x6E, 0xFC, 0xA1, + 0x03, 0x61, 0x61, 0xFD, 0xC1, 0x03, 0x61, 0xC3, 0xD5, 0xD5, 0x43, 0x69, 0x72, 0x75, 0xC9, 0xD0, + 0xFF, 0xFA, 0xC9, 0xD0, 0x42, 0x73, 0x74, 0xC4, 0xAE, 0xE1, 0xD7, 0xC1, 0x03, 0x61, 0x73, 0xC3, + 0xF7, 0x42, 0x65, 0x72, 0xEB, 0x94, 0xC3, 0xF1, 0x45, 0x64, 0x67, 0x72, 0x73, 0x74, 0xFF, 0xF3, + 0xFF, 0xF9, 0xCC, 0x68, 0xCC, 0x68, 0xCC, 0x68, 0xC1, 0x02, 0x81, 0x74, 0xC9, 0xA2, 0x41, 0x6C, + 0xCB, 0x68, 0x41, 0xA4, 0xC6, 0xB9, 0x21, 0xC3, 0xFC, 0x21, 0x74, 0xFD, 0x41, 0x75, 0xD4, 0x0E, + 0x42, 0x69, 0x73, 0xC9, 0x2E, 0xC3, 0x8E, 0x4C, 0x61, 0x62, 0x66, 0x67, 0x6B, 0x6D, 0x6E, 0x6F, + 0x70, 0x73, 0x74, 0x76, 0xEA, 0x97, 0xCA, 0x30, 0xFF, 0xB3, 0xE8, 0x79, 0xFF, 0xBD, 0xCC, 0x91, + 0xFF, 0xD1, 0xFF, 0xE1, 0xFF, 0xE7, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xF9, 0xC4, 0x03, 0x31, 0x69, + 0x6F, 0x75, 0xC3, 0xD1, 0xBC, 0xC9, 0xBB, 0xC8, 0xEE, 0xCF, 0xBA, 0x41, 0x6B, 0xC3, 0xC0, 0x42, + 0x6B, 0x73, 0xC9, 0x89, 0xC3, 0x83, 0x43, 0x61, 0x65, 0xC3, 0xF5, 0x83, 0xC6, 0x65, 0xEE, 0x83, + 0xC1, 0x01, 0xD1, 0x64, 0xC9, 0x78, 0x41, 0x72, 0xC8, 0xC4, 0x42, 0xA4, 0xB6, 0xC9, 0x97, 0xFF, + 0xFC, 0xC9, 0x03, 0x61, 0x61, 0x65, 0x69, 0x6C, 0x6E, 0x6F, 0x74, 0x75, 0xC3, 0xFF, 0xDA, 0xFF, + 0xDE, 0xC9, 0x90, 0xFF, 0xE5, 0xD0, 0xB4, 0xFF, 0xEF, 0xC4, 0x8E, 0xDC, 0xBF, 0xFF, 0xF9, 0x42, + 0x6B, 0x76, 0xD2, 0xE0, 0xC8, 0x9B, 0xC3, 0x03, 0xD2, 0x61, 0x64, 0x73, 0xFF, 0xF9, 0xDF, 0x79, + 0xE3, 0x46, 0x41, 0x62, 0xC9, 0x36, 0x21, 0x72, 0xFC, 0x41, 0x6A, 0xC3, 0x42, 0xC4, 0x03, 0xD2, + 0x61, 0x69, 0x73, 0x74, 0xFF, 0xF9, 0xE9, 0x71, 0xFF, 0xFC, 0xC7, 0x8A, 0x41, 0x6D, 0xC9, 0x08, + 0x42, 0x6B, 0x74, 0xC3, 0x05, 0xC3, 0x05, 0xC6, 0x03, 0xD2, 0x61, 0x67, 0x6B, 0x6F, 0x73, 0x74, + 0xC7, 0x30, 0xC3, 0xF9, 0xC6, 0x29, 0xFF, 0xF5, 0xFF, 0xF9, 0xC2, 0xE3, 0x41, 0x72, 0xCB, 0xCC, + 0x42, 0x6A, 0x6C, 0xCB, 0x70, 0xD8, 0x93, 0xC1, 0x00, 0xA1, 0x6E, 0xCE, 0x99, 0x21, 0x61, 0xFA, + 0x41, 0x75, 0xCE, 0x90, 0x43, 0x61, 0x64, 0x76, 0xFF, 0xFC, 0xE7, 0x79, 0xC2, 0xD1, 0x42, 0x6C, + 0x70, 0xC2, 0xAC, 0xC5, 0x4B, 0x45, 0x61, 0x64, 0x67, 0x69, 0x6E, 0xC3, 0x2F, 0xCB, 0x4B, 0xCB, + 0x4B, 0xE9, 0x92, 0xC8, 0x95, 0x43, 0x69, 0x6E, 0x76, 0xCA, 0x21, 0xC3, 0x1F, 0xC2, 0x89, 0x4A, + 0x62, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0xC2, 0x8B, 0xFF, 0xBD, 0xD1, 0x87, + 0xFF, 0xC1, 0xFF, 0xCE, 0xFF, 0xD5, 0xFF, 0xDF, 0xFF, 0xE6, 0xFF, 0xF6, 0xCA, 0x47, 0x41, 0xC3, + 0xED, 0x9B, 0xC2, 0x03, 0xD2, 0x6C, 0xC3, 0xFF, 0xFC, 0xED, 0x97, 0x41, 0x69, 0xC4, 0xEC, 0x21, + 0x67, 0xFC, 0x21, 0x69, 0xFD, 0x42, 0x6C, 0x73, 0xFF, 0xFD, 0xC2, 0x8A, 0x42, 0x62, 0x6D, 0xC8, + 0x7C, 0xC8, 0x7C, 0xC1, 0x02, 0x81, 0x6B, 0xC3, 0x1F, 0xC8, 0x04, 0xA2, 0x61, 0x64, 0x65, 0x69, + 0x6B, 0x6F, 0x73, 0x75, 0xC5, 0x2D, 0xC2, 0x5C, 0xFF, 0xEC, 0xD2, 0x02, 0xC2, 0x4B, 0xFF, 0xF3, + 0xFF, 0xFA, 0xC8, 0x72, 0x42, 0x6C, 0x78, 0xCA, 0xE2, 0xC8, 0x54, 0x41, 0x74, 0xDE, 0x59, 0x43, + 0x6C, 0x73, 0x74, 0xC2, 0x33, 0xC2, 0xA5, 0xFF, 0xFC, 0x45, 0x69, 0x6E, 0x6F, 0x75, 0xC3, 0xFE, + 0xC7, 0xD1, 0x3F, 0xFF, 0xF6, 0xC8, 0x2B, 0xCA, 0x17, 0x41, 0x76, 0xC4, 0x82, 0x21, 0x73, 0xFC, + 0x21, 0x67, 0xFD, 0xA0, 0x0A, 0xA2, 0x21, 0xB6, 0xFD, 0x44, 0x61, 0x65, 0x6F, 0xC3, 0xFF, 0xF7, + 0xC8, 0x1F, 0xC8, 0x1F, 0xFF, 0xFD, 0x42, 0x61, 0xC3, 0xC8, 0xEE, 0xD5, 0x78, 0xC2, 0x01, 0xD1, + 0x6C, 0x6F, 0xC7, 0xF7, 0xC7, 0xF7, 0x42, 0x64, 0x69, 0xC1, 0xE1, 0xC7, 0x54, 0xC2, 0x00, 0x41, + 0x6B, 0x6E, 0xC7, 0x4D, 0xFF, 0xF9, 0x41, 0x6C, 0xC1, 0xDC, 0xC1, 0x00, 0xA1, 0xC3, 0xC9, 0xC6, + 0x46, 0x61, 0x65, 0x69, 0x6F, 0x72, 0x75, 0xC9, 0x3E, 0xFF, 0xED, 0xFF, 0xF6, 0xE1, 0x5C, 0xFF, + 0xFA, 0xC8, 0x07, 0x41, 0xA4, 0xC7, 0xC1, 0xC2, 0x01, 0xD1, 0x61, 0xC3, 0xCA, 0xBC, 0xFF, 0xFC, + 0xCB, 0x03, 0x61, 0x65, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0x76, 0x79, 0xFF, 0x74, + 0xE0, 0x2A, 0xFF, 0x89, 0xFF, 0xA9, 0xCA, 0x98, 0xFF, 0xB6, 0xFF, 0xBD, 0xC2, 0xB0, 0xFF, 0xE0, + 0xFF, 0xF7, 0xC7, 0xF1, 0x41, 0x69, 0xC7, 0x90, 0x21, 0x74, 0xFC, 0x43, 0x66, 0x6B, 0x6E, 0xC7, + 0x89, 0xFF, 0xFD, 0xC1, 0xF9, 0x42, 0x69, 0xC3, 0xD1, 0x03, 0xCA, 0x88, 0xC6, 0x03, 0xD2, 0x61, + 0x69, 0x6F, 0x72, 0x74, 0x75, 0xFF, 0xEF, 0xD3, 0x2B, 0xD7, 0x84, 0xFF, 0xF9, 0xC1, 0x5E, 0xC7, + 0xB8, 0x42, 0x62, 0x72, 0xCC, 0x55, 0xC1, 0x49, 0xC1, 0x02, 0x71, 0x76, 0xC1, 0x6A, 0x21, 0x73, + 0xFA, 0x41, 0x6C, 0xE6, 0x3C, 0xC1, 0x01, 0xE1, 0x73, 0xC2, 0x8E, 0x21, 0x6E, 0xFA, 0x41, 0x70, + 0xC7, 0xC9, 0xC1, 0x01, 0x01, 0x61, 0xC1, 0x35, 0x21, 0x68, 0xFA, 0x41, 0x67, 0xCC, 0xF5, 0xC2, + 0x03, 0x61, 0x69, 0x72, 0xFF, 0xFC, 0xC7, 0x2C, 0x4B, 0x62, 0x64, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, + 0x73, 0x74, 0x76, 0xC3, 0xFF, 0xC9, 0xC9, 0x50, 0xFF, 0xD6, 0xFF, 0xD9, 0xD4, 0x08, 0xFF, 0xE3, + 0xFF, 0xE6, 0xFF, 0xF0, 0xFF, 0xF7, 0xD6, 0x0F, 0xDB, 0x06, 0x41, 0xA4, 0xC1, 0x31, 0x21, 0xC3, + 0xFC, 0x42, 0x6A, 0x6B, 0xC1, 0x73, 0xFF, 0xFD, 0xA1, 0x03, 0xD2, 0x73, 0xF9, 0x42, 0x64, 0x6F, + 0xC5, 0xA1, 0xC0, 0xEA, 0x41, 0x65, 0xE6, 0x81, 0x41, 0x67, 0xC1, 0xA1, 0x45, 0x67, 0x6C, 0x6E, + 0x72, 0x73, 0xFF, 0xF8, 0xC7, 0x6B, 0xFF, 0xFC, 0xC2, 0x9B, 0xC1, 0x51, 0x41, 0x6B, 0xC2, 0x44, + 0x42, 0x67, 0x73, 0xC1, 0x89, 0xE8, 0x04, 0xC3, 0x03, 0x31, 0x61, 0x73, 0x74, 0xC3, 0x9F, 0xC0, + 0xDB, 0xC2, 0xD2, 0x46, 0x63, 0x6B, 0x6D, 0x6E, 0x73, 0x76, 0xFF, 0xE9, 0xD9, 0x3F, 0xC7, 0x44, + 0xFF, 0xED, 0xFF, 0xF4, 0xC1, 0x6A, 0x43, 0x65, 0x72, 0x73, 0xCB, 0xA0, 0xC1, 0x54, 0xC0, 0xBC, + 0x41, 0x74, 0xC2, 0x9D, 0x21, 0x73, 0xFC, 0xC1, 0x03, 0x01, 0x73, 0xC0, 0xB8, 0xC1, 0x00, 0xA1, + 0x72, 0xC1, 0x49, 0x21, 0x74, 0xFA, 0x41, 0x72, 0xC6, 0xCB, 0x49, 0x64, 0x67, 0x69, 0x6B, 0x6E, + 0x70, 0x72, 0x73, 0x76, 0xFF, 0xDC, 0xC9, 0x16, 0xC6, 0xBD, 0xC3, 0x95, 0xFF, 0xEA, 0xC7, 0x0D, + 0xFF, 0xED, 0xFF, 0xF9, 0xFF, 0xFC, 0x43, 0xA5, 0xA4, 0xB6, 0xFF, 0x86, 0xFF, 0xAD, 0xFF, 0xE4, + 0x58, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x79, 0xC3, 0xFB, 0x41, 0xC8, 0xF0, 0xCA, 0xE5, 0xFB, + 0x86, 0xFC, 0x09, 0xFC, 0x46, 0xFC, 0x4F, 0xFC, 0x5F, 0xFC, 0xB7, 0xFC, 0xDC, 0xFD, 0x11, 0xFD, + 0x36, 0xFD, 0x4D, 0xFD, 0x67, 0xFD, 0xBF, 0xFD, 0xE2, 0xFE, 0x09, 0xFE, 0xB0, 0xFE, 0xEC, 0xFF, + 0x38, 0xFF, 0x68, 0xC6, 0x97, 0xFF, 0x6D, 0xFF, 0xF6, 0xA0, 0x0A, 0xC2, 0x45, 0x2E, 0x65, 0x6A, + 0x72, 0x73, 0xC5, 0xEE, 0xC5, 0xEE, 0xC6, 0x9B, 0xFF, 0xFD, 0xBF, 0xEC, 0x42, 0x72, 0x74, 0xC2, + 0x0D, 0xCA, 0x89, 0x41, 0x6E, 0xC3, 0xCF, 0x21, 0x61, 0xFC, 0xC3, 0x04, 0xC2, 0x61, 0x6D, 0x6F, + 0xC2, 0xF1, 0xFF, 0xFD, 0xCE, 0xDC, 0xC1, 0x03, 0x61, 0xC3, 0xC8, 0xF7, 0x41, 0x6C, 0xCE, 0xFC, + 0x44, 0x64, 0x67, 0x6C, 0x73, 0xC2, 0xEF, 0xC6, 0x67, 0xFF, 0xF6, 0xFF, 0xFC, 0xC1, 0x03, 0x61, + 0x2E, 0xC5, 0x3D, 0x46, 0x2E, 0x62, 0x6D, 0x6E, 0x74, 0x76, 0xCF, 0x71, 0xC8, 0x5D, 0xFF, 0xFA, + 0xC5, 0xA7, 0xC8, 0x5D, 0xC6, 0x54, 0x42, 0x2E, 0x75, 0xCB, 0x7A, 0xC0, 0x2E, 0xC1, 0x03, 0x61, + 0x6B, 0xC5, 0xEA, 0xCE, 0x02, 0xF1, 0x2E, 0x64, 0x66, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, + 0x73, 0x74, 0x75, 0x76, 0xC5, 0x87, 0xFF, 0x99, 0xC8, 0x3D, 0xBF, 0x97, 0xFF, 0xA9, 0xD0, 0x36, + 0xFF, 0xB7, 0xFF, 0xCD, 0xC8, 0x3D, 0xFF, 0xE0, 0xD6, 0x24, 0xFF, 0xF3, 0xFF, 0xFA, 0xCA, 0x32, + 0xC1, 0x02, 0xF1, 0x75, 0xC8, 0x10, 0x43, 0x6E, 0x72, 0x74, 0xC5, 0x24, 0xC5, 0x24, 0xC5, 0x24, + 0x4A, 0x2E, 0x61, 0x62, 0x65, 0x69, 0x6B, 0x6D, 0x70, 0x79, 0xC3, 0xC8, 0x00, 0xFF, 0xF0, 0xC5, + 0xF7, 0xFF, 0xF6, 0xC5, 0x1A, 0xC5, 0xF7, 0xC5, 0xF7, 0xC5, 0xF7, 0xC5, 0x4A, 0xCB, 0xC4, 0xC2, + 0x02, 0x01, 0x68, 0x69, 0xFF, 0xE1, 0xC1, 0x14, 0xC3, 0x00, 0xD1, 0x67, 0x6C, 0x72, 0xCA, 0xDD, + 0xC5, 0x22, 0xCE, 0x60, 0x43, 0x6C, 0x72, 0x74, 0xD2, 0x8C, 0xBF, 0x1A, 0xD4, 0x73, 0x42, 0x2E, + 0x79, 0xC5, 0x0C, 0xBF, 0x29, 0x41, 0xC3, 0xC7, 0x89, 0x21, 0x73, 0xFC, 0x41, 0x70, 0xC7, 0xB4, + 0xC9, 0x02, 0xF1, 0x61, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x72, 0x74, 0x78, 0xD1, 0xBD, 0xFF, 0xD8, + 0xFF, 0xE4, 0xFF, 0xEE, 0xBF, 0xE2, 0xD2, 0x00, 0xFF, 0xF9, 0xD2, 0x00, 0xFF, 0xFC, 0x41, 0x72, + 0xDC, 0xA2, 0x42, 0xA4, 0xB6, 0xFF, 0xFC, 0xC0, 0xC9, 0xA1, 0x03, 0xD2, 0xC3, 0xF9, 0x41, 0x77, + 0xD7, 0x95, 0x41, 0x73, 0xBF, 0x33, 0xC3, 0x03, 0x61, 0x61, 0x69, 0xC3, 0xFF, 0xF8, 0xFF, 0xFC, + 0xCB, 0x20, 0xC3, 0x04, 0x71, 0x72, 0x73, 0x74, 0xD3, 0xC5, 0xCE, 0x55, 0xE5, 0x31, 0xC1, 0x03, + 0x01, 0x74, 0xBF, 0x8B, 0x41, 0x72, 0xC2, 0x9E, 0x42, 0x65, 0x72, 0xFF, 0xFC, 0xBE, 0xDA, 0x42, + 0x6C, 0x76, 0xE3, 0x6E, 0xFF, 0xF9, 0xA0, 0x0A, 0xE2, 0x21, 0x73, 0xFD, 0x42, 0x65, 0x69, 0xFF, + 0xFD, 0xC4, 0x8E, 0xC1, 0x0B, 0x02, 0x74, 0xC4, 0xEE, 0x42, 0x2E, 0x72, 0xCA, 0x67, 0xC7, 0x37, + 0x46, 0x64, 0x66, 0x6E, 0x72, 0x73, 0x74, 0xC9, 0x25, 0xC9, 0x25, 0xFF, 0xEC, 0xC5, 0x27, 0xFF, + 0xF3, 0xFF, 0xF9, 0xC9, 0x02, 0xF1, 0x64, 0x65, 0x67, 0x6B, 0x6C, 0x6E, 0x6F, 0x73, 0x75, 0xC6, + 0xE7, 0xFF, 0xAF, 0xD7, 0x0C, 0xFF, 0xBB, 0xFF, 0xCC, 0xFF, 0xED, 0xC4, 0x67, 0xCA, 0xDA, 0xBE, + 0x92, 0xC1, 0x03, 0x61, 0x74, 0xC4, 0xA6, 0x21, 0x6B, 0xFA, 0xC2, 0x00, 0x91, 0x6B, 0x72, 0xCA, + 0x26, 0xC4, 0xED, 0x41, 0x6C, 0xC2, 0x32, 0x42, 0xA4, 0xB6, 0xFF, 0xFC, 0xC4, 0x33, 0xC5, 0x04, + 0x12, 0x61, 0x6E, 0x74, 0x75, 0xC3, 0xFF, 0xE9, 0xC4, 0xD9, 0xC4, 0xD9, 0xFF, 0xEC, 0xFF, 0xF9, + 0x42, 0x61, 0x6F, 0xD6, 0xBF, 0xC4, 0x44, 0x42, 0x66, 0x74, 0xDC, 0x24, 0xC3, 0xE3, 0xC1, 0x02, + 0x01, 0x2E, 0xC4, 0x0C, 0xC4, 0x02, 0xF1, 0x2E, 0x65, 0x69, 0x73, 0xC9, 0xEC, 0xD8, 0x80, 0xC4, + 0xB3, 0xBE, 0xF1, 0xC2, 0x03, 0x31, 0x61, 0x76, 0xC4, 0x35, 0xC9, 0xDD, 0x4A, 0x2E, 0x64, 0x66, + 0x67, 0x6C, 0x70, 0x72, 0x73, 0x74, 0x76, 0xC6, 0xA4, 0xFF, 0xD4, 0xFF, 0xDB, 0xC4, 0x2C, 0xFF, + 0xE2, 0xFF, 0xE8, 0xFF, 0xF7, 0xC4, 0x9B, 0xC4, 0x18, 0xC4, 0x2C, 0x44, 0x2E, 0x64, 0x6E, 0x70, + 0xC4, 0x7C, 0xD0, 0xD5, 0xEC, 0x1F, 0xCD, 0xE8, 0xC4, 0x00, 0x41, 0x66, 0x6E, 0x73, 0x76, 0xC3, + 0xC2, 0xC9, 0xA8, 0xCB, 0xDB, 0xC9, 0xA8, 0x42, 0x6F, 0x75, 0xE6, 0xA6, 0xC3, 0xB3, 0x42, 0x70, + 0x73, 0xBD, 0xC9, 0xC4, 0x09, 0xA1, 0x03, 0x31, 0x61, 0xF9, 0xC1, 0x04, 0x82, 0x73, 0xE2, 0xF9, + 0xC2, 0x02, 0xF1, 0x61, 0x6F, 0xC3, 0x9A, 0xC3, 0xD8, 0x41, 0x74, 0xC0, 0x87, 0x42, 0x70, 0x73, + 0xC3, 0xCB, 0xFF, 0xFC, 0x45, 0x2E, 0x67, 0x6C, 0x72, 0x74, 0xCD, 0x50, 0xFF, 0xE6, 0xFF, 0xEC, + 0xFF, 0xF9, 0xC3, 0x56, 0x42, 0x6E, 0x74, 0xD3, 0x8A, 0xC3, 0x76, 0x42, 0x74, 0x76, 0xBE, 0x09, + 0xC3, 0x6F, 0xC6, 0x02, 0xF1, 0x66, 0x67, 0x6E, 0x70, 0x73, 0x76, 0xC3, 0x68, 0xC3, 0xC5, 0xC9, + 0x4E, 0xC3, 0x68, 0xC2, 0xF8, 0xC3, 0x68, 0xC2, 0x00, 0x91, 0x62, 0x76, 0xC9, 0x39, 0xC3, 0x53, + 0x41, 0xA4, 0xCA, 0x8B, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFF, 0xD0, 0xFF, 0xD7, 0xFF, + 0xDE, 0xC3, 0x13, 0xFF, 0xF3, 0xFF, 0xFC, 0x42, 0x6C, 0x70, 0xCD, 0x4C, 0xC3, 0x90, 0x41, 0x6B, + 0xC5, 0xE2, 0xC2, 0x04, 0x12, 0x6E, 0x72, 0xC3, 0x66, 0xFF, 0xFC, 0xC3, 0x02, 0xF1, 0x6C, 0x70, + 0x72, 0xC3, 0x5D, 0xE1, 0xF2, 0xC3, 0xCC, 0x41, 0x6B, 0xC8, 0xF9, 0x42, 0x6E, 0x72, 0xFF, 0xFC, + 0xF6, 0xB0, 0x22, 0xA5, 0xA4, 0xE9, 0xF9, 0x50, 0x2E, 0x61, 0x65, 0x68, 0x69, 0x6A, 0x6C, 0x6E, + 0x6F, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xC3, 0xB0, 0xFF, 0x15, 0xFF, 0x34, 0xC3, 0xB0, + 0xFF, 0x41, 0xFF, 0x50, 0xFF, 0x5E, 0xC3, 0xB0, 0xFF, 0x7D, 0xFF, 0xBD, 0xBD, 0x13, 0xC5, 0xB9, + 0xFF, 0xD0, 0xD9, 0xCE, 0xFF, 0xDB, 0xFF, 0xFB, 0x43, 0x64, 0x67, 0x74, 0xC3, 0x2F, 0xD5, 0x77, + 0xC3, 0x10, 0x41, 0x2E, 0xC3, 0x25, 0x46, 0x2E, 0x64, 0x6E, 0x72, 0x73, 0x74, 0xC3, 0x71, 0xD2, + 0xD8, 0xFF, 0xF2, 0xFF, 0xFC, 0xC3, 0x71, 0xC3, 0x2B, 0x41, 0x76, 0xFE, 0xA5, 0x44, 0x63, 0x6E, + 0x73, 0x74, 0xC2, 0xEB, 0xBD, 0x8C, 0xC3, 0x5A, 0xC2, 0xD7, 0x42, 0x72, 0x74, 0xC2, 0x30, 0xC0, + 0x9E, 0xC1, 0x03, 0x31, 0x74, 0xC2, 0xF6, 0xC1, 0x03, 0x01, 0x6F, 0xBC, 0xBE, 0xC4, 0x02, 0x01, + 0x63, 0x66, 0x73, 0x74, 0xC2, 0xEA, 0xC2, 0xEA, 0xFF, 0xF4, 0xFF, 0xFA, 0x42, 0x2E, 0x72, 0xC2, + 0x7E, 0xF0, 0x5A, 0x46, 0x6B, 0x6D, 0x6E, 0x70, 0x72, 0x74, 0xC2, 0xB5, 0xC2, 0x07, 0xC2, 0x44, + 0xCC, 0x41, 0xC7, 0x25, 0xC2, 0xA1, 0x41, 0x6A, 0xD5, 0x09, 0x23, 0xA5, 0xA4, 0xB6, 0xE2, 0xE9, + 0xFC, 0xC6, 0x0B, 0x22, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFF, 0x95, 0xFF, 0xA8, 0xFF, 0xAC, + 0xFF, 0xB9, 0xFF, 0xCC, 0xFF, 0xF9, 0xC2, 0x02, 0x01, 0x2E, 0x74, 0xC2, 0x44, 0xC2, 0xA1, 0x42, + 0x6B, 0x6C, 0xFF, 0xF7, 0xC2, 0x65, 0x41, 0x2E, 0xC2, 0x5E, 0x21, 0x74, 0xFC, 0xC1, 0x02, 0x01, + 0x74, 0xDF, 0x4A, 0x42, 0x64, 0x74, 0xC2, 0x51, 0xFF, 0xFA, 0x43, 0x67, 0x6B, 0x73, 0xBC, 0xF0, + 0xBC, 0x58, 0xBC, 0x58, 0x43, 0x64, 0x6C, 0x73, 0xC2, 0x16, 0xC2, 0x16, 0xC2, 0xC3, 0x43, 0xA5, + 0xA4, 0xB6, 0xFF, 0xEC, 0xFF, 0xF6, 0xC9, 0x33, 0xC7, 0x03, 0x61, 0x61, 0x65, 0x69, 0x6A, 0x75, + 0x79, 0xC3, 0xFF, 0xC7, 0xFF, 0xD2, 0xFF, 0xDB, 0xC2, 0x5F, 0xFA, 0xF3, 0xFA, 0xF3, 0xFF, 0xF6, + 0x41, 0x74, 0xC2, 0x47, 0x45, 0x62, 0x63, 0x6D, 0x72, 0x73, 0xC1, 0xE6, 0xCB, 0xB0, 0xC2, 0x43, + 0xDC, 0x24, 0xFF, 0xFC, 0xC1, 0x03, 0x01, 0x74, 0xC1, 0xA6, 0x44, 0x62, 0x6C, 0x74, 0x76, 0xC1, + 0xD0, 0xC1, 0xD0, 0xFF, 0xFA, 0xC2, 0x2D, 0xC1, 0x00, 0x91, 0x72, 0xC7, 0xA9, 0x42, 0x63, 0x72, + 0xC7, 0xA3, 0xC1, 0xFB, 0x44, 0x2E, 0x67, 0x72, 0x73, 0xC1, 0xB6, 0xBC, 0x86, 0xC1, 0xB6, 0xBB, + 0xEE, 0x23, 0xA5, 0xA4, 0xB6, 0xE6, 0xEC, 0xF3, 0xC5, 0x0B, 0x43, 0x61, 0x65, 0x69, 0x75, 0xC3, + 0xFF, 0xBC, 0xF8, 0x6E, 0xFF, 0xD2, 0xE0, 0x98, 0xFF, 0xF9, 0xC1, 0x03, 0x01, 0x6B, 0xC7, 0x76, + 0x42, 0x6A, 0x73, 0xC4, 0x40, 0xBD, 0x7E, 0xC1, 0x03, 0x61, 0x6D, 0xC7, 0x69, 0xC1, 0x03, 0x01, + 0x73, 0xC7, 0xF0, 0x42, 0x72, 0x75, 0xD0, 0x0C, 0xBC, 0x11, 0x44, 0x2E, 0x64, 0x67, 0x6E, 0xC1, + 0x70, 0xFB, 0xC3, 0xC4, 0x26, 0xC1, 0x70, 0xC1, 0x03, 0x01, 0x74, 0xC2, 0x10, 0xC9, 0x02, 0xF1, + 0x63, 0x64, 0x69, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x74, 0xFF, 0xCD, 0xC4, 0x13, 0xC7, 0x43, 0xFF, + 0xD3, 0xFF, 0xDA, 0xFF, 0xE0, 0xFF, 0xE6, 0xFF, 0xED, 0xFF, 0xFA, 0x42, 0x6B, 0x76, 0xC1, 0x7D, + 0xC7, 0x25, 0xC1, 0x03, 0x31, 0x73, 0xC1, 0x95, 0x44, 0x6E, 0x72, 0x73, 0x74, 0xE3, 0x0D, 0xFF, + 0xF3, 0xFF, 0xFA, 0xD0, 0x8F, 0xC1, 0x05, 0x62, 0x73, 0xEB, 0x86, 0xC6, 0x02, 0xF1, 0x64, 0x6B, + 0x6C, 0x6E, 0x72, 0x74, 0xC1, 0xCC, 0xCA, 0xE9, 0xFF, 0xFA, 0xC3, 0xD5, 0xC3, 0xD5, 0xD5, 0x85, + 0x42, 0x6C, 0x72, 0xE2, 0x1E, 0xC1, 0x0A, 0xC2, 0x01, 0xD1, 0x6E, 0x74, 0xC1, 0x60, 0xED, 0x42, + 0xC2, 0x00, 0xC1, 0x6E, 0x74, 0xC1, 0x38, 0xC6, 0xE0, 0xC3, 0x03, 0x31, 0x61, 0x69, 0xC3, 0xFF, + 0xEE, 0xFF, 0xF7, 0xC3, 0x75, 0x42, 0x73, 0x74, 0xC5, 0x93, 0xC0, 0xE5, 0x42, 0xA5, 0xA4, 0xC0, + 0xDE, 0xDA, 0x49, 0x45, 0x65, 0x69, 0x6F, 0x79, 0xC3, 0xFF, 0x9F, 0xFF, 0xF2, 0xC3, 0x8D, 0xC1, + 0x34, 0xFF, 0xF9, 0xC2, 0x04, 0x82, 0x6E, 0x72, 0xC0, 0xC7, 0xC0, 0xC7, 0x43, 0xA5, 0xA4, 0xB6, + 0xFF, 0xF7, 0xDA, 0x29, 0xC0, 0xBE, 0xC7, 0x05, 0x42, 0x61, 0x65, 0x69, 0x6C, 0x72, 0x73, 0xC3, + 0xFF, 0x82, 0xFF, 0x95, 0xFF, 0xAA, 0xFF, 0xC3, 0xFF, 0xDD, 0xC1, 0x11, 0xFF, 0xF6, 0xC2, 0x00, + 0xD1, 0x6C, 0x75, 0xBA, 0xB9, 0xCC, 0xF8, 0x41, 0x67, 0xE4, 0xC0, 0x42, 0xA4, 0xB6, 0xBD, 0xB0, + 0xBD, 0xB0, 0xA2, 0x02, 0x81, 0x61, 0xC3, 0xF5, 0xF9, 0x41, 0x6E, 0xCC, 0xDD, 0xC1, 0x02, 0x01, + 0x69, 0xBA, 0x8D, 0xC9, 0x04, 0xA2, 0x61, 0x65, 0x69, 0x6B, 0x6C, 0x6E, 0x73, 0x75, 0x76, 0xC0, + 0x73, 0xCF, 0x01, 0xCC, 0x5C, 0xFF, 0xDB, 0xFF, 0xEF, 0xBD, 0xB6, 0xBA, 0xA2, 0xFF, 0xF6, 0xFF, + 0xFA, 0x41, 0x69, 0xD4, 0xBF, 0x42, 0x61, 0x69, 0xC0, 0x55, 0xC3, 0x0B, 0x22, 0x67, 0x6C, 0xF5, + 0xF9, 0x42, 0x2E, 0x6D, 0xC6, 0x2F, 0xC6, 0x2F, 0x41, 0x74, 0xC0, 0x12, 0x41, 0x64, 0xC6, 0x24, + 0x21, 0x61, 0xFC, 0x42, 0x6B, 0x74, 0xDF, 0x0A, 0xFF, 0xFD, 0x42, 0x65, 0x6C, 0xC0, 0x00, 0xC0, + 0x30, 0x4A, 0x2E, 0x63, 0x64, 0x66, 0x6C, 0x6D, 0x6E, 0x72, 0x74, 0x75, 0xC0, 0xD6, 0xC6, 0x0F, + 0xE1, 0x0A, 0xC0, 0x67, 0xFF, 0xDB, 0xFF, 0xE0, 0xFF, 0xE7, 0xFF, 0xF2, 0xFF, 0xF9, 0xBF, 0xF9, + 0x43, 0x2E, 0x6E, 0x74, 0xC0, 0x48, 0xC2, 0xC0, 0xC5, 0xF0, 0x42, 0x2E, 0x65, 0xC0, 0x00, 0xC0, + 0x00, 0x42, 0x65, 0x6D, 0xBF, 0xF9, 0xC5, 0xDF, 0x43, 0x61, 0x65, 0x73, 0xE2, 0xE5, 0xC9, 0xBC, + 0xFF, 0xF9, 0x42, 0x6E, 0x75, 0xC0, 0x73, 0xC0, 0x26, 0x43, 0x61, 0x69, 0x6E, 0xBF, 0xB1, 0xFF, + 0xF9, 0xCC, 0x12, 0x49, 0x2E, 0x61, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x72, 0x74, 0xC5, 0x95, 0xC0, + 0x84, 0xDE, 0xAA, 0xFF, 0xCD, 0xE0, 0x5B, 0xFF, 0xD7, 0xFF, 0xE5, 0xFF, 0xF6, 0xFF, 0x7E, 0xC1, + 0x03, 0x61, 0x2E, 0xBF, 0xBB, 0x41, 0x63, 0xBF, 0xB5, 0x49, 0x61, 0x62, 0x63, 0x64, 0x6B, 0x6C, + 0x6E, 0x73, 0x74, 0xC2, 0x67, 0xC2, 0x67, 0xD9, 0x2C, 0xC2, 0x67, 0xC2, 0x88, 0xFF, 0xF6, 0xD9, + 0x2C, 0xFF, 0xFC, 0xBF, 0x81, 0x42, 0x6C, 0x72, 0xBF, 0x95, 0xBF, 0x95, 0x21, 0xA4, 0xF9, 0xC2, + 0x03, 0x61, 0x75, 0xC3, 0xBF, 0x1B, 0xFF, 0xFD, 0xC1, 0x02, 0xF1, 0x6B, 0xC0, 0x2F, 0x41, 0x75, + 0xBB, 0x9F, 0x42, 0x65, 0x73, 0xFF, 0xFC, 0xC5, 0x5E, 0x42, 0x69, 0x73, 0xFF, 0xF9, 0xB9, 0xA9, + 0x44, 0x63, 0x6C, 0x6D, 0x72, 0xC5, 0x50, 0xFF, 0xE8, 0xC0, 0x17, 0xFF, 0xF9, 0x42, 0x69, 0x6F, + 0xC5, 0x1E, 0xBF, 0x5D, 0x42, 0x66, 0x74, 0xC6, 0x93, 0xFF, 0xF9, 0x44, 0x63, 0x6A, 0x6E, 0x72, + 0xBF, 0x4F, 0xBF, 0x4F, 0xB9, 0x5F, 0xBF, 0x1F, 0xC1, 0x02, 0xF1, 0x65, 0xC5, 0x28, 0x46, 0x61, + 0x64, 0x65, 0x66, 0x6B, 0x73, 0xC1, 0xF2, 0xFF, 0xFA, 0xEB, 0x19, 0xB9, 0xD6, 0xFE, 0xFA, 0xC0, + 0xD0, 0x41, 0x6D, 0xC1, 0xDF, 0xC2, 0x02, 0xF1, 0x6B, 0x6D, 0xBF, 0x25, 0xFF, 0xFC, 0x42, 0x63, + 0x6B, 0xBF, 0x79, 0xC5, 0x02, 0x42, 0x6B, 0x6C, 0xC4, 0xFB, 0xBF, 0x15, 0x41, 0x67, 0xBF, 0x0E, + 0x44, 0x63, 0x64, 0x6E, 0x76, 0xBF, 0x0A, 0xBF, 0xB7, 0xFF, 0xFC, 0xC4, 0xF0, 0x43, 0xA5, 0xA4, + 0xB6, 0xFF, 0xE8, 0xFF, 0xF3, 0xC3, 0x8B, 0x26, 0x61, 0x65, 0x69, 0x75, 0x79, 0xC3, 0x9D, 0xA4, + 0xB7, 0xCE, 0xD7, 0xF6, 0xA0, 0x0B, 0x73, 0xC1, 0x03, 0x31, 0x69, 0xEB, 0x22, 0xC1, 0x02, 0xF1, + 0x70, 0xC1, 0x93, 0xC2, 0x02, 0xF1, 0x67, 0x6C, 0xBF, 0x84, 0xC4, 0xBD, 0xC1, 0x02, 0xF1, 0x6C, + 0xBE, 0xCE, 0x43, 0xA5, 0xA4, 0xB6, 0xFF, 0xF1, 0xFF, 0xFA, 0xBE, 0x98, 0xCE, 0x02, 0x01, 0x2E, + 0x61, 0x63, 0x65, 0x69, 0x6A, 0x6D, 0x6F, 0x72, 0x73, 0x74, 0x76, 0x79, 0xC3, 0xC1, 0x74, 0xFE, + 0x95, 0xB8, 0xCE, 0xFE, 0xE7, 0xFF, 0x0D, 0xFF, 0x33, 0xC1, 0x74, 0xFF, 0x54, 0xFF, 0xCB, 0xFF, + 0xD8, 0xB8, 0xCE, 0xFF, 0xDB, 0xFF, 0xE1, 0xFF, 0xF6, 0x41, 0x65, 0xF8, 0x2D, 0xC4, 0x03, 0x61, + 0x2E, 0x64, 0x65, 0x67, 0xC4, 0x73, 0xFF, 0xFC, 0xC4, 0x73, 0xBE, 0x1D, 0xC2, 0x03, 0x61, 0x61, + 0x75, 0xC4, 0x64, 0xBB, 0x9F, 0xC2, 0x03, 0xD2, 0x6F, 0x72, 0xB9, 0x0F, 0xB9, 0x0F, 0xC8, 0x02, + 0xF1, 0x62, 0x67, 0x69, 0x6D, 0x6E, 0x70, 0x73, 0x74, 0xB9, 0x06, 0xBE, 0x6C, 0xB8, 0x7C, 0xBE, + 0x6C, 0xFF, 0xDF, 0xFF, 0xEE, 0xC4, 0x52, 0xFF, 0xF7, 0x42, 0x2E, 0x6E, 0xC4, 0x37, 0xBE, 0xAE, + 0x42, 0x73, 0x74, 0xB9, 0x26, 0xBE, 0x4A, 0x47, 0x67, 0x6B, 0x6D, 0x70, 0x72, 0x73, 0x74, 0xFF, + 0xF2, 0xC2, 0xF1, 0xC4, 0x29, 0xBE, 0xF0, 0xFF, 0xF9, 0xBE, 0xF0, 0xBE, 0xA0, 0x45, 0x63, 0x6E, + 0x70, 0x72, 0x73, 0xBE, 0xDA, 0xBE, 0x2D, 0xC4, 0x13, 0xC2, 0xDB, 0xBD, 0xBD, 0x44, 0x6C, 0x6E, + 0x73, 0x74, 0xC2, 0xCB, 0xE6, 0x6D, 0xBE, 0xCA, 0xC7, 0xCB, 0x42, 0x64, 0x72, 0xBD, 0xA0, 0xDE, + 0xF1, 0x41, 0x6D, 0xDC, 0xDC, 0x44, 0x6E, 0x72, 0x73, 0x74, 0xFE, 0xF7, 0xFF, 0xFC, 0xBE, 0x62, + 0xBE, 0x62, 0x22, 0xA5, 0xA4, 0xE8, 0xF3, 0xA4, 0x05, 0x42, 0x61, 0x65, 0x69, 0xC3, 0xB0, 0xC6, + 0xD6, 0xFB, 0xC1, 0x03, 0x01, 0x6B, 0xB7, 0xEC, 0xC1, 0x00, 0xA1, 0x65, 0xCD, 0x3F, 0xA1, 0x03, + 0x01, 0x74, 0xFA, 0xC1, 0x01, 0x01, 0x68, 0xB7, 0xF4, 0x46, 0x6B, 0x6C, 0x6E, 0x72, 0x73, 0x74, + 0xBE, 0x7E, 0xC3, 0xB7, 0xFF, 0xE9, 0xD2, 0xD9, 0xFF, 0xF5, 0xFF, 0xFA, 0x41, 0x64, 0xDE, 0xBB, + 0xC4, 0x02, 0xF1, 0x67, 0x6B, 0x6C, 0x6E, 0xC3, 0xA0, 0xBE, 0x67, 0xFF, 0xFC, 0xBC, 0xAE, 0x41, + 0x67, 0xC3, 0x69, 0xC1, 0x03, 0x01, 0x61, 0xBE, 0x54, 0xC6, 0x02, 0xF1, 0x64, 0x67, 0x6C, 0x70, + 0x73, 0x74, 0xBD, 0xDF, 0xFF, 0xF6, 0xBD, 0xDF, 0xC0, 0x57, 0xC3, 0x87, 0xFF, 0xFA, 0xC1, 0x03, + 0x61, 0x61, 0xBD, 0x1C, 0x42, 0x73, 0x74, 0xB9, 0x77, 0xC3, 0x41, 0xC6, 0x02, 0xF1, 0x64, 0x67, + 0x6B, 0x6C, 0x70, 0x72, 0xBE, 0x2C, 0xFF, 0xF3, 0xBE, 0xFA, 0xBE, 0x2C, 0xBE, 0x2C, 0xFF, 0xF9, + 0x23, 0xA5, 0xA4, 0xB6, 0xB0, 0xC9, 0xEB, 0x57, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xF7, + 0xDC, 0xBE, 0x10, 0xF8, 0x38, 0xC2, 0x11, 0xF8, 0x69, 0xF8, 0x92, 0xEF, 0xDA, 0xF8, 0x9F, 0xF8, + 0xFC, 0xF9, 0x37, 0xFA, 0x60, 0xFB, 0x0A, 0xFB, 0x61, 0xFB, 0xC1, 0xFC, 0x06, 0xFC, 0xAF, 0xC5, + 0x83, 0xFC, 0xEC, 0xFE, 0xA5, 0xFE, 0xF7, 0xFF, 0x70, 0xFF, 0x92, 0xFF, 0xF9, 0x41, 0x68, 0xBA, + 0x73, 0xA0, 0x0B, 0xA2, 0x43, 0x65, 0x69, 0x73, 0xCA, 0x1C, 0xBD, 0xC3, 0xFF, 0xFD, 0x41, 0x66, + 0xBF, 0xC2, 0x42, 0x69, 0x6F, 0xEF, 0xCD, 0xB7, 0x33, 0x43, 0x2E, 0x72, 0x74, 0xC2, 0xE7, 0xBE, + 0xCD, 0xFF, 0xF9, 0x41, 0x73, 0xD0, 0x5D, 0x42, 0x69, 0x73, 0xFF, 0xFC, 0xB7, 0xC3, 0x43, 0x66, + 0x6C, 0x76, 0xBD, 0x99, 0xFF, 0xF9, 0xBD, 0x99, 0x42, 0x65, 0x69, 0xBC, 0xE2, 0xBC, 0xE2, 0x41, + 0x65, 0xC9, 0xE1, 0x41, 0x61, 0xC2, 0x92, 0x45, 0x2E, 0x61, 0x64, 0x66, 0x6C, 0xBC, 0xD3, 0xC9, + 0xCE, 0xFF, 0xF8, 0xFF, 0xFC, 0xBD, 0x80, 0xA0, 0x0B, 0xC2, 0x41, 0x61, 0xE4, 0xB4, 0x22, 0x6C, + 0x70, 0xF9, 0xFC, 0x41, 0x76, 0xDA, 0xD8, 0xA1, 0x03, 0x31, 0x61, 0xFC, 0x47, 0x2E, 0x62, 0x6D, + 0x6E, 0x73, 0x74, 0x76, 0xBC, 0xAE, 0xBD, 0x5B, 0xCC, 0x87, 0xBC, 0xAE, 0xB6, 0xF3, 0xFF, 0xFB, + 0xBD, 0x5B, 0xC1, 0x03, 0x01, 0x72, 0xB9, 0xEE, 0x42, 0x6B, 0x74, 0xBD, 0x3F, 0xFF, 0xFA, 0xC3, + 0x01, 0x01, 0x61, 0x73, 0x74, 0xBE, 0x27, 0xB6, 0xC3, 0xBF, 0x41, 0x42, 0x2E, 0x6E, 0xC2, 0x65, + 0xBC, 0x7F, 0x42, 0x61, 0x6F, 0xFF, 0xF9, 0xBC, 0x78, 0xC3, 0x03, 0x61, 0x65, 0x6C, 0x73, 0xBD, + 0x1E, 0xFF, 0xF9, 0xB7, 0x59, 0xCF, 0x02, 0xF1, 0x2E, 0x63, 0x64, 0x66, 0x67, 0x6B, 0x6C, 0x6D, + 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0x78, 0xBC, 0x65, 0xFF, 0x48, 0xFF, 0x4F, 0xFF, 0x59, 0xD9, + 0x82, 0xFF, 0x64, 0xFF, 0x79, 0xFF, 0x83, 0xFF, 0x92, 0xFF, 0xA9, 0xFF, 0xB7, 0xFF, 0xD3, 0xFF, + 0xDA, 0xFF, 0xF4, 0xBC, 0x65, 0x42, 0x61, 0x65, 0xBC, 0x92, 0xBC, 0x92, 0xA1, 0x03, 0x61, 0x68, + 0xF9, 0xC1, 0x01, 0xC2, 0x65, 0xB6, 0x39, 0xA1, 0x03, 0x31, 0x72, 0xFA, 0xC1, 0x00, 0x71, 0x6E, + 0xC6, 0x9E, 0xC1, 0x03, 0x31, 0x73, 0xB7, 0x00, 0x42, 0x6B, 0x73, 0xC1, 0xF8, 0xC1, 0xF8, 0x21, + 0x69, 0xF9, 0x42, 0x6C, 0x6E, 0xB6, 0xA2, 0xFF, 0xFD, 0xA0, 0x0B, 0xE2, 0x21, 0xB6, 0xFD, 0x42, + 0x64, 0xC3, 0xBC, 0xA8, 0xFF, 0xFD, 0x43, 0x61, 0x6F, 0x75, 0xC1, 0xDA, 0xBC, 0xA1, 0xB6, 0x8E, + 0x42, 0xA4, 0xB6, 0xB6, 0x84, 0xBA, 0x87, 0x44, 0x67, 0x73, 0x74, 0xC3, 0xB6, 0x83, 0xEB, 0xDD, + 0xBD, 0xA1, 0xFF, 0xF9, 0x41, 0xC3, 0xCF, 0xDA, 0x48, 0x2E, 0x69, 0x6B, 0x6D, 0x6E, 0x73, 0x74, + 0x75, 0xC1, 0xB8, 0xFF, 0xFC, 0xB6, 0x72, 0xC1, 0xB8, 0xC1, 0xBB, 0xC1, 0xB8, 0xB5, 0xD6, 0xB6, + 0x6C, 0xC3, 0x04, 0x71, 0x65, 0x69, 0x75, 0xB5, 0xE4, 0xBD, 0x4D, 0xB5, 0xD6, 0x42, 0x2E, 0x74, + 0xC1, 0x93, 0xFF, 0xF4, 0x42, 0x69, 0x73, 0xF8, 0x93, 0xB5, 0xDE, 0x42, 0x61, 0x70, 0xBC, 0x4C, + 0xBE, 0x55, 0xCE, 0x04, 0xC2, 0x61, 0x62, 0x63, 0x67, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x72, + 0x73, 0x74, 0x78, 0xBB, 0x3C, 0xB6, 0xBE, 0xC1, 0x7E, 0xFF, 0x75, 0xFF, 0x7A, 0xFF, 0x80, 0xFF, + 0x90, 0xFF, 0x9D, 0xFF, 0xA4, 0xFF, 0xB5, 0xFF, 0xC6, 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xF9, 0xC1, + 0x03, 0x61, 0x65, 0xBE, 0x12, 0x41, 0xB6, 0xBC, 0x12, 0xC3, 0x03, 0x01, 0x65, 0x75, 0xC3, 0xC1, + 0x47, 0xB5, 0xFB, 0xFF, 0xFC, 0x45, 0x65, 0x68, 0x6F, 0x72, 0x74, 0xC1, 0x61, 0xBB, 0x55, 0xB5, + 0xEF, 0xB8, 0x76, 0xBB, 0x55, 0x43, 0x6C, 0x73, 0x75, 0xB5, 0x49, 0xBB, 0x45, 0xD9, 0x66, 0x43, + 0x73, 0x76, 0xC3, 0xDA, 0x31, 0xBB, 0x3B, 0xBB, 0x3E, 0xC2, 0x02, 0x01, 0x6A, 0x6C, 0xC1, 0x17, + 0xFF, 0xF6, 0x43, 0x64, 0x72, 0x74, 0xBD, 0xDE, 0xBD, 0xDE, 0xBD, 0xDE, 0x43, 0x64, 0x6E, 0x73, + 0xB5, 0xB8, 0xBB, 0x1E, 0xB8, 0x3F, 0xC2, 0x03, 0x01, 0x63, 0x6B, 0xBB, 0xC1, 0xC0, 0xFA, 0xC1, + 0x03, 0x01, 0x6E, 0xB5, 0xA5, 0x41, 0x74, 0xCE, 0x6B, 0x22, 0x61, 0x69, 0xF6, 0xFC, 0xCF, 0x02, + 0xF1, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x73, 0x76, 0xC3, + 0xDC, 0x10, 0xC0, 0xE2, 0xEA, 0xF6, 0xFF, 0x9B, 0xD2, 0xC6, 0xB7, 0x2F, 0xFF, 0xA7, 0xFF, 0xB7, + 0xFF, 0xCB, 0xCE, 0x62, 0xFF, 0xD4, 0xFF, 0xDE, 0xFF, 0xE8, 0xFF, 0xFB, 0xCE, 0xE0, 0x42, 0x62, + 0x75, 0xBB, 0x79, 0xBD, 0x82, 0x43, 0x6C, 0x6D, 0x6E, 0xBB, 0x72, 0xBB, 0x72, 0xBA, 0xC5, 0x21, + 0xA4, 0xF6, 0xC3, 0x02, 0x01, 0x65, 0x6F, 0xC3, 0xBB, 0x65, 0xFF, 0xEC, 0xFF, 0xFD, 0xC1, 0x07, + 0x93, 0x65, 0xB4, 0xDA, 0x42, 0x2E, 0x6A, 0xBB, 0x53, 0xBB, 0x53, 0x42, 0x72, 0x73, 0xBD, 0x55, + 0xBB, 0x4C, 0x41, 0x74, 0xCF, 0x12, 0x21, 0x65, 0xFC, 0xC1, 0x03, 0x31, 0x73, 0xB4, 0xC9, 0x41, + 0x74, 0xB4, 0xF6, 0x47, 0x61, 0x64, 0x69, 0x6D, 0x6E, 0x73, 0x74, 0xC1, 0xA6, 0xCA, 0x9B, 0xFF, + 0xF3, 0xFF, 0xF6, 0xBA, 0x87, 0xFF, 0xFC, 0xC2, 0xA0, 0x42, 0x6B, 0x74, 0xB4, 0x9C, 0xCA, 0x85, + 0x48, 0x64, 0x6B, 0x6C, 0x6D, 0x70, 0x72, 0x73, 0x76, 0xE4, 0xCB, 0xBA, 0x6A, 0xFF, 0xC4, 0xFF, + 0xCB, 0xBA, 0x94, 0xFF, 0xE3, 0xFF, 0xF9, 0xBA, 0xA8, 0xC1, 0x00, 0x91, 0x69, 0xBA, 0x51, 0x46, + 0x2E, 0x61, 0x65, 0x6F, 0x73, 0x74, 0xC4, 0x15, 0xBA, 0x89, 0xC4, 0x15, 0xC0, 0x31, 0xBA, 0x4B, + 0xBA, 0x89, 0xA0, 0x0C, 0x02, 0xA1, 0x03, 0x01, 0x61, 0xFD, 0x21, 0x73, 0xFB, 0x42, 0x2E, 0x6F, + 0xBA, 0x8A, 0xBA, 0x6B, 0xC6, 0x03, 0x61, 0x66, 0x6C, 0x6E, 0x72, 0x73, 0x74, 0xFF, 0xD5, 0xFF, + 0xDB, 0xFF, 0xF6, 0xBA, 0xD3, 0xF7, 0x5E, 0xFF, 0xF9, 0x42, 0x2E, 0x6E, 0xBA, 0xBE, 0xBA, 0xBE, + 0x41, 0x73, 0xF7, 0x42, 0x44, 0x67, 0x6E, 0x72, 0x74, 0xBA, 0xB3, 0xBA, 0xB3, 0xFF, 0xF5, 0xFF, + 0xFC, 0xC1, 0x01, 0xD1, 0x2E, 0xBA, 0x56, 0x21, 0x67, 0xFA, 0xC5, 0x03, 0x61, 0x62, 0x63, 0x64, + 0x65, 0x6E, 0xC1, 0x29, 0xF6, 0xDD, 0xB4, 0xCF, 0xC5, 0xD5, 0xFF, 0xFD, 0x41, 0x6B, 0xB9, 0x6E, + 0x42, 0x69, 0x6F, 0xFF, 0xFC, 0xBA, 0x37, 0x41, 0x69, 0xC1, 0xEC, 0x46, 0x63, 0x67, 0x6E, 0x70, + 0x73, 0x74, 0xF4, 0x42, 0xBA, 0x0D, 0xFF, 0xF5, 0xFF, 0xFC, 0xCE, 0x35, 0xD3, 0x3A, 0x41, 0x70, + 0xB9, 0xFA, 0xC1, 0x00, 0x91, 0x70, 0xB9, 0xB8, 0x43, 0x6D, 0x70, 0x73, 0xFF, 0xF6, 0xFF, 0xFA, + 0xCB, 0x42, 0xC1, 0x02, 0xF1, 0x6B, 0xD8, 0x7B, 0x41, 0x68, 0xBF, 0x88, 0x21, 0x67, 0xFC, 0x22, + 0x63, 0x67, 0xF3, 0xFD, 0x41, 0x6B, 0xBA, 0x43, 0xC1, 0x03, 0x01, 0x73, 0xB6, 0x23, 0xC1, 0x03, + 0x31, 0x6E, 0xB9, 0xE9, 0x46, 0x2E, 0x64, 0x66, 0x67, 0x6B, 0x6E, 0xBF, 0x6C, 0xFF, 0xF4, 0xB9, + 0x86, 0xB9, 0x86, 0xFF, 0xFA, 0xCA, 0xFB, 0x42, 0x6A, 0x74, 0xBF, 0x5C, 0xB9, 0xB1, 0x44, 0xA5, + 0xA4, 0xB6, 0xA9, 0xFF, 0xD6, 0xFF, 0xE6, 0xFF, 0xF9, 0xBF, 0x52, 0xC7, 0x06, 0x62, 0x61, 0x65, + 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0x39, 0xFF, 0x59, 0xFF, 0x6F, 0xFF, 0x90, 0xFF, 0xAD, 0xFF, + 0xC4, 0xFF, 0xF3, 0xA0, 0x03, 0x82, 0x21, 0x64, 0xFD, 0x43, 0x6E, 0x72, 0x74, 0xFF, 0xFD, 0xBA, + 0x5B, 0xCC, 0x38, 0xC3, 0x01, 0x91, 0x67, 0x6E, 0x78, 0xB9, 0x75, 0xB3, 0xD1, 0xB9, 0x75, 0x42, + 0x6C, 0x74, 0xC5, 0x10, 0xF7, 0x41, 0x41, 0x74, 0xE4, 0xB2, 0xC4, 0x00, 0xD1, 0x61, 0x69, 0x6C, + 0x6F, 0xFF, 0xF5, 0xB9, 0x87, 0xB3, 0x30, 0xFF, 0xFC, 0x41, 0xA4, 0xBA, 0xA5, 0x42, 0x6F, 0xC3, + 0xC2, 0xBB, 0xFF, 0xFC, 0x42, 0xA4, 0xB6, 0xB3, 0x16, 0xB9, 0x63, 0x21, 0xC3, 0xF9, 0xC1, 0x02, + 0x01, 0x72, 0xE3, 0xD7, 0x41, 0x66, 0xB3, 0x13, 0x21, 0x6C, 0xFC, 0x21, 0x6C, 0xFD, 0x42, 0x6C, + 0x6E, 0xB9, 0x49, 0xB9, 0x2A, 0x42, 0x61, 0x79, 0xB9, 0x0F, 0xB9, 0x23, 0xC7, 0x01, 0xD1, 0x65, + 0x69, 0x6A, 0x6F, 0x72, 0x75, 0x79, 0xDD, 0xAB, 0xFF, 0xEF, 0xB2, 0xEE, 0xFF, 0xF2, 0xFF, 0xF9, + 0xB9, 0x1F, 0xEF, 0xAA, 0xC1, 0x02, 0x01, 0x64, 0xB9, 0x23, 0xCB, 0x03, 0x61, 0x61, 0x65, 0x6B, + 0x6C, 0x6E, 0x6F, 0x70, 0x73, 0x74, 0x75, 0x79, 0xFF, 0x7F, 0xFF, 0x89, 0xFF, 0xA0, 0xFF, 0xB3, + 0xFF, 0xC1, 0xFF, 0xC4, 0xB8, 0x98, 0xB2, 0xEB, 0xFF, 0xE2, 0xFF, 0xFA, 0xB8, 0x50, 0x42, 0x63, + 0x75, 0xB8, 0xF9, 0xB8, 0xDA, 0x46, 0x64, 0x67, 0x6D, 0x6E, 0x73, 0x78, 0xB8, 0xD3, 0xDF, 0x3C, + 0xB8, 0xD3, 0xB3, 0x7D, 0xB2, 0xDA, 0xB8, 0xD3, 0x42, 0x6E, 0x70, 0xC0, 0xE0, 0xB8, 0xC0, 0x43, + 0x64, 0x6E, 0x74, 0xB2, 0x8B, 0xE3, 0x56, 0xB2, 0x8B, 0x44, 0x61, 0x65, 0x69, 0x79, 0xFF, 0xF6, + 0xB2, 0x81, 0xB2, 0x81, 0xB3, 0x0B, 0x43, 0x65, 0x69, 0x74, 0xB2, 0xFE, 0xB5, 0x85, 0xB8, 0xE8, + 0xC1, 0x02, 0x81, 0xC3, 0xC6, 0x22, 0x42, 0xA4, 0xB6, 0xB8, 0xB1, 0xC1, 0xCC, 0xCB, 0x03, 0x61, + 0x61, 0x65, 0x69, 0x6A, 0x6F, 0x72, 0x73, 0x75, 0x76, 0x79, 0xC3, 0xFF, 0xB1, 0xFF, 0xB8, 0xFF, + 0xCB, 0xB5, 0x8C, 0xB8, 0xB4, 0xFF, 0xDC, 0xFF, 0xE9, 0xDD, 0x20, 0xFF, 0xF3, 0xB8, 0xAA, 0xFF, + 0xF9, 0x41, 0x6E, 0xC1, 0xB6, 0x42, 0x61, 0x73, 0xB8, 0x25, 0xB2, 0x5D, 0x43, 0x64, 0x67, 0x6E, + 0xBA, 0xD4, 0xFF, 0xF9, 0xBE, 0x04, 0x43, 0x62, 0x76, 0xC3, 0xBA, 0xCA, 0xB8, 0x71, 0xBA, 0x2A, + 0x47, 0x67, 0x6E, 0x70, 0x72, 0x74, 0x76, 0xC3, 0xFF, 0xE1, 0xFF, 0xEC, 0xF3, 0x0C, 0xB9, 0xD6, + 0xFF, 0xF6, 0xB7, 0x72, 0xCC, 0x0E, 0x44, 0x67, 0x6E, 0x74, 0x76, 0xB8, 0x51, 0xF8, 0xE6, 0xB8, + 0x51, 0xB7, 0xF4, 0x42, 0x67, 0x6E, 0xB8, 0x44, 0xB7, 0xE7, 0x43, 0x6E, 0x72, 0x74, 0xB8, 0x3D, + 0xB2, 0xBF, 0xD6, 0xD6, 0x22, 0xA5, 0xA4, 0xEF, 0xF6, 0xC4, 0x03, 0x61, 0x61, 0x65, 0x69, 0xC3, + 0xB8, 0x38, 0xB8, 0x7E, 0xFF, 0xDD, 0xFF, 0xFB, 0x41, 0x64, 0xB7, 0xC2, 0x21, 0x67, 0xFC, 0xC1, + 0x03, 0x01, 0x69, 0xB1, 0xCB, 0x45, 0x64, 0x67, 0x6E, 0x70, 0x73, 0xC6, 0x4A, 0xD6, 0x88, 0xFF, + 0xF7, 0xFF, 0xFA, 0xBD, 0x9B, 0x43, 0x67, 0x73, 0x74, 0xB7, 0xA5, 0xB1, 0xEA, 0xB8, 0x52, 0x41, + 0x6B, 0xDF, 0x1E, 0x41, 0x6D, 0xB8, 0x44, 0x45, 0x63, 0x67, 0x6D, 0x72, 0x76, 0xFF, 0xF8, 0xC1, + 0x20, 0xB8, 0x40, 0xFF, 0xFC, 0xD8, 0xA7, 0xC1, 0x03, 0x32, 0x65, 0xB1, 0x81, 0xC1, 0x03, 0x31, + 0x69, 0xB1, 0xA8, 0x42, 0x65, 0x73, 0xBE, 0x9E, 0xB9, 0x47, 0x41, 0x61, 0xB9, 0x1C, 0x45, 0x64, + 0x67, 0x70, 0x72, 0x76, 0xFF, 0xE9, 0xB8, 0x19, 0xFF, 0xEF, 0xFF, 0xF5, 0xFF, 0xFC, 0x44, 0xA5, + 0xA4, 0xB6, 0xA9, 0xFF, 0xB7, 0xFF, 0xC9, 0xFF, 0xF0, 0xBD, 0x42, 0x58, 0x61, 0x62, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x79, 0x7A, 0xC3, 0xFA, 0xEA, 0xCA, 0x4E, 0xFB, 0x21, 0xE9, 0x50, 0xFB, 0xB7, 0xDE, 0xCF, + 0xE9, 0x50, 0xFB, 0xE4, 0xFC, 0x53, 0xFC, 0x97, 0xC0, 0xD9, 0xBD, 0x0A, 0xBB, 0xFA, 0xFC, 0xA3, + 0xFC, 0xE5, 0xBB, 0xFA, 0xFD, 0xF0, 0xFE, 0x8F, 0xFF, 0x02, 0xFF, 0x45, 0xFF, 0x7E, 0xFF, 0x9A, + 0xBA, 0x05, 0xFF, 0xF3, 0x41, 0x6C, 0xB3, 0x90, 0x41, 0x63, 0xB1, 0x12, 0x21, 0x69, 0xFC, 0xC4, + 0x02, 0x01, 0x61, 0x62, 0x6C, 0x6F, 0xB1, 0x23, 0xFF, 0xF5, 0xFF, 0xFD, 0xB7, 0x39, 0x42, 0x68, + 0x6C, 0xEA, 0xB7, 0xB6, 0x7C, 0xC1, 0x00, 0x71, 0x65, 0xC1, 0x65, 0xC6, 0x02, 0x01, 0x61, 0x65, + 0x72, 0x73, 0x75, 0x79, 0xDB, 0x3A, 0xB6, 0x6F, 0xFF, 0xFA, 0xB7, 0x00, 0xB7, 0x1D, 0xB7, 0x1D, + 0xC1, 0x01, 0xD1, 0x73, 0xB6, 0xF4, 0x42, 0x61, 0x6F, 0xB8, 0x60, 0xB1, 0x5E, 0x42, 0x66, 0x74, + 0xCD, 0x51, 0xFF, 0xF9, 0xC1, 0x00, 0xA1, 0x73, 0xB1, 0x86, 0x45, 0x61, 0x65, 0x67, 0x6E, 0x73, + 0xBE, 0xD0, 0xB7, 0x17, 0xB7, 0xE7, 0xFF, 0xFA, 0xB1, 0xD6, 0x41, 0x65, 0xF4, 0xB6, 0x44, 0x65, + 0x6E, 0x73, 0x74, 0xB6, 0x2C, 0xB7, 0x03, 0xB6, 0xF9, 0xFF, 0xFC, 0x41, 0x62, 0xB6, 0xB9, 0xC1, + 0x00, 0x91, 0x61, 0xB0, 0xA8, 0xC1, 0x01, 0xD1, 0x73, 0xB0, 0xCA, 0xC1, 0x00, 0xD1, 0x6B, 0xB4, + 0x6C, 0x41, 0x69, 0xB9, 0xF2, 0x43, 0x72, 0x73, 0xC3, 0xFF, 0xFC, 0xB0, 0x73, 0xB1, 0xAB, 0x49, + 0x65, 0x69, 0x6C, 0x6E, 0x6F, 0x73, 0x74, 0x75, 0x76, 0xFF, 0xDC, 0xB5, 0xFB, 0xFF, 0xE0, 0xB0, + 0x7B, 0xFF, 0xE6, 0xFF, 0xEC, 0xFF, 0xF6, 0xB6, 0xC8, 0xB0, 0x7B, 0x44, 0x69, 0x73, 0x75, 0xC3, + 0xB0, 0xE9, 0xB4, 0x5F, 0xB0, 0xE9, 0xC6, 0xFB, 0x41, 0xA4, 0xB9, 0x21, 0x42, 0x73, 0xC3, 0xD8, + 0xCD, 0xFF, 0xFC, 0x48, 0x61, 0x64, 0x6C, 0x6F, 0x73, 0x74, 0x75, 0xC3, 0xB6, 0x9E, 0xFF, 0xE8, + 0xFF, 0xF9, 0xB6, 0x9E, 0xB3, 0xBD, 0xBF, 0x43, 0xB6, 0x94, 0xE6, 0x76, 0x42, 0x6F, 0x74, 0xB0, + 0xB8, 0xB0, 0x49, 0x41, 0xB6, 0xBF, 0x8F, 0x44, 0x66, 0x73, 0x75, 0xC3, 0xCC, 0xA7, 0xFF, 0xF5, + 0xB6, 0x7A, 0xFF, 0xFC, 0x41, 0xB6, 0xB5, 0xD6, 0x21, 0xC3, 0xFC, 0x43, 0x6C, 0x73, 0x76, 0xBB, + 0xE5, 0xFF, 0xFD, 0xB5, 0xCF, 0x21, 0x72, 0xF6, 0x44, 0x65, 0x6F, 0x72, 0xC3, 0xFF, 0xFD, 0xCF, + 0x52, 0xAF, 0xF6, 0xC3, 0xBA, 0x41, 0x73, 0xB7, 0x68, 0x42, 0x66, 0x72, 0xCE, 0x42, 0xFF, 0xFC, + 0x44, 0x65, 0x69, 0x72, 0x73, 0xFF, 0xF9, 0xB1, 0x0C, 0xAF, 0xDE, 0xBD, 0x3D, 0x43, 0x6C, 0x6E, + 0x72, 0xAF, 0xD1, 0xAF, 0xD1, 0xB0, 0x67, 0x41, 0x6B, 0xB3, 0x19, 0xC1, 0x00, 0x41, 0x61, 0xDA, + 0xFA, 0x21, 0x72, 0xFA, 0x47, 0x64, 0x67, 0x69, 0x6B, 0x73, 0x74, 0x75, 0xFF, 0xC4, 0xFF, 0xDC, + 0xBC, 0xF3, 0xFF, 0xE9, 0xFF, 0xF3, 0xFF, 0xFD, 0xB5, 0x46, 0xC1, 0x01, 0xD1, 0x72, 0xB8, 0x47, + 0xC2, 0x02, 0x01, 0x65, 0x69, 0xF3, 0xB0, 0xB7, 0x1D, 0x42, 0x61, 0xC3, 0xB5, 0x61, 0xD2, 0x5E, + 0xC1, 0x00, 0x61, 0x70, 0xAF, 0x88, 0x46, 0x66, 0x67, 0x69, 0x6C, 0x72, 0x73, 0xD2, 0xA1, 0xB5, + 0x54, 0xB7, 0x20, 0xFF, 0xF3, 0xCF, 0xF9, 0xFF, 0xFA, 0x45, 0x65, 0x69, 0x6C, 0x70, 0x75, 0xFF, + 0xD1, 0xB5, 0xD8, 0xFF, 0xD7, 0xFF, 0xED, 0xB6, 0x8B, 0x41, 0x76, 0xAF, 0x7E, 0x42, 0x6B, 0x72, + 0xAF, 0x7A, 0xFF, 0xFC, 0xC2, 0x01, 0xD1, 0x62, 0x6D, 0xB5, 0x94, 0xB5, 0x94, 0x42, 0x69, 0xC3, + 0xCC, 0x92, 0xB0, 0x83, 0x21, 0x74, 0xF9, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x73, 0x79, 0xFF, 0xE6, + 0xB5, 0xA0, 0xB5, 0xAA, 0xFF, 0xED, 0xFF, 0xFD, 0xB4, 0xD3, 0x42, 0x6B, 0x6E, 0xB5, 0x5A, 0xAF, + 0x58, 0x41, 0x68, 0xE8, 0xF4, 0xC1, 0x01, 0x91, 0x6E, 0xAF, 0x5D, 0xC5, 0x05, 0x82, 0x61, 0x6C, + 0x72, 0x79, 0xC3, 0xAF, 0x2F, 0xB5, 0xA3, 0xAF, 0xB9, 0xB4, 0xAF, 0xC2, 0xE7, 0x41, 0xC3, 0xC2, + 0xB1, 0xC1, 0x02, 0x01, 0x65, 0xC1, 0x65, 0x43, 0x67, 0x6B, 0x6E, 0xAF, 0x13, 0xB5, 0x60, 0xAF, + 0x20, 0x42, 0xA5, 0xA4, 0xB5, 0x37, 0xB5, 0x37, 0x44, 0x61, 0x69, 0x72, 0xC3, 0xB6, 0x0C, 0xFF, + 0xEF, 0xB2, 0xDF, 0xFF, 0xF9, 0x43, 0xA5, 0xA4, 0xB6, 0xB5, 0x23, 0xB5, 0x23, 0xB5, 0x0F, 0x4A, + 0x61, 0x63, 0x65, 0x6B, 0x6C, 0x6E, 0x70, 0x74, 0x76, 0xC3, 0xFF, 0xAB, 0xFF, 0xB2, 0xFF, 0xB6, + 0xFF, 0xBC, 0xFF, 0xCE, 0xAE, 0xEB, 0xFF, 0xD2, 0xFF, 0xE9, 0xAE, 0xEB, 0xFF, 0xF6, 0x42, 0x66, + 0x73, 0xB4, 0xFA, 0xAE, 0xC0, 0x43, 0x6B, 0x6C, 0xC3, 0xB0, 0xA6, 0xB4, 0x45, 0xC2, 0x48, 0x41, + 0xA4, 0xB9, 0x39, 0xA1, 0x00, 0x71, 0xC3, 0xFC, 0x41, 0x64, 0xCA, 0xEC, 0x21, 0x6E, 0xFC, 0x21, + 0x61, 0xFD, 0xC2, 0x01, 0xD1, 0x70, 0x73, 0xB4, 0xF5, 0xAE, 0x96, 0xC1, 0x00, 0x41, 0x70, 0xAE, + 0xAC, 0xC3, 0x00, 0x71, 0x65, 0x6F, 0x75, 0xB4, 0x9C, 0xFF, 0xFA, 0xB4, 0x59, 0x41, 0x64, 0xB7, + 0x33, 0xC1, 0x00, 0x71, 0xC3, 0xB4, 0x7C, 0x22, 0x69, 0x6C, 0xF6, 0xFA, 0xC1, 0x03, 0x61, 0x6A, + 0xAE, 0x99, 0xC1, 0x02, 0x81, 0x65, 0xF4, 0xB3, 0x41, 0xB6, 0xE5, 0x3E, 0x4D, 0x61, 0x62, 0x65, + 0x69, 0x6A, 0x6C, 0x6F, 0x72, 0x73, 0x74, 0x76, 0x79, 0xC3, 0xD8, 0xB9, 0xB4, 0x2E, 0xFF, 0xA2, + 0xFF, 0xA9, 0xFF, 0xB7, 0xFF, 0xC3, 0xFF, 0xC6, 0xFF, 0xD5, 0xFF, 0xEB, 0xFF, 0xF0, 0xFF, 0xF6, + 0xB3, 0xEE, 0xFF, 0xFC, 0xC3, 0x03, 0xD2, 0x61, 0x6A, 0xC3, 0xD7, 0x8E, 0xB4, 0x74, 0xD8, 0xAC, + 0x42, 0xA5, 0xB6, 0xB3, 0xBA, 0xB4, 0x87, 0x55, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, + 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xB4, 0x8A, 0xFD, + 0x28, 0xFD, 0x37, 0xFD, 0x44, 0xFD, 0x59, 0xFD, 0x66, 0xFD, 0x73, 0xFD, 0x87, 0xB4, 0x80, 0xFD, + 0xB8, 0xFD, 0xEC, 0xFE, 0x10, 0xFE, 0x6D, 0xB4, 0x8A, 0xFE, 0xB2, 0xFE, 0xE0, 0xFF, 0x48, 0xFF, + 0xC5, 0xB3, 0xB3, 0xFF, 0xED, 0xFF, 0xF9, 0x41, 0x69, 0xB8, 0x8E, 0x21, 0x74, 0xFC, 0x44, 0x6B, + 0x6C, 0x73, 0x76, 0xAD, 0xE0, 0xC3, 0xB5, 0xBA, 0x4F, 0xB9, 0xC2, 0x44, 0x64, 0x70, 0x73, 0x74, + 0xBB, 0xE8, 0xB4, 0x7C, 0xBA, 0x42, 0xC3, 0x2C, 0x47, 0x62, 0x65, 0x69, 0x6B, 0x73, 0x74, 0x75, + 0xB6, 0x78, 0xB3, 0x66, 0xC0, 0x81, 0xB4, 0x6F, 0xAE, 0xAA, 0xB0, 0x21, 0xB1, 0x18, 0x41, 0x74, + 0xCD, 0x17, 0xC1, 0x03, 0x31, 0x69, 0xB9, 0x8E, 0x4C, 0x64, 0x67, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, + 0x72, 0x73, 0x74, 0x75, 0x76, 0xC3, 0xC1, 0xD4, 0xAB, 0xFF, 0xC3, 0xFF, 0xC6, 0xB9, 0x8B, 0xFF, + 0xD3, 0xB9, 0x88, 0xFF, 0xE0, 0xB1, 0x28, 0xFF, 0xF6, 0xB4, 0x4F, 0xFF, 0xFA, 0xC1, 0x00, 0xD1, + 0x65, 0xAD, 0x8D, 0x41, 0x64, 0xB4, 0x24, 0x41, 0xA4, 0xAE, 0x4F, 0x21, 0xC3, 0xFC, 0x21, 0x6B, + 0xFD, 0x44, 0x73, 0x74, 0x79, 0xC3, 0xFF, 0xFD, 0xD2, 0x7C, 0xAD, 0x79, 0xBE, 0x66, 0x42, 0x67, + 0x6B, 0xAD, 0x5A, 0xB3, 0x5C, 0xC2, 0x00, 0xA1, 0x70, 0x74, 0xAD, 0xA6, 0xB0, 0xAB, 0x44, 0x61, + 0x65, 0x73, 0x74, 0xB3, 0x4C, 0xB3, 0x4C, 0xAF, 0x29, 0xAE, 0x1F, 0xC8, 0x02, 0xF1, 0x63, 0x6B, + 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0xB9, 0x25, 0xFF, 0xC2, 0xFF, 0xC8, 0xFF, 0xD6, 0xB3, 0xEC, + 0xFF, 0xE3, 0xFF, 0xEA, 0xFF, 0xF3, 0x42, 0x64, 0x74, 0xB0, 0x59, 0xAD, 0x4F, 0x41, 0x73, 0xCF, + 0x06, 0x42, 0x2E, 0x65, 0xB8, 0xFF, 0xB8, 0xFF, 0x21, 0x74, 0xF9, 0x46, 0x64, 0x67, 0x6B, 0x6F, + 0x73, 0x79, 0xC0, 0x15, 0xFF, 0xF2, 0xD4, 0x23, 0xB0, 0x30, 0xFF, 0xFD, 0xAD, 0x2C, 0xC1, 0x00, + 0x41, 0x61, 0xAD, 0x00, 0x45, 0x2E, 0x61, 0x68, 0x6B, 0x74, 0xB2, 0xF6, 0xAD, 0x13, 0xAC, 0xF4, + 0xB0, 0xE3, 0xFF, 0xFA, 0x41, 0x61, 0xB2, 0xF9, 0xC7, 0x00, 0x91, 0x63, 0x64, 0x6C, 0x6E, 0x73, + 0x74, 0x76, 0xAD, 0x7C, 0xB4, 0x19, 0xFF, 0xBE, 0xFF, 0xD3, 0xFF, 0xEC, 0xBB, 0xEE, 0xFF, 0xFC, + 0x41, 0x6F, 0xB3, 0x77, 0x41, 0x6B, 0xAF, 0x57, 0x21, 0x72, 0xFC, 0x21, 0xB6, 0xFD, 0xA1, 0x03, + 0xD2, 0xC3, 0xFD, 0x42, 0x64, 0x67, 0xB3, 0x64, 0xB5, 0x6D, 0xC4, 0x02, 0xF1, 0x6B, 0x6D, 0x72, + 0x73, 0xBA, 0xC9, 0xB5, 0x66, 0xFF, 0xF9, 0xAC, 0xDB, 0x41, 0x72, 0xCF, 0xBE, 0xC3, 0x03, 0x62, + 0x61, 0x65, 0x75, 0xEF, 0x8A, 0xFF, 0xFC, 0xB2, 0xFA, 0xC1, 0x08, 0xF2, 0x72, 0xEB, 0x2B, 0x41, + 0x63, 0xB2, 0x1B, 0xC3, 0x01, 0xD1, 0x65, 0x72, 0x79, 0xAD, 0x21, 0xBD, 0x1F, 0xFF, 0xFC, 0x41, + 0xC3, 0xB3, 0x83, 0xC8, 0x03, 0x61, 0x63, 0x6B, 0x6D, 0x6E, 0x6F, 0x74, 0x76, 0xC3, 0xB2, 0xB5, + 0xFF, 0xE6, 0xC1, 0x84, 0xCF, 0x4D, 0xB2, 0xA1, 0xFF, 0xF0, 0xFF, 0xFC, 0xBD, 0x33, 0xA0, 0x0C, + 0x22, 0x41, 0x64, 0xB2, 0xC0, 0x44, 0x64, 0x6E, 0x70, 0x74, 0xFF, 0xF9, 0xFF, 0xFC, 0xBB, 0xE2, + 0xB3, 0x02, 0x41, 0x67, 0xB0, 0x43, 0x43, 0x64, 0x69, 0x75, 0xB2, 0x44, 0xB2, 0xF1, 0xAC, 0x48, + 0x43, 0x6C, 0x6E, 0x72, 0xF1, 0xFC, 0xFF, 0xF2, 0xFF, 0xF6, 0xC1, 0x03, 0x01, 0x67, 0xAD, 0xC6, + 0xA0, 0x0C, 0x42, 0x21, 0x73, 0xFD, 0xA1, 0x00, 0xA1, 0x6C, 0xFD, 0x45, 0x64, 0x69, 0x6A, 0x6B, + 0x6C, 0xBE, 0x50, 0xDA, 0xBB, 0xB2, 0xCC, 0xB8, 0x05, 0xFA, 0x5D, 0x46, 0x67, 0x6C, 0x6E, 0x72, + 0x74, 0x78, 0xFF, 0xDF, 0xFF, 0xEB, 0xD3, 0x23, 0xFF, 0xF0, 0xB4, 0xC5, 0xB2, 0x0F, 0x43, 0x67, + 0x70, 0x72, 0xB2, 0xA9, 0xB2, 0xA9, 0xB1, 0xFC, 0x23, 0xA5, 0xA4, 0xB6, 0xB8, 0xE3, 0xF6, 0x57, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x79, 0xC3, 0xFE, 0x49, 0xC4, 0xEA, 0xB6, 0x96, 0xE3, 0xEC, 0xFE, + 0xAC, 0xB6, 0x96, 0xB6, 0x96, 0xB4, 0xA1, 0xFF, 0x09, 0xFF, 0x21, 0xBB, 0x75, 0xB6, 0x96, 0xFF, + 0x2F, 0xD9, 0x6B, 0xFF, 0x3B, 0xB6, 0x96, 0xFF, 0x4E, 0xFF, 0x74, 0xB6, 0x96, 0xFF, 0x96, 0xB6, + 0x96, 0xB1, 0xEB, 0xFF, 0xF9, 0xC1, 0x02, 0x01, 0x69, 0xAC, 0x8D, 0x41, 0x65, 0xAE, 0xC0, 0x41, + 0x61, 0xB1, 0xD9, 0x45, 0x61, 0x65, 0x68, 0x69, 0x6E, 0xB1, 0x67, 0xAE, 0xB8, 0xFF, 0xF2, 0xFF, + 0xF8, 0xFF, 0xFC, 0x41, 0x64, 0xD8, 0x2E, 0x21, 0x6E, 0xFC, 0x41, 0x70, 0xEB, 0xC2, 0x21, 0x6D, + 0xFC, 0x42, 0x69, 0xC3, 0xAC, 0x61, 0xBC, 0x02, 0xC7, 0x01, 0xD1, 0x61, 0x65, 0x69, 0x6B, 0x6C, + 0x73, 0x74, 0xFF, 0xEF, 0xFF, 0xF6, 0xB3, 0x1E, 0xAE, 0x93, 0xC1, 0x4B, 0xAC, 0x0C, 0xFF, 0xF9, + 0x41, 0x69, 0xB9, 0xB8, 0x42, 0x72, 0x73, 0xFF, 0xFC, 0xAB, 0xF0, 0xC1, 0x00, 0x91, 0x6E, 0xB0, + 0xDF, 0xC6, 0x02, 0x01, 0x61, 0x6F, 0x72, 0x73, 0x75, 0xC3, 0xFF, 0xFA, 0xB1, 0x87, 0xAB, 0x59, + 0xAB, 0x74, 0xB1, 0x87, 0xD6, 0x88, 0x42, 0x72, 0xC3, 0xAB, 0x38, 0xBE, 0xFC, 0x41, 0x70, 0xAB, + 0x58, 0x44, 0x61, 0x65, 0x67, 0x73, 0xB1, 0x90, 0xB1, 0x90, 0xFF, 0xF5, 0xFF, 0xFC, 0x41, 0x73, + 0xBA, 0xCA, 0x42, 0x69, 0x6F, 0xFF, 0xFC, 0xAB, 0x92, 0x44, 0x69, 0x6C, 0x6E, 0x6F, 0xB1, 0x78, + 0xFF, 0xF9, 0xAB, 0x21, 0xB1, 0x78, 0x41, 0x6A, 0xAB, 0x9E, 0x47, 0x61, 0x67, 0x69, 0x6B, 0x6C, + 0x6F, 0x74, 0xB1, 0x67, 0xFF, 0xFC, 0xB1, 0x5D, 0xAB, 0x1D, 0xFB, 0xF3, 0xB1, 0x67, 0xB1, 0x13, + 0x42, 0x6C, 0x6F, 0xAE, 0x0B, 0xB0, 0xFD, 0x42, 0x66, 0xC3, 0xFF, 0xF9, 0xBC, 0xE2, 0x42, 0x61, + 0x74, 0xAB, 0x76, 0xAB, 0x07, 0x43, 0x67, 0x6B, 0x73, 0xB2, 0x93, 0xAA, 0xD6, 0xFF, 0xF9, 0x42, + 0x72, 0x75, 0xB1, 0x28, 0xB0, 0x5B, 0x43, 0x65, 0x6F, 0x72, 0xB1, 0x2B, 0xB0, 0x54, 0xB1, 0x53, + 0x42, 0x75, 0x76, 0xAB, 0x54, 0xAA, 0xB8, 0x42, 0x6B, 0x74, 0xAA, 0xDE, 0xAA, 0xD0, 0xA0, 0x0C, + 0x62, 0x21, 0xA5, 0xFD, 0x47, 0x65, 0x69, 0x6B, 0x6F, 0x73, 0x74, 0xC3, 0xB2, 0x52, 0xB1, 0x0D, + 0xFF, 0xEC, 0xB1, 0x0D, 0xFF, 0xF3, 0xC6, 0x3A, 0xFF, 0xFD, 0x41, 0x6F, 0xB0, 0x20, 0x21, 0x68, + 0xFC, 0xC1, 0x00, 0x71, 0x6B, 0xBD, 0x48, 0x43, 0x61, 0x69, 0x72, 0xAA, 0x93, 0xFF, 0xFA, 0xB9, + 0xC1, 0x43, 0x63, 0x73, 0x74, 0xFF, 0xED, 0xD0, 0xF2, 0xFF, 0xF6, 0x42, 0x2E, 0x61, 0xB0, 0xAD, + 0xB0, 0xAD, 0x45, 0x61, 0x65, 0x6F, 0x74, 0x76, 0xD3, 0xC0, 0xFF, 0xF9, 0xB0, 0xCF, 0xB2, 0x26, + 0xAA, 0x85, 0x43, 0x61, 0x69, 0xC3, 0xB0, 0xB5, 0xB0, 0xB5, 0xB3, 0x8B, 0xD2, 0x03, 0x61, 0x61, + 0x62, 0x64, 0x65, 0x67, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0x77, + 0xC3, 0xB0, 0xB5, 0xFE, 0xF8, 0xFF, 0x05, 0xB0, 0xB5, 0xFF, 0x25, 0xB0, 0xB5, 0xFF, 0x3D, 0xFF, + 0x4E, 0xFF, 0x6B, 0xFF, 0x79, 0xFF, 0x83, 0xFF, 0x8A, 0xFF, 0xA8, 0xFF, 0xD5, 0xFF, 0xE6, 0xFF, + 0xF6, 0xAF, 0xDE, 0xB5, 0x12, 0xC1, 0x02, 0xF1, 0x72, 0xB3, 0x23, 0x41, 0x61, 0xAA, 0xAC, 0xA1, + 0x02, 0xF1, 0x6E, 0xFC, 0x48, 0x61, 0x65, 0x69, 0x6F, 0x70, 0x73, 0x75, 0x7A, 0xAF, 0xD6, 0xFF, + 0xF1, 0xAF, 0xD6, 0xFF, 0xFB, 0xB0, 0xB3, 0xAF, 0x96, 0xB0, 0x06, 0xDC, 0xE4, 0x42, 0x6A, 0x73, + 0xA9, 0xFD, 0xAE, 0x4A, 0xC1, 0x02, 0x81, 0x6C, 0xAB, 0xD7, 0x42, 0x6B, 0x74, 0xAA, 0x80, 0xA9, + 0xE4, 0x45, 0x61, 0x65, 0x6C, 0x73, 0xC3, 0xAD, 0x2F, 0xFF, 0xF3, 0xA9, 0xDD, 0xFF, 0xF9, 0xCA, + 0x98, 0x41, 0x61, 0xE8, 0xBA, 0x41, 0x72, 0xAB, 0x8F, 0x21, 0x74, 0xFC, 0x43, 0x69, 0x6C, 0x73, + 0xAD, 0x14, 0xFF, 0xF5, 0xFF, 0xFD, 0x41, 0x65, 0xB1, 0x9F, 0x41, 0x73, 0xB7, 0x13, 0x42, 0x65, + 0x74, 0xC4, 0x0F, 0xA9, 0xB0, 0x47, 0x61, 0x64, 0x69, 0x6B, 0x6C, 0x73, 0xC3, 0xAC, 0xFB, 0xFF, + 0xF1, 0xC8, 0x5D, 0xA9, 0xA9, 0xFF, 0xF5, 0xFF, 0xF9, 0xC4, 0xF2, 0xC1, 0x02, 0x81, 0x61, 0xAD, + 0x55, 0x42, 0x72, 0x75, 0xA9, 0xCE, 0xA9, 0xB4, 0x42, 0x74, 0x76, 0xA9, 0xC7, 0xA9, 0xC7, 0x22, + 0x64, 0x73, 0xF2, 0xF9, 0xC1, 0x00, 0xD1, 0x61, 0xA9, 0x93, 0xC2, 0x02, 0x81, 0x6C, 0xC3, 0xFF, + 0xFA, 0xC0, 0x1C, 0xA0, 0x0C, 0x82, 0xA1, 0x01, 0xE1, 0x74, 0xFD, 0x43, 0x6F, 0x72, 0x73, 0xA9, + 0x8A, 0xCB, 0xB6, 0xFF, 0xFB, 0x41, 0x76, 0xB5, 0x3B, 0x41, 0x69, 0xC5, 0xAB, 0x43, 0x61, 0x6C, + 0x76, 0xFF, 0xF8, 0xFF, 0xFC, 0xAA, 0x35, 0x46, 0x64, 0x65, 0x69, 0x6F, 0x73, 0xC3, 0xFF, 0xE4, + 0xA9, 0xE0, 0xAC, 0x99, 0xAD, 0xE0, 0xFF, 0xF6, 0xB3, 0x65, 0xC1, 0x00, 0xC1, 0x6B, 0xA9, 0x5E, + 0x44, 0x6B, 0x6E, 0x73, 0x74, 0xAB, 0xB4, 0xA9, 0x2E, 0xFF, 0xFA, 0xA9, 0x55, 0x42, 0x69, 0xC3, + 0xA9, 0x21, 0xB0, 0x25, 0x41, 0x6A, 0xA9, 0xFE, 0x44, 0x65, 0x72, 0x73, 0x74, 0xAB, 0x03, 0xFF, + 0xF5, 0xFF, 0xFC, 0xA9, 0xE5, 0x50, 0x61, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6B, 0x6C, 0x6D, 0x6E, + 0x6F, 0x70, 0x72, 0x73, 0x74, 0x76, 0xAC, 0x5B, 0xFF, 0x18, 0xAC, 0x5B, 0xAC, 0x5B, 0xFF, 0x2C, + 0xA9, 0x15, 0xFF, 0x47, 0xFF, 0x60, 0xFF, 0x76, 0xFF, 0x8A, 0xA9, 0x15, 0xFF, 0x95, 0xFF, 0xC2, + 0xFF, 0xDB, 0xFF, 0xF3, 0xAC, 0x5B, 0xC1, 0x03, 0x61, 0x6B, 0xD2, 0x72, 0x42, 0x73, 0x75, 0xA9, + 0x06, 0xA8, 0xF9, 0x42, 0x65, 0x74, 0xA8, 0xC5, 0xA8, 0xCB, 0xC4, 0x00, 0xD1, 0x64, 0x65, 0x72, + 0x73, 0xFF, 0xF2, 0xA9, 0xC1, 0xA8, 0xC4, 0xFF, 0xF9, 0xC1, 0x00, 0x61, 0x65, 0xA8, 0xDC, 0x46, + 0x61, 0x65, 0x67, 0x6C, 0x72, 0x73, 0xAC, 0x01, 0xAC, 0x01, 0xB0, 0x26, 0xA9, 0x16, 0xFF, 0xFA, + 0xB9, 0x9E, 0xC3, 0x03, 0x31, 0x69, 0x6E, 0x72, 0xAB, 0xEE, 0xA8, 0x9C, 0xA8, 0x9C, 0x41, 0x61, + 0xFD, 0x5F, 0x41, 0x67, 0xA8, 0xB3, 0x41, 0x67, 0xA9, 0x54, 0x21, 0x6F, 0xFC, 0x43, 0x65, 0x6B, + 0x6C, 0xFF, 0xF5, 0xFF, 0xFD, 0xBA, 0x5E, 0xA0, 0x05, 0xC2, 0x42, 0x72, 0x75, 0xFF, 0xFD, 0xA9, + 0x58, 0x45, 0x61, 0x70, 0x73, 0x74, 0x76, 0xAB, 0xBF, 0xFF, 0xDD, 0xFF, 0xEC, 0xFF, 0xF9, 0xBC, + 0x2D, 0x41, 0x70, 0xA8, 0x94, 0x41, 0x68, 0xAF, 0x02, 0x21, 0x67, 0xFC, 0x21, 0x69, 0xFD, 0x43, + 0x6C, 0x72, 0x73, 0xFF, 0xFD, 0xA8, 0x83, 0xEA, 0xBA, 0x42, 0x69, 0x73, 0xA9, 0x20, 0xA8, 0x79, + 0xC1, 0x00, 0xA1, 0x65, 0xA9, 0x0A, 0x43, 0x6C, 0x74, 0x76, 0xAB, 0x2A, 0xFF, 0xFA, 0xA8, 0x5F, + 0x21, 0x72, 0xB7, 0x46, 0x64, 0x67, 0x6E, 0x6F, 0x73, 0x74, 0xFF, 0xDC, 0xAA, 0x86, 0xFF, 0xE6, + 0xA8, 0xC1, 0xFF, 0xF3, 0xFF, 0xFD, 0x44, 0x65, 0x70, 0x72, 0x73, 0xA8, 0x24, 0xA8, 0x4F, 0xA8, + 0xAE, 0xB4, 0x87, 0x41, 0x72, 0xAB, 0xBB, 0x42, 0x6C, 0x72, 0xA8, 0xD3, 0xA8, 0x2E, 0xC1, 0x02, + 0x81, 0x62, 0xAA, 0x1F, 0x42, 0xA5, 0xB6, 0xA8, 0xDE, 0xA8, 0x21, 0x43, 0x69, 0x73, 0xC3, 0xAF, + 0x9B, 0xB5, 0x12, 0xFF, 0xF9, 0x45, 0x62, 0x6C, 0x70, 0x72, 0x73, 0xA7, 0xE3, 0xA8, 0x02, 0xA7, + 0xF5, 0xA8, 0x02, 0xA8, 0x02, 0x45, 0x69, 0x6B, 0x6F, 0x70, 0x76, 0xA8, 0xBD, 0xAF, 0x50, 0xA8, + 0xBD, 0xA8, 0x00, 0xA8, 0xBD, 0x41, 0x6E, 0xC2, 0x8F, 0x43, 0x61, 0x72, 0x73, 0xFF, 0xFC, 0xA8, + 0xA9, 0xA7, 0xF9, 0x49, 0x61, 0x62, 0x67, 0x69, 0x6B, 0x6E, 0x6F, 0x73, 0x74, 0xC0, 0xD5, 0xFF, + 0xB0, 0xFF, 0xB4, 0xFF, 0xBB, 0xA9, 0x81, 0xFF, 0xC8, 0xFF, 0xD2, 0xFF, 0xE2, 0xFF, 0xF6, 0x42, + 0x61, 0x69, 0xA7, 0x9F, 0xA7, 0x99, 0x42, 0x6B, 0x70, 0xA7, 0xBF, 0xA7, 0xBF, 0x43, 0x61, 0x72, + 0xC3, 0xA7, 0x91, 0xA7, 0xC5, 0xA8, 0xC3, 0xC4, 0x03, 0x31, 0x70, 0x73, 0x74, 0xC3, 0xFF, 0xE8, + 0xFF, 0xEF, 0xFF, 0xF6, 0xBB, 0x4B, 0x41, 0x6B, 0xAF, 0xB6, 0x45, 0x65, 0x6F, 0x72, 0x75, 0x76, + 0xA7, 0x6E, 0xFC, 0x43, 0xA7, 0xA8, 0xA7, 0x9B, 0xA7, 0x9B, 0xC4, 0x03, 0x61, 0x6F, 0x72, 0x73, + 0x74, 0xA7, 0x70, 0xAF, 0x2C, 0xFF, 0xEC, 0xFF, 0xF0, 0xC1, 0x02, 0x81, 0x6B, 0xAA, 0x72, 0x43, + 0x61, 0x69, 0x75, 0xFF, 0xFA, 0xA7, 0x5B, 0xA7, 0x68, 0x51, 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, + 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0xAA, 0x97, 0xAA, 0x62, 0xFE, + 0x6D, 0xFE, 0x81, 0xA7, 0x51, 0xFE, 0x96, 0xA7, 0x51, 0xB7, 0xF0, 0xFE, 0xA9, 0xFE, 0xD8, 0xFE, + 0xE8, 0xFF, 0x1A, 0xFF, 0x2D, 0xFF, 0x8A, 0xFF, 0xBE, 0xFF, 0xE1, 0xFF, 0xF6, 0x41, 0x76, 0xA7, + 0xA7, 0x43, 0x6B, 0x6C, 0x72, 0xA7, 0xA3, 0xA7, 0xA3, 0xFF, 0xFC, 0xC1, 0x01, 0x01, 0x65, 0xA7, + 0x03, 0xC7, 0x00, 0xD1, 0x61, 0x65, 0x69, 0x72, 0x73, 0x75, 0xC3, 0xFF, 0xF0, 0xAB, 0xF4, 0xAC, + 0x9D, 0xB6, 0x37, 0xFF, 0xFA, 0xA7, 0x93, 0xBD, 0xA5, 0xC2, 0x02, 0x81, 0x6B, 0x72, 0xA6, 0xDF, + 0xA6, 0xFE, 0xC1, 0x02, 0x81, 0x72, 0xA6, 0xF5, 0xC1, 0x01, 0x01, 0x65, 0xA7, 0x0A, 0x42, 0x69, + 0x6B, 0xA6, 0xD0, 0xA6, 0xD0, 0x48, 0x61, 0x64, 0x65, 0x67, 0x6C, 0x6E, 0x6F, 0x73, 0xFF, 0xE4, + 0xAC, 0x95, 0xFF, 0xED, 0xA7, 0x0A, 0xA7, 0x30, 0xFF, 0xF3, 0xAC, 0x95, 0xFF, 0xF9, 0x41, 0x76, + 0xA6, 0xD7, 0x42, 0x6F, 0x73, 0xA6, 0xB8, 0xFF, 0xFC, 0x41, 0x72, 0xC6, 0x01, 0xC1, 0x01, 0x01, + 0x6C, 0xA6, 0xA1, 0x44, 0x61, 0x69, 0x6E, 0x73, 0xFF, 0xF6, 0xA9, 0xED, 0xA6, 0x9B, 0xFF, 0xFA, + 0x42, 0x76, 0xC3, 0xA6, 0xB5, 0xBB, 0xEB, 0x44, 0x61, 0x6B, 0x70, 0xC3, 0xA9, 0xD9, 0xFF, 0xF9, + 0xA7, 0x6B, 0xBA, 0x27, 0x41, 0x6C, 0xA7, 0x5E, 0x42, 0x61, 0x6B, 0xDD, 0x62, 0xFF, 0xFC, 0x41, + 0x6D, 0xA9, 0x8C, 0x41, 0x65, 0xA6, 0x9F, 0x43, 0x6B, 0x6F, 0x74, 0xFF, 0xFC, 0xA6, 0x8E, 0xA7, + 0x7A, 0x43, 0x61, 0x6F, 0x73, 0xA6, 0xF6, 0xFF, 0xEE, 0xFF, 0xF6, 0xC1, 0x00, 0x71, 0x6C, 0xA6, + 0xE9, 0x42, 0x69, 0x6F, 0xA6, 0x47, 0xA6, 0x66, 0x45, 0x65, 0x69, 0x6C, 0x70, 0x72, 0xFF, 0xF3, + 0xA6, 0x52, 0xFF, 0xF9, 0xCD, 0x4B, 0xA6, 0xDC, 0x41, 0x64, 0xD6, 0xA5, 0xC1, 0x00, 0x71, 0x6B, + 0xA6, 0xC8, 0x22, 0x6E, 0x73, 0xF6, 0xFA, 0x41, 0x72, 0xA6, 0xC3, 0x41, 0x69, 0xA6, 0x8A, 0xC1, + 0x00, 0x71, 0x74, 0xAC, 0x2E, 0x44, 0x6E, 0x73, 0x74, 0x76, 0xFF, 0xF6, 0xFF, 0xFA, 0xA6, 0x25, + 0xDC, 0x39, 0x42, 0x67, 0x74, 0xA6, 0x18, 0xA6, 0x0C, 0xC4, 0x02, 0x81, 0x67, 0x6E, 0x72, 0x76, + 0xA6, 0x1E, 0xFF, 0xF9, 0xA5, 0xFF, 0xA5, 0xFF, 0xC1, 0x0C, 0xA2, 0x69, 0xA5, 0xF0, 0x42, 0x61, + 0x6C, 0xD4, 0x88, 0xFF, 0xFA, 0x42, 0x6C, 0x72, 0xA5, 0xE9, 0xA6, 0x50, 0x41, 0x61, 0xC0, 0x41, + 0x21, 0x6C, 0xFC, 0xA0, 0x0C, 0xA2, 0xC1, 0x00, 0x61, 0xC3, 0xA7, 0x9A, 0x22, 0x6B, 0x6C, 0xF7, + 0xFA, 0x42, 0x65, 0x73, 0xA5, 0xC7, 0xA6, 0x99, 0x41, 0x64, 0xA7, 0xE5, 0x42, 0xA4, 0xB6, 0xA6, + 0x29, 0xFF, 0xFC, 0x50, 0x61, 0x62, 0x64, 0x65, 0x67, 0x69, 0x6B, 0x6E, 0x6F, 0x70, 0x73, 0x74, + 0x75, 0x76, 0x79, 0xC3, 0xFF, 0x8F, 0xA5, 0xFC, 0xFF, 0x94, 0xFF, 0xA2, 0xA5, 0xBB, 0xFF, 0xB6, + 0xFF, 0xCB, 0xCD, 0xDA, 0xFF, 0xD2, 0xFF, 0xDD, 0xFF, 0xE9, 0xFF, 0xEE, 0xA6, 0x22, 0xAD, 0x75, + 0xA5, 0xBB, 0xFF, 0xF9, 0x41, 0x6B, 0xA8, 0xA7, 0x43, 0x6A, 0x6B, 0x70, 0xA5, 0xAD, 0xA6, 0x6A, + 0xA5, 0xAD, 0x42, 0x61, 0x76, 0xA5, 0x7C, 0xA5, 0xB0, 0x44, 0x61, 0x6E, 0x73, 0x74, 0xFF, 0xEB, + 0xA5, 0x75, 0xFF, 0xEF, 0xFF, 0xF9, 0x41, 0x6F, 0xA6, 0x34, 0x42, 0x6B, 0x74, 0xFF, 0xFC, 0xA5, + 0x8B, 0x22, 0x61, 0x73, 0xD3, 0xF9, 0xC1, 0x01, 0x51, 0x65, 0xB1, 0x3A, 0xA1, 0x00, 0xC1, 0x72, + 0xFA, 0x42, 0x61, 0x69, 0xA5, 0x4D, 0xA5, 0x4D, 0xC4, 0x02, 0x81, 0x65, 0x6A, 0x72, 0x73, 0xFF, + 0xF4, 0xA8, 0x63, 0xFF, 0xF9, 0xA8, 0x73, 0x4F, 0x61, 0x64, 0x65, 0x67, 0x69, 0x6A, 0x6B, 0x6C, + 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0xA8, 0x89, 0xFE, 0x3A, 0xA8, 0x89, 0xFE, 0x6E, 0xA8, + 0x89, 0xFE, 0x8B, 0xFE, 0x9C, 0xFE, 0xB0, 0xFE, 0xC1, 0xFE, 0xDA, 0xFE, 0xF1, 0xFF, 0x7C, 0xFF, + 0xC2, 0xFF, 0xDA, 0xFF, 0xF1, 0x44, 0xA5, 0xA4, 0xB6, 0xA9, 0xFC, 0x00, 0xFD, 0xC4, 0xFF, 0xD2, + 0xD5, 0x78, 0x5C, 0x2E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, + 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xC3, 0xAA, + 0x1A, 0xAF, 0xC0, 0xB1, 0xD5, 0xB2, 0xDD, 0xB6, 0x57, 0xBB, 0x4C, 0xBD, 0x5E, 0xC0, 0xEB, 0xC2, + 0x64, 0xC6, 0x55, 0xC7, 0x81, 0xCC, 0x72, 0xD1, 0x52, 0xD4, 0x1B, 0xD8, 0x82, 0xDC, 0x5B, 0xDF, + 0x37, 0xDF, 0x80, 0xE4, 0xBE, 0xED, 0x95, 0xF3, 0xA9, 0xF6, 0xD5, 0xF9, 0x0D, 0xF9, 0x61, 0xF9, + 0x86, 0xFA, 0xAA, 0xFA, 0xF2, 0xFF, 0xF3, +}; + +constexpr SerializedHyphenationPatterns sv_patterns = { + 0x5BD2u, + sv_trie_data, + sizeof(sv_trie_data), +}; diff --git a/scripts/update_hyphenation.sh b/scripts/update_hyphenation.sh index 53706eacc4..1f8d643b15 100755 --- a/scripts/update_hyphenation.sh +++ b/scripts/update_hyphenation.sh @@ -24,3 +24,4 @@ process ru process it process uk process pl +process sv diff --git a/test/hyphenation_eval/HyphenationEvaluationTest.cpp b/test/hyphenation_eval/HyphenationEvaluationTest.cpp index 27b9fff385..0ac7eabc77 100644 --- a/test/hyphenation_eval/HyphenationEvaluationTest.cpp +++ b/test/hyphenation_eval/HyphenationEvaluationTest.cpp @@ -45,6 +45,7 @@ const std::vector kSupportedLanguages = { {"spanish", "test/hyphenation_eval/resources/spanish_hyphenation_tests.txt", "es"}, {"italian", "test/hyphenation_eval/resources/italian_hyphenation_tests.txt", "it"}, {"polish", "test/hyphenation_eval/resources/polish_hyphenation_tests.txt", "pl"}, + {"swedish", "test/hyphenation_eval/resources/swedish_hyphenation_tests.txt", "sv"}, }; std::vector expectedPositionsFromAnnotatedWord(const std::string& annotated) { @@ -388,4 +389,4 @@ int main(int argc, char* argv[]) { } return 0; -} \ No newline at end of file +} diff --git a/test/hyphenation_eval/resources/swedish_hyphenation_tests.txt b/test/hyphenation_eval/resources/swedish_hyphenation_tests.txt new file mode 100644 index 0000000000..fde671b132 --- /dev/null +++ b/test/hyphenation_eval/resources/swedish_hyphenation_tests.txt @@ -0,0 +1,5012 @@ +# Hyphenation Test Data +# Source: Uppdrag Hail Mary - Andy Weir.epub +# Language: sv_SE +# Min prefix: 2 +# Min suffix: 2 +# Total words: 5000 +# Format: word | hyphenated_form | frequency_in_source +# +# Hyphenation points are marked with '=' +# Example: Silbentrennung -> Sil=ben=tren=nung +# + +skulle|skul=le|629 +kommer|kom=mer|479 +mycket|myck=et|437 +Flinta|Flin=ta|392 +skeppet|skep=pet|263 +Stratt|Stratt|224 +astrofager|ast=ro=fa=ger|213 +tillbaka|till=ba=ka|206 +behöver|be=hö=ver|193 +jorden|jor=den|167 +håller|hål=ler|161 +kanske|kanske|160 +Varför|Var=för|157 +ungefär|un=ge=fär|139 +astrofagerna|ast=ro=fa=ger=na|135 +fortfarande|fort=fa=ran=de|118 +labbet|lab=bet|108 +aldrig|ald=rig|100 +mindre|mind=re|96 +skärmen|skär=men|96 +tunneln|tun=neln|95 +människor|män=ni=skor|94 +gjorde|gjor=de|93 +problem|pro=blem|91 +igenom|ige=nom|90 +ingenting|ing=en=ting|90 +gånger|gång=er|89 +enkelt|en=kelt|88 +första|förs=ta|88 +nästan|näs=tan|88 +precis|pre=cis|87 +huvudet|hu=vu=det|86 +sitter|sit=ter|85 +tittar|tit=tar|85 +längre|läng=re|83 +kontrollrummet|kon=troll=rum=met|82 +tänker|tän=ker|82 +faktiskt|fak=tiskt|81 +grader|gra=der|81 +sätter|sät=ter|81 +gången|gång=en|80 +luftslussen|luft=slus=sen|79 +energi|ener=gi|78 +känner|kän=ner|78 +skrovet|skro=vet|78 +väggen|väg=gen|78 +förmodligen|för=mod=li=gen|77 +ligger|lig=ger|77 +Objekt|Ob=jekt|77 +Kanske|Kanske|76 +åtminstone|åt=min=sto=ne|76 +rymden|rym=den|75 +händer|hän=der|73 +bättre|bätt=re|72 +Flintas|Flin=tas|72 +svävar|svä=var|72 +taumöbor|tau=mö=bor|71 +tillräckligt|till=räck=ligt|71 +verkligen|verk=li=gen|71 +börjar|bör=jar|70 +varför|var=för|70 +alltid|all=tid|69 +fungerar|fun=ge=rar|69 +tvungen|tvung=en|69 +handen|han=den|68 +använder|an=vän=der|67 +sekunder|se=kun=der|67 +timmar|tim=mar|67 +Förmodligen|För=mod=li=gen|66 +omkring|om=kring|66 +någonting|nå=gon=ting|65 +eridier|eri=di=er|64 +förstås|för=stås|64 +mellan|mel=lan|64 +rummet|rum=met|64 +därför|där=för|63 +händerna|hän=der=na|63 +snabbt|snabbt|63 +xenonit|xe=no=nit|63 +Adrian|Ad=ri=an|62 +använda|an=vän=da|62 +gravitation|gra=vi=ta=tion|62 +procent|pro=cent|62 +betyder|be=ty=der|60 +väldigt|väl=digt|60 +eftersom|ef=ter=som|59 +frågade|frå=ga=de|59 +minuter|mi=nu=ter|59 +vatten|vat=ten|59 +kilometer|ki=lo=me=ter|58 +öppnar|öpp=nar|58 +befinner|be=fin=ner|56 +bränsle|brän=sle|56 +luften|luf=ten|56 +stället|stäl=let|56 +ögonblick|ögon=blick|54 +försöker|för=sö=ker|53 +golvet|gol=vet|53 +ögonen|ögo=nen|53 +förstår|för=står|52 +hastighet|has=tig=het|52 +lägger|läg=ger|52 +koldioxid|kol=di=ox=id|51 +motorerna|mo=to=rer=na|51 +större|stör=re|51 +ljuset|lju=set|50 +verkar|ver=kar|49 +atmosfär|at=mo=sfär|48 +själva|själ=va|48 +astrofag|ast=ro=fag|47 +hittar|hit=tar|47 +närmare|när=ma=re|47 +ombord|om=bord|47 +stjärna|stjär=na|47 +system|sys=tem|47 +datorn|da=torn|46 +fullständigt|full=stän=digt|46 +fungera|fun=ge=ra|46 +luckan|luck=an|46 +någonsin|nå=gon=sin|45 +samtidigt|sam=ti=digt|45 +särskilt|sär=skilt|45 +trycker|tryck=er|45 +varandra|var=and=ra|45 +planeten|pla=ne=ten|44 +sovsalen|sov=sa=len|44 +bordet|bor=det|43 +dessutom|dess=utom|43 +vidare|vi=da=re|43 +allihop|al=li=hop|42 +behövde|be=höv=de|42 +information|in=for=ma=tion|42 +planet|pla=net|42 +skeppets|skep=pets|42 +överleva|över=le=va|42 +Dimitrij|Di=mit=rij|41 +rymdskepp|rymd=skepp|41 +senare|se=na=re|41 +vilken|vil=ken|41 +DuBois|Du=Bo=is|40 +sekund|se=kund|40 +Uppdrag|Upp=drag|40 +forskare|fors=ka=re|39 +klarar|kla=rar|39 +Petrovaskopet|Pe=tro=vas=ko=pet|39 +allting|all=ting|38 +hoppas|hop=pas|38 +klättrar|klätt=rar|38 +Mycket|Myck=et|38 +omloppsbana|om=lopps=ba=na|38 +skalbaggarna|skal=bag=gar=na|38 +framför|fram=för|37 +hantera|han=te=ra|37 +Skeppet|Skep=pet|37 +sträcker|sträck=er|37 +säkert|sä=kert|37 +extremt|ex=tremt|36 +faller|fal=ler|36 +framåt|fram=åt|36 +hundra|hun=dra|36 +Lokken|Lok=ken|36 +långsamt|lång=samt|36 +snabbare|snab=ba=re|36 +tidigare|ti=di=ga=re|36 +tunnel|tun=nel|36 +väntar|vän=tar|36 +Celsius|Cel=si=us|35 +doktor|dok=tor|35 +finnas|fin=nas|35 +Iljuchina|Iljuchi=na|35 +taumöborna|tau=mö=bor=na|35 +alltihop|allt=i=hop|34 +atmosfären|at=mo=sfä=ren|34 +behöva|be=hö=va|34 +består|be=står|34 +besättningen|be=sätt=ning=en|34 +centimeter|cen=ti=me=ter|34 +cylindern|cy=lin=dern|34 +dörren|dör=ren|34 +Ingenting|Ing=en=ting|34 +mitten|mit=ten|34 +någonstans|nå=gon=stans|34 +stolen|sto=len|34 +stänger|stäng=er|34 +Taumöba|Tau=mö=ba|34 +tittade|tit=ta=de|34 +världen|värl=den|34 +armarna|ar=mar=na|33 +blivit|bli=vit|33 +Eridier|Eri=di=er|33 +fortsätter|fort=sät=ter|33 +Precis|Pre=cis|33 +vilket|vil=ket|33 +Astrofagerna|Ast=ro=fa=ger=na|32 +Förstår|För=står|32 +jordens|jor=dens|32 +kedjan|ked=jan|32 +kommit|kom=mit|32 +miljoner|mil=jo=ner|32 +mänskligheten|mänsk=lig=he=ten|32 +möjligt|möj=ligt|32 +Petrovalinjen|Pe=tro=va=lin=jen|32 +systemet|sys=te=met|32 +Adrians|Ad=ri=ans|31 +dräkten|dräk=ten|31 +konstant|kon=stant|31 +resten|res=ten|31 +trodde|trod=de|31 +alldeles|all=de=les|30 +behövs|be=hövs|30 +experiment|ex=pe=ri=ment|30 +förstå|för=stå|30 +infraröda|in=fra=rö=da|30 +lyckas|lyck=as|30 +pekade|pe=ka=de|30 +Skulle|Skul=le|30 +varenda|var=en=da|30 +vetenskap|ve=ten=skap|30 +bredvid|bred=vid|29 +handlar|hand=lar|29 +material|ma=te=ri=al|29 +räcker|räck=er|29 +släpper|släp=per|29 +sättet|sät=tet|29 +sönder|sön=der|29 +Doktor|Dok=tor|28 +eridiska|eri=dis=ka|28 +griper|gri=per|28 +klockan|klock=an|28 +kollar|kol=lar|28 +riktigt|rik=tigt|28 +stegen|ste=gen|28 +arbeta|ar=be=ta|27 +flesta|fles=ta|27 +frågar|frå=gar|27 +heller|hel=ler|27 +människa|män=ni=ska|27 +perfekt|per=fekt|27 +tillverka|till=ver=ka|27 +trycket|tryck=et|27 +vetenskapliga|ve=ten=skap=li=ga|27 +ytterligare|yt=ter=li=ga=re|27 +annars|an=nars|26 +förmodar|för=mo=dar|26 +knackar|knack=ar|26 +kroppen|krop=pen|26 +minsta|mins=ta|26 +Människor|Män=ni=skor|26 +nickade|nick=a=de|26 +senaste|se=nas=te|26 +vänster|väns=ter|26 +försöka|för=sö=ka|25 +gillar|gil=lar|25 +infrarött|in=fra=rött|25 +klocka|klocka|25 +luftsluss|luft=sluss|25 +lämnar|läm=nar|25 +sekunden|se=kun=den|25 +spindriften|spindrif=ten|25 +temperaturen|tem=pe=ra=tu=ren|25 +ansiktet|an=sik=tet|24 +avstånd|av=stånd|24 +definitivt|de=fi=ni=tivt|24 +egentligen|egent=li=gen|24 +eridisk|eri=disk|24 +gravitationen|gra=vi=ta=tio=nen|24 +hennes|hen=nes|24 +hälften|hälf=ten|24 +klotet|klo=tet|24 +livsuppehållande|livs=up=pe=hål=lan=de|24 +motorer|mo=to=rer|24 +månader|må=na=der|24 +omedelbart|ome=del=bart|24 +papper|pap=per|24 +sticker|stick=er|24 +största|störs=ta|24 +tankarna|tan=kar=na|24 +Vilket|Vil=ket|24 +väntat|vän=tat|24 +Astrofager|Ast=ro=fa=ger|23 +diameter|dia=me=ter|23 +följer|föl=jer|23 +levande|le=van=de|23 +lämnade|läm=na=de|23 +måttbandet|mått=ban=det|23 +normalt|nor=malt|23 +närheten|när=he=ten|23 +personer|per=so=ner|23 +rotera|ro=te=ra|23 +stjärnor|stjär=nor|23 +tanken|tan=ken|23 +Ungefär|Un=ge=fär|23 +utrustning|ut=rust=ning|23 +verket|ver=ket|23 +visste|viss=te|23 +Allting|All=ting|22 +ammoniak|am=mo=ni=ak|22 +avgjort|av=gjort|22 +axlarna|ax=lar=na|22 +behållare|be=hål=la=re|22 +färdas|fär=das|22 +färden|fär=den|22 +förklara|för=kla=ra|22 +försvinner|för=svin=ner|22 +ganska|gans=ka|22 +Kommer|Kom=mer|22 +kontrollerar|kon=trol=le=rar|22 +plockar|plock=ar|22 +provet|pro=vet|22 +roterar|ro=te=rar|22 +rymddräkten|rymd=dräk=ten|22 +räknar|räk=nar|22 +skakade|ska=ka=de|22 +sängen|säng=en|22 +vinkel|vin=kel|22 +Åtminstone|Åt=min=sto=ne|22 +överallt|över=allt|22 +besättning|be=sätt=ning|21 +betydligt|be=tyd=ligt|21 +celler|cel=ler|21 +dåligt|då=ligt|21 +genast|ge=nast|21 +handtag|hand=tag|21 +intressant|in=tres=sant|21 +lättare|lät=ta=re|21 +magnetfält|mag=net=fält|21 +omöjligt|omöj=ligt|21 +plötsligt|plöts=ligt|21 +slutar|slu=tar|21 +stirrade|stir=ra=de|21 +sådant|så=dant|21 +tillsammans|till=sam=mans|21 +tvungna|tvung=na|21 +visade|vi=sa=de|21 +vrider|vri=der|21 +andetag|an=de=tag|20 +befann|be=fann|20 +bokstavligen|bok=stav=li=gen|20 +började|bör=ja=de|20 +Dessutom|Dess=utom|20 +innehåller|in=ne=hål=ler|20 +knappt|knappt|20 +känsla|käns=la|20 +lösning|lös=ning|20 +misstänker|miss=tän=ker|20 +närmaste|när=mas=te|20 +planeter|pla=ne=ter|20 +snurrar|snur=rar|20 +strålning|strål=ning|20 +upprepar|upp=re=par|20 +utanför|ut=an=för|20 +vakuum|va=ku=um|20 +vänder|vän=der|20 +åstadkomma|åstad=kom=ma|20 +blicken|blick=en|19 +direkt|di=rekt|19 +energin|ener=gin|19 +främmande|främ=man=de|19 +historia|his=to=ria|19 +hittade|hit=ta=de|19 +innebär|in=ne=bär|19 +kamera|ka=me=ra|19 +kontrollera|kon=trol=le=ra|19 +likadant|li=ka=dant|19 +lyckades|lyck=a=des|19 +pilotstolen|pi=lot=sto=len|19 +rejält|re=jält|19 +rymddräkt|rymd=dräkt|19 +sjunker|sjun=ker|19 +stanna|stan=na|19 +starkt|starkt|19 +temperatur|tem=pe=ra=tur|19 +Trevärlden|Tre=värl=den|19 +acceleration|ac=ce=le=ra=tion|18 +använde|an=vän=de|18 +arbetar|ar=be=tar|18 +behållaren|be=hål=la=ren|18 +början|bör=jan|18 +centrifug|cen=tri=fug|18 +Därför|Där=för|18 +Eridani|Eri=da=ni|18 +fortplantar|fort=plan=tar|18 +hjälpa|hjäl=pa|18 +liksom|lik=som|18 +ljusår|ljus=år|18 +lutade|lu=ta=de|18 +metall|me=tall|18 +mumlar|mum=lar|18 +pannan|pan=nan|18 +problemet|pro=ble=met|18 +rimligt|rim=ligt|18 +teknik|tek=nik|18 +uppenbart|uppen=bart|18 +utvecklats|ut=veck=lats|18 +finger|fing=er|17 +förresten|för=res=ten|17 +förseglar|för=seg=lar|17 +hastigheten|has=tig=he=ten|17 +hittat|hit=tat|17 +hämtar|häm=tar|17 +Intressant|In=tres=sant|17 +kallas|kal=las|17 +marken|mar=ken|17 +resultat|re=sul=tat|17 +ryckte|ryck=te|17 +Shapiro|Sha=pi=ro|17 +skakar|ska=kar|17 +stannar|stan=nar|17 +starkare|star=ka=re|17 +stirrar|stir=rar|17 +talade|ta=la=de|17 +uppdraget|upp=dra=get|17 +varken|var=ken|17 +Vilken|Vil=ken|17 +vinklar|vink=lar|17 +vänner|vän=ner|17 +xenoniten|xe=no=ni=ten|17 +absolut|ab=so=lut|16 +Alltså|Allt=så|16 +bränslet|brän=slet|16 +driver|dri=ver|16 +därefter|där=ef=ter|16 +Dåligt|Då=ligt|16 +fingrar|fing=rar|16 +fingret|fing=ret|16 +flyttar|flyt=tar|16 +fortplanta|fort=plan=ta|16 +fungerade|fun=ge=ra=de|16 +försökte|för=sök=te|16 +genomskinliga|ge=nom=skin=li=ga|16 +hittills|hit=tills|16 +informationen|in=for=ma=tio=nen|16 +knappast|knap=past|16 +kvävet|kvä=vet|16 +livsform|livs=form|16 +munnen|mun=nen|16 +pansaret|pan=sa=ret|16 +praktiskt|prak=tiskt|16 +pratar|pra=tar|16 +skillnad|skill=nad|16 +skriver|skri=ver|16 +snarare|sna=ra=re|16 +solsystem|sol=sys=tem|16 +stjärnan|stjär=nan|16 +studsar|stud=sar|16 +svarta|svar=ta|16 +sådana|så=da=na|16 +träffa|träf=fa|16 +verktyg|verk=tyg|16 +äntligen|änt=li=gen|16 +återvänder|åter=vän=der|16 +atmosfärer|at=mo=sfä=rer|15 +beredd|be=redd|15 +bilden|bil=den|15 +centrifugen|cen=tri=fu=gen|15 +Datorn|Da=torn|15 +Eftersom|Ef=ter=som|15 +experimentet|ex=pe=ri=men=tet|15 +fortsatte|fort=sat=te|15 +föremål|fö=re=mål|15 +gammal|gam=mal|15 +George|Ge=or=ge|15 +glaset|gla=set|15 +gäller|gäl=ler|15 +jordiska|jor=dis=ka|15 +kallar|kal=lar|15 +kinesiska|ki=ne=sis=ka|15 +laboratoriebänken|la=bo=ra=to=rie=bän=ken|15 +linjen|lin=jen|15 +längst|längst|15 +mängder|mäng=der|15 +naturligtvis|na=tur=ligt=vis|15 +Någonting|Nå=gon=ting|15 +ordentligt|or=dent=ligt|15 +prover|pro=ver|15 +självmordsuppdrag|själv=mordsupp=drag|15 +skicka|skicka|15 +slussen|slus=sen|15 +smälta|smäl=ta|15 +studera|stu=de=ra|15 +ställer|stäl=ler|15 +Taumöborna|Tau=mö=bor=na|15 +trettio|tret=tio|15 +tycker|tyck=er|15 +uppför|upp=för|15 +uppleva|upp=le=va|15 +upptäcka|upp=täcka|15 +upptäckte|upp=täck=te|15 +utstrålar|ut=strå=lar|15 +vaknar|vak=nar|15 +ögonblicket|ögon=blick=et|15 +Armarna|Ar=mar=na|14 +besättningsdelen|be=sätt=nings=de=len|14 +börjat|bör=jat|14 +diagram|dia=gram|14 +elever|ele=ver|14 +enligt|en=ligt|14 +flyger|fly=ger|14 +framsteg|fram=steg|14 +frågor|frå=gor|14 +förseglade|för=seg=la=de|14 +försök|för=sök|14 +generatorn|ge=ne=ra=torn|14 +hejdar|hej=dar|14 +hjärna|hjär=na|14 +Jorden|Jor=den|14 +kontroller|kon=trol=ler|14 +kräver|krä=ver|14 +laptop|lap=top|14 +Leclerc|Leclerc|14 +millimeter|mil=li=me=ter|14 +minskar|mins=kar|14 +mänskliga|mänsk=li=ga|14 +neutriner|ne=u=tri=ner|14 +objekt|ob=jekt|14 +panelen|pa=ne=len|14 +prickarna|prick=ar=na|14 +primära|pri=mä=ra|14 +princip|prin=cip|14 +provtagaren|prov=ta=ga=ren|14 +rullar|rul=lar|14 +räckte|räck=te|14 +Självklart|Själv=klart|14 +skaffa|skaf=fa|14 +spelar|spe=lar|14 +stabil|sta=bil|14 +stjärnorna|stjär=nor=na|14 +stoppuret|stopp=uret|14 +stämmer|stäm=mer|14 +svarar|sva=rar|14 +svaret|sva=ret|14 +Taumöbor|Tau=mö=bor|14 +tredje|tred=je|14 +upplever|upp=le=ver|14 +utomjordiska|ut=om=jor=dis=ka|14 +vinkar|vin=kar|14 +väggarna|väg=gar=na|14 +världens|värl=dens|14 +öppnade|öpp=na=de|14 +accelererar|ac=ce=le=re=rar|13 +alltså|allt=så|13 +aluminium|alu=mi=ni=um|13 +anteckningar|an=teck=ning=ar|13 +Astrofag|Ast=ro=fag|13 +astrofagernas|ast=ro=fa=ger=nas|13 +astronauter|ast=ro=nau=ter|13 +cirkel|cir=kel|13 +externa|ex=ter=na|13 +fantastiskt|fan=tas=tiskt|13 +förrän|förr=än|13 +förtjust|för=tjust|13 +förvissa|för=vis=sa|13 +gesten|ges=ten|13 +gjorda|gjor=da|13 +handtaget|hand=ta=get|13 +kameran|ka=me=ran|13 +kontrollerna|kon=trol=ler=na|13 +ljudet|lju=det|13 +mikron|mik=ron|13 +mikroskopet|mik=ro=sko=pet|13 +misstag|miss=tag|13 +normala|nor=ma=la|13 +nummer|num=mer|13 +nödvändigt|nöd=vän=digt|13 +paddan|pad=dan|13 +Petrovafrekvensen|Pe=tro=va=f=re=kven=sen|13 +platta|plat=ta|13 +Projekt|Pro=jekt|13 +prylar|pry=lar|13 +ryggen|ryg=gen|13 +rymdpromenad|rymd=pro=me=nad|13 +rörelse|rö=rel=se|13 +slutet|slu=tet|13 +solens|so=lens|13 +sonden|son=den|13 +spänner|spän=ner|13 +stoppar|stop=par|13 +ställe|stäl=le|13 +svarade|sva=ra=de|13 +timmarna|tim=mar=na|13 +ursprungliga|ur=sprung=li=ga|13 +Ursäkta|Ur=säk=ta|13 +utveckla|ut=veck=la|13 +vinschen|vin=schen|13 +väntade|vän=ta=de|13 +våglängder|våg=läng=der|13 +önskar|öns=kar|13 +Allihop|Al=li=hop|12 +avgöra|av=gö=ra|12 +bestämt|be=stämt|12 +botten|bot=ten|12 +bubbla|bubb=la|12 +diagrammet|dia=gram=met|12 +engelska|eng=els=ka|12 +Enligt|En=ligt|12 +enormt|enormt|12 +fattar|fat=tar|12 +flytande|fly=tan=de|12 +fönster|föns=ter|12 +fönstret|fönst=ret|12 +förklarar|för=kla=rar|12 +förseglad|för=seg=lad|12 +gissar|gis=sar|12 +grimas|gri=mas|12 +hangarfartyg|han=gar=far=tyg|12 +hänger|häng=er|12 +iakttar|iakt=tar|12 +klarade|kla=ra=de|12 +knyter|kny=ter|12 +minska|mins=ka|12 +pumpar|pum=par|12 +påminner|på=min=ner|12 +riktad|rik=tad|12 +rotation|ro=ta=tion|12 +räknade|räk=na=de|12 +självklart|själv=klart|12 +skalbagge|skal=bag=ge|12 +skärmar|skär=mar|12 +strunt|strunt|12 +tecknade|teck=na=de|12 +tilltalande|till=ta=lan=de|12 +tjocka|tjocka|12 +tänkbara|tänk=ba=ra|12 +utomjordisk|ut=om=jor=disk|12 +vänstra|vänst=ra|12 +öppnas|öpp=nas|12 +överst|överst|12 +absurt|ab=surt|11 +amerikanska|ame=ri=kans=ka|11 +anledning|an=led=ning|11 +annorlunda|an=nor=lun=da|11 +ansikte|an=sik=te|11 +använt|an=vänt|11 +apparat|ap=pa=rat|11 +arbete|ar=be=te|11 +ArcLight|Ar=c=Light|11 +atmosfärers|at=mo=sfä=rers|11 +bibehålla|bi=be=hål=la|11 +bränsletankar|brän=sle=tan=kar|11 +byggde|bygg=de|11 +drämmer|dräm=mer|11 +fortsätta|fort=sät=ta|11 +fundera|fun=de=ra|11 +funderar|fun=de=rar|11 +följande|föl=jan=de|11 +förblir|för=blir|11 +generator|ge=ne=ra=tor|11 +genomskinlig|ge=nom=skin=lig|11 +handlade|hand=la=de|11 +Herregud|Her=re=gud|11 +intelligens|in=tel=li=gens|11 +komplicerat|kom=pli=ce=rat|11 +konstruerat|kon=stru=e=rat|11 +korrekt|kor=rekt|11 +kraften|kraf=ten|11 +kvalificerade|kva=li=fi=ce=ra=de|11 +lagrar|lag=rar|11 +livsformer|livs=for=mer|11 +lyckats|lyck=ats|11 +lämnat|läm=nat|11 +länkar|län=kar|11 +miljarder|mil=jar=der|11 +mittväggen|mitt=väg=gen|11 +nedför|ned=för|11 +normal|nor=mal|11 +ordning|ord=ning|11 +overallen|ove=ral=len|11 +partiklar|par=tik=lar|11 +position|po=si=tion|11 +riktning|rikt=ning|11 +rycker|ryck=er|11 +ryggskölden|rygg=sköl=den|11 +Ryland|Ry=land|11 +skruvar|skru=var|11 +sliter|sli=ter|11 +spaken|spa=ken|11 +suckar|suck=ar|11 +taumöba|tau=mö=ba|11 +tjugosex|tju=go=sex|11 +torkar|tor=kar|11 +tydligen|tyd=li=gen|11 +undrar|und=rar|11 +upphör|upp=hör|11 +upptäcker|upp=täck=er|11 +utomjordingar|ut=om=jor=ding=ar|11 +utomjordiskt|ut=om=jor=diskt|11 +verkade|ver=ka=de|11 +verkligt|verk=ligt|11 +vickar|vick=ar|11 +viftar|vif=tar|11 +Xenonit|Xe=no=nit|11 +Alltihop|Allt=i=hop|10 +automatiskt|au=to=ma=tiskt|10 +avståndet|av=stån=det|10 +befinna|be=fin=na|10 +bestämmer|be=stäm=mer|10 +betyda|be=ty=da|10 +bollen|bol=len|10 +bröstet|brös=tet|10 +Bättre|Bätt=re|10 +cylinder|cy=lin=der|10 +decimeter|de=ci=me=ter|10 +dragkraft|drag=kraft|10 +enbart|en=bart|10 +enheter|en=he=ter|10 +enorma|enor=ma|10 +eridiskt|eri=diskt|10 +femtio|fem=tio|10 +ficklampa|fick=lam=pa|10 +fjorton|fjor=ton|10 +glider|gli=der|10 +gnider|gni=der|10 +graders|gra=ders|10 +harklade|hark=la=de|10 +högsta|högs=ta|10 +ingenjör|in=gen=jör|10 +instrument|in=stru=ment|10 +intelligenta|in=tel=li=gen=ta|10 +jobbar|job=bar|10 +Jösses|Jös=ses|10 +kanten|kan=ten|10 +klättra|klätt=ra|10 +kvinna|kvin=na|10 +Labbet|Lab=bet|10 +livlinan|liv=li=nan|10 +ljushastigheten|ljus=h=as=tig=he=ten|10 +mackapären|mack=a=pä=ren|10 +mellanväggen|mel=lan=väg=gen|10 +mikroskop|mik=ro=skop|10 +miljon|mil=jon|10 +mitokondrier|mi=to=kond=ri=er|10 +motorn|mo=torn|10 +mänsklig|mänsk=lig|10 +möjliga|möj=li=ga|10 +mörker|mör=ker|10 +nuvarande|nu=va=ran=de|10 +närvarande|när=va=ran=de|10 +objektglas|ob=jekt=glas|10 +odlingstankarna|od=lingstan=kar=na|10 +område|om=rå=de|10 +området|om=rå=det|10 +plockade|plock=a=de|10 +processen|pro=ces=sen|10 +projektet|pro=jek=tet|10 +resultatet|re=sul=ta=tet|10 +riskera|ris=ke=ra|10 +räknat|räk=nat|10 +sjunka|sjun=ka|10 +skador|ska=dor|10 +skickade|skick=a=de|10 +skälet|skä=let|10 +skärmarna|skär=mar=na|10 +slippa|slip=pa|10 +släppte|släpp=te|10 +spindrift|spindrift|10 +stilen|sti=len|10 +symbolen|sym=bo=len|10 +tankar|tan=kar|10 +tejpar|tej=par|10 +testade|tes=ta=de|10 +tillfället|till=fäl=let|10 +tillverkade|till=ver=ka=de|10 +tillverkar|till=ver=kar|10 +tretton|tret=ton|10 +ungarna|ung=ar=na|10 +utrymmet|ut=rym=met|10 +vanligt|van=ligt|10 +Varenda|Var=en=da|10 +varmare|var=ma=re|10 +vattnet|vatt=net|10 +vinkeln|vin=keln|10 +väljer|väl=jer|10 +värmen|vär=men|10 +våglängd|våg=längd|10 +återgår|åter=går|10 +absorberar|ab=sor=be=rar|9 +alternativ|al=ter=na=tiv|9 +amerikansk|ame=ri=kansk|9 +Antingen|An=ting=en|9 +apropå|apro=på|9 +bekräftar|be=kräf=tar|9 +beredda|be=red=da|9 +besättningskamrater|be=sätt=nings=kam=ra=ter|9 +blinkar|blin=kar|9 +blängde|bläng=de|9 +Browne|Brow=ne|9 +chansen|chan=sen|9 +Cylindern|Cy=lin=dern|9 +därifrån|där=i=från|9 +effektivt|ef=fek=tivt|9 +eridierna|eri=di=er=na|9 +fallet|fal=let|9 +flyttade|flyt=ta=de|9 +frekvenser|fre=kven=ser|9 +fyller|fyl=ler|9 +fyrtio|fyr=tio|9 +förhållande|för=hål=lan=de|9 +förrådet|för=rå=det|9 +förrådsutrymmet|för=råds=ut=rym=met|9 +försiktigt|för=sik=tigt|9 +fötter|föt=ter|9 +generation|ge=ne=ra=tion|9 +hexagonen|hexa=go=nen|9 +hållet|hål=let|9 +hårdare|hår=da=re|9 +hörnet|hör=net|9 +identiska|iden=tis=ka|9 +imponerande|im=po=ne=ran=de|9 +irriterande|ir=ri=te=ran=de|9 +jobbet|job=bet|9 +jordisk|jor=disk|9 +justerar|ju=ste=rar|9 +klickar|klick=ar|9 +klorna|klor=na|9 +kläder|klä=der|9 +kompis|kom=pis|9 +kontakten|kon=tak=ten|9 +kontroll|kon=troll|9 +kryper|kry=per|9 +laboratorium|la=bo=ra=to=ri=um|9 +ljusets|lju=sets|9 +lossar|los=sar|9 +läcker|läck=er|9 +lärare|lä=ra=re|9 +långsammare|lång=sam=ma=re|9 +medvetslös|med=vets=lös|9 +miniodlingarna|mi=ni=od=ling=ar=na|9 +Minsann|Min=sann|9 +modell|mo=dell|9 +människorna|män=ni=skor=na|9 +mänsklighetens|mänsk=lig=he=tens|9 +måltid|mål=tid|9 +närmast|när=mast|9 +Nästan|Näs=tan|9 +observera|ob=ser=ve=ra|9 +oerhört|oer=hört|9 +omloppsbanan|om=lopps=ba=nan|9 +omsorgsfullt|om=sorgs=fullt|9 +organismer|or=ga=nis=mer|9 +otroligt|otro=ligt|9 +pansar|pan=sar|9 +Petrovalinje|Pe=tro=va=lin=je|9 +placerade|pla=ce=ra=de|9 +Plötsligt|Plöts=ligt|9 +reagerar|re=a=ge=rar|9 +Redell|Re=dell|9 +relativt|re=la=tivt|9 +risken|ris=ken|9 +risker|ris=ker|9 +rotationen|ro=ta=tio=nen|9 +ryssarna|rys=sar=na|9 +samlar|sam=lar|9 +samtal|sam=tal|9 +senast|se=nast|9 +skjuter|skju=ter|9 +slumpmässigt|slump=mäs=sigt|9 +snurra|snur=ra|9 +starka|star=ka|9 +status|sta=tus|9 +stönar|stö=nar|9 +säkerhets|sä=ker=hets|9 +Tunneln|Tun=neln|9 +tunnelväggen|tun=nel=väg=gen|9 +tyckte|tyck=te|9 +uppgift|upp=gift|9 +uppgiften|upp=gif=ten|9 +uppriktigt|upp=rik=tigt|9 +upptäckt|upp=täckt|9 +utomjording|ut=om=jor=ding|9 +utrymme|ut=rym=me|9 +utvecklas|ut=veck=las|9 +varelse|va=rel=se|9 +veckor|veck=or|9 +vetenskaplig|ve=ten=skap=lig|9 +viktigaste|vik=ti=gas=te|9 +yttersta|yt=ters=ta|9 +återstående|åter=stå=en=de|9 +återvända|åter=vän=da|9 +allvar|all=var|8 +ansträngning|an=sträng=ning|8 +Antarktis|Ant=ark=tis|8 +atomer|ato=mer|8 +Bajkonur|Baj=ko=nur|8 +behålla|be=hål=la|8 +behållarna|be=hål=lar=na|8 +biologi|bio=lo=gi|8 +brinna|brin=na|8 +bränsletankarna|brän=sle=tan=kar=na|8 +dagarna|da=gar=na|8 +effekt|ef=fekt|8 +elektricitet|el=ekt=ri=ci=tet|8 +enastående|ena=stå=en=de|8 +Faktiskt|Fak=tiskt|8 +farligt|far=ligt|8 +fjärde|fjär=de|8 +flytta|flyt=ta|8 +formen|for=men|8 +forskarna|fors=kar=na|8 +följaktligen|följ=akt=li=gen|8 +förbaskat|för=bas=kat|8 +Förenta|För=en=ta|8 +föreställa|fö=re=stäl=la|8 +förmåga|för=må=ga|8 +förråd|för=råd|8 +genomföra|ge=nom=fö=ra|8 +gläder|glä=der|8 +grundläggande|grund=läg=gan=de|8 +gränser|grän=ser|8 +handtagen|hand=ta=gen|8 +helvete|hel=ve=te|8 +hjälper|hjäl=per|8 +häpnadsväckande|häp=nads=väck=an=de|8 +jazzhänder|jazzhän=der|8 +kammaren|kam=ma=ren|8 +kastade|kas=ta=de|8 +knappen|knap=pen|8 +konstigt|kons=tigt|8 +kunnat|kun=nat|8 +laptoppen|lap=top=pen|8 +leende|le=en=de|8 +livlina|liv=li=na|8 +livlinor|liv=li=nor|8 +locket|lock=et|8 +lossnar|loss=nar|8 +luktar|luk=tar|8 +lycklig|lyck=lig|8 +maskinen|ma=ski=nen|8 +menade|me=na=de|8 +minnas|min=nas|8 +modellen|mo=del=len|8 +mojäng|mo=jäng|8 +nacken|nack=en|8 +naturliga|na=tur=li=ga|8 +navigationskonsolen|na=vi=ga=tions=kon=so=len|8 +omvandlar|om=vand=lar|8 +operationen|ope=ra=tio=nen|8 +Petrovaskop|Pe=tro=vas=kop|8 +Poängen|Po=äng=en|8 +provbehållaren|prov=be=hål=la=ren|8 +provrör|prov=rör|8 +påskriften|på=skrif=ten|8 +radion|ra=di=on|8 +reflekterar|re=flek=te=rar|8 +robotarmarna|ro=bot=armar=na|8 +ruskar|rus=kar|8 +rörelser|rö=rel=ser|8 +saktar|sak=tar|8 +samlat|sam=lat|8 +sextio|sex=tio|8 +sjukdomar|sjuk=do=mar|8 +sjutton|sjut=ton|8 +skeppen|skep=pen|8 +skickar|skick=ar|8 +skriva|skri=va|8 +sluter|slu=ter|8 +slänga|slänga|8 +slängde|släng=de|8 +slänger|släng=er|8 +smäller|smäl=ler|8 +smärtstillande|smärt=stil=lan=de|8 +sparkar|spar=kar|8 +stilla|stil=la|8 +struntar|strun=tar|8 +stänga|stänga|8 +suckade|suck=a=de|8 +svänger|sväng=er|8 +teleskopet|te=le=sko=pet|8 +texten|tex=ten|8 +tillfälle|till=fäl=le|8 +tillgång|till=gång|8 +timmen|tim=men|8 +tjugotre|tju=go=tre|8 +Trevärldens|Tre=värl=dens|8 +tryggt|tryggt|8 +träffas|träf=fas|8 +Tydligen|Tyd=li=gen|8 +täcker|täck=er|8 +uppfatta|upp=fat=ta|8 +uppfattning|upp=fatt=ning|8 +uppfunnit|upp=fun=nit|8 +urskilja|ur=skil=ja|8 +utföra|ut=fö=ra|8 +utvecklade|ut=veck=la=de|8 +vaknade|vak=na=de|8 +vanliga|van=li=ga|8 +vartenda|vart=en=da|8 +viktiga|vik=ti=ga|8 +viktigt|vik=tigt|8 +vägrar|väg=rar|8 +zoomar|zoo=mar|8 +Återigen|Åter=i=gen|8 +Överallt|Över=allt|8 +överlevde|över=lev=de|8 +överlever|över=le=ver|8 +överväldigande|över=väl=di=gan=de|8 +Absolut|Ab=so=lut|7 +absorbera|ab=sor=be=ra|7 +aktern|ak=tern|7 +aktiverar|ak=ti=ve=rar|7 +analys|ana=lys|7 +användas|an=vän=das|7 +apparaten|ap=pa=ra=ten|7 +astronauterna|ast=ro=nau=ter=na|7 +auktoritet|auk=to=ri=tet|7 +avsiktligt|av=sikt=ligt|7 +begriper|be=gri=per|7 +bekräfta|be=kräf=ta|7 +bekväm|be=kväm|7 +biljoner|bil=jo=ner|7 +blåste|blås=te|7 +brukar|bru=kar|7 +bränslekapsel|brän=s=le=kap=sel|7 +bränslekapslar|brän=s=le=kaps=lar|7 +bänken|bän=ken|7 +datorer|da=to=rer|7 +domaren|do=ma=ren|7 +dramatiskt|dra=ma=tiskt|7 +dricker|drick=er|7 +dräller|dräl=ler|7 +dödade|dö=da=de|7 +effektiva|ef=fek=ti=va|7 +elegant|ele=gant|7 +Eridierna|Eri=di=er=na|7 +Eridisk|Eri=disk|7 +evolutionen|evo=lu=tio=nen|7 +fastsatt|fast=satt|7 +femton|fem=ton|7 +flämtar|fläm=tar|7 +flåsar|flå=sar|7 +Fortfarande|Fort=fa=ran=de|7 +frekvenserna|fre=kven=ser=na|7 +frukost|fru=kost|7 +frågan|frå=gan|7 +Frånsett|Från=sett|7 +funderade|fun=de=ra=de|7 +fångar|fång=ar|7 +följde|följ=de|7 +gastar|gas=tar|7 +generna|ge=ner=na|7 +genomför|ge=nom=för|7 +genomskinligt|ge=nom=skin=ligt|7 +glasskivan|glas=ski=van|7 +grabbar|grab=bar|7 +gradvis|grad=vis|7 +Gravitationen|Gra=vi=ta=tio=nen|7 +hettan|het=tan|7 +hinner|hin=ner|7 +hälfter|hälf=ter|7 +identifiera|iden=ti=fi=e=ra|7 +infekterad|in=fek=te=rad|7 +inifrån|in=i=från|7 +Instämmer|In=stäm=mer|7 +interstellär|in=ter=stel=lär|7 +intill|in=till|7 +Jordens|Jor=dens|7 +jordiskt|jor=diskt|7 +jämförelse|jäm=fö=rel=se|7 +kallare|kal=la=re|7 +kandidater|kan=di=da=ter|7 +katastrof|ka=ta=strof|7 +klarat|kla=rat|7 +kliver|kli=ver|7 +klumpiga|klum=pi=ga|7 +klyftig|klyf=tig|7 +knackade|knack=a=de|7 +knogarna|kno=gar=na|7 +kolossalt|ko=los=salt|7 +komplicerad|kom=pli=ce=rad|7 +kontakt|kon=takt|7 +kritan|kri=tan|7 +lampor|lam=por|7 +likadan|li=ka=dan|7 +Luften|Luf=ten|7 +luftslussens|luft=slus=sens|7 +Lyckligtvis|Lyck=ligt=vis|7 +läkare|lä=ka=re|7 +massor|mas=sor|7 +meningen|me=ning=en|7 +modeller|mo=del=ler|7 +Motorerna|Mo=to=rer=na|7 +motsatta|mot=sat=ta|7 +motsvarar|mot=sva=rar|7 +musklerna|musk=ler=na|7 +mängden|mäng=den|7 +märkligt|märk=ligt|7 +möjlighet|möj=lig=het|7 +Naturligtvis|Na=tur=ligt=vis|7 +närmar|när=mar|7 +omgiven|om=gi=ven|7 +Orlandräkten|Or=landräk=ten|7 +panorerar|pa=no=re=rar|7 +person|per=son|7 +placerar|pla=ce=rar|7 +plötslig|plöts=lig|7 +protoner|pro=to=ner|7 +proverna|pro=ver=na|7 +pärlor|pär=lor|7 +radarskärmen|ra=dar=skär=men|7 +reflekteras|re=flek=te=ras|7 +Robotarmarna|Ro=bot=armar=na|7 +roboten|ro=bo=ten|7 +rymdfarkost|rymd=far=kost|7 +rymdpromenader|rymd=pro=me=na=der|7 +samtliga|samt=li=ga|7 +sannolikt|san=no=likt|7 +separat|se=pa=rat|7 +sexton|sex=ton|7 +siffror|siff=ror|7 +sjukdom|sjuk=dom|7 +Skalbaggarna|Skal=bag=gar=na|7 +skickat|skick=at|7 +skrapar|skra=par|7 +skrattade|skrat=ta=de|7 +skrattar|skrat=tar|7 +Skärmen|Skär=men|7 +slutsatser|slut=sat=ser|7 +smälter|smäl=ter|7 +snabba|snab=ba|7 +somliga|som=li=ga|7 +somnar|som=nar|7 +specifika|spe=ci=fi=ka|7 +spektrat|spekt=rat|7 +startar|star=tar|7 +Status|Sta=tus|7 +Stratts|Stratts|7 +stycken|styck=en|7 +styrka|styr=ka|7 +ställt|ställt|7 +stängde|stäng=de|7 +synfält|syn=fält|7 +synliga|syn=li=ga|7 +synligt|syn=ligt|7 +synvinkel|syn=vin=kel|7 +tappar|tap=par|7 +tecken|teck=en|7 +testat|tes=tat|7 +tidigt|ti=digt|7 +tillstånd|till=stånd|7 +tjockt|tjockt|7 +troligen|tro=li=gen|7 +trycka|trycka|7 +träffar|träf=far|7 +tvåhundra|två=hun=dra|7 +Tänker|Tän=ker|7 +uppsättning|upp=sätt=ning|7 +utifrån|ut=i=från|7 +utmärkt|ut=märkt|7 +vanlig|van=lig|7 +varsin|var=sin|7 +Vatten|Vat=ten|7 +villebråd|vil=le=bråd|7 +värsta|värs=ta|7 +vätska|väts=ka|7 +ägnade|äg=na=de|7 +ändras|änd=ras|7 +åstadkommer|åstad=kom=mer|7 +öppningen|öpp=ning=en|7 +överlevande|över=le=van=de|7 +överlevt|över=levt|7 +accelerationen|ac=ce=le=ra=tio=nen|6 +accelerera|ac=ce=le=re=ra|6 +Afrika|Af=ri=ka|6 +alltför|allt=för|6 +alternativet|al=ter=na=ti=vet|6 +Ammoniak|Am=mo=ni=ak|6 +aningen|aning=en|6 +antaganden|an=ta=gan=den|6 +antingen|an=ting=en|6 +Använd|An=vänd|6 +användbart|an=vänd=bart|6 +arbetade|ar=be=ta=de|6 +arbetsbänk|ar=bets=bänk|6 +asteroid|as=te=ro=id|6 +Astronauter|Ast=ro=nau=ter|6 +avancerad|avan=ce=rad|6 +avgörande|av=gö=ran=de|6 +avläsning|av=läs=ning|6 +avläsningar|av=läs=ning=ar|6 +avsedd|av=sedd|6 +behövt|be=hövt|6 +beräkna|be=räk=na|6 +beräkningar|be=räk=ning=ar|6 +Berätta|Be=rät=ta|6 +berättat|be=rät=tat|6 +beslut|be=slut|6 +Besättningen|Be=sätt=ning=en|6 +bisarra|bi=sar=ra|6 +bitarna|bi=tar=na|6 +bläddrade|blädd=ra=de|6 +brytning|bryt=ning|6 +brännmärken|bränn=mär=ken|6 +bränslekapslarna|brän=s=le=kaps=lar=na|6 +burriton|bur=ri=ton|6 +cellerna|cel=ler=na|6 +decennier|de=cen=ni=er|6 +delade|de=la=de|6 +detalj|de=talj|6 +dubbelt|dub=belt|6 +Därefter|Där=ef=ter|6 +enstaka|en=sta=ka|6 +erbjuder|er=bju=der|6 +ersättare|er=sät=ta=re|6 +evighet|evig=het|6 +expeditionen|ex=pe=di=tio=nen|6 +fantastisk|fan=tas=tisk|6 +fantastiska|fan=tas=tis=ka|6 +farkosten|far=kos=ten|6 +farten|far=ten|6 +farväl|far=väl|6 +fastsatta|fast=sat=ta|6 +femhundra|fem=hun=dra|6 +former|for=mer|6 +forskning|forsk=ning|6 +frivillig|fri=vil=lig|6 +funktion|funk=tion|6 +funktioner|funk=tio=ner|6 +fysisk|fy=sisk|6 +färger|fär=ger|6 +fäster|fäs=ter|6 +förbli|för=bli|6 +föreställer|fö=re=stäl=ler|6 +förflytta|för=flyt=ta|6 +förhållanden|för=hål=lan=den|6 +förklarade|för=kla=ra=de|6 +förklaring|för=kla=ring|6 +förlora|för=lo=ra|6 +försedd|för=sedd|6 +försegla|för=seg=la|6 +förstod|för=stod|6 +Försök|För=sök|6 +försöket|för=sö=ket|6 +förändras|för=änd=ras|6 +fötterna|föt=ter=na|6 +gemensamt|ge=men=samt|6 +giftigt|gif=tigt|6 +halvvägs|halv=vägs|6 +Hjärtat|Hjär=tat|6 +hoppar|hop=par|6 +hundratals|hun=dra=tals|6 +härifrån|här=i=från|6 +Håller|Hål=ler|6 +hållit|hål=lit|6 +Ibland|Ibland|6 +Iljuchinas|Iljuchi=nas|6 +improviserade|im=pro=vi=se=ra=de|6 +impulsen|im=pul=sen|6 +innanför|in=nan=för|6 +interstellära|in=ter=stel=lä=ra|6 +isolera|iso=le=ra|6 +Javisst|Ja=visst|6 +jobbade|job=ba=de|6 +jämfört|jäm=fört|6 +kablarna|kab=lar=na|6 +kamerorna|ka=me=ror=na|6 +killar|kil=lar|6 +knappar|knap=par|6 +komagegga|ko=ma=geg=ga|6 +kommunicera|kom=mu=ni=ce=ra|6 +komplicerade|kom=pli=ce=ra=de|6 +konstruera|kon=stru=e=ra|6 +konstruktionen|kon=struk=tio=nen|6 +kontrolleras|kon=trol=le=ras|6 +Kraften|Kraf=ten|6 +kubikmeter|ku=bik=me=ter|6 +lagrad|lag=rad|6 +liknar|lik=nar|6 +listan|lis=tan|6 +livsviktiga|livs=vik=ti=ga|6 +ljuskälla|lju=s=käl=la|6 +Luckan|Luck=an|6 +lådorna|lå=dor=na|6 +löjligt|löj=ligt|6 +madrassen|mad=ras=sen|6 +manuella|ma=nu=el=la|6 +massan|mas=san|6 +Massor|Mas=sor|6 +materialet|ma=te=ri=a=let|6 +medicinska|me=di=cins=ka|6 +meningslöst|me=nings=löst|6 +Merkurius|Mer=ku=ri=us|6 +miljontals|mil=jon=tals|6 +minnet|min=net|6 +misslyckas|miss=lyck=as|6 +mobilen|mo=bi=len|6 +montera|mon=te=ra|6 +monterade|mon=te=ra=de|6 +morgon|mor=gon|6 +muttrade|mutt=ra=de|6 +namnet|nam=net|6 +nanogram|na=n=o=gram|6 +Någonstans|Nå=gon=stans|6 +objektet|ob=jek=tet|6 +objektglaset|ob=jekt=gla=set|6 +observationer|ob=ser=va=tio=ner|6 +observationsrummet|ob=ser=va=tions=rum=met|6 +odlingarna|od=ling=ar=na|6 +overall|ove=rall|6 +oväntat|ovän=tat|6 +papperet|pap=pe=ret|6 +partiklarna|par=tik=lar=na|6 +passerar|pas=se=rar|6 +perfekta|per=fek=ta|6 +Petrovavåglängden|Pe=tro=va=våg=läng=den|6 +Planeten|Pla=ne=ten|6 +planeterna|pla=ne=ter=na|6 +platser|plat=ser|6 +plocka|plocka|6 +populationen|po=pu=la=tio=nen|6 +predatorer|pre=da=to=rer|6 +Problemet|Pro=ble=met|6 +producera|pro=du=ce=ra|6 +program|pro=gram|6 +provbehållare|prov=be=hål=la=re|6 +påverkar|på=ver=kar|6 +rapporterar|rap=por=te=rar|6 +relation|re=la=tion|6 +reportern|re=por=tern|6 +resurser|re=sur=ser|6 +riktade|rik=ta=de|6 +riktig|rik=tig|6 +ryggpansar|rygg=pan=sar|6 +Ryssland|Ryss=land|6 +räddar|räd=dar|6 +rösten|rös=ten|6 +samman|sam=man|6 +siktar|sik=tar|6 +situationen|si=tu=a=tio=nen|6 +Skalbaggar|Skal=bag=gar|6 +skalbaggarnas|skal=bag=gar=nas|6 +skenor|ske=nor|6 +skivor|ski=vor|6 +skrivit|skri=vit|6 +slipper|slip=per|6 +slungar|slung=ar|6 +släppa|släp=pa|6 +smetar|sme=tar|6 +smärta|smär=ta|6 +soldaten|sol=da=ten|6 +solida|so=li=da|6 +solsystemet|sol=sys=te=met|6 +speciell|spe=ci=ell|6 +spjärn|spjärn|6 +spänna|spän=na|6 +stenar|ste=nar|6 +stiger|sti=ger|6 +stoppade|stop=pa=de|6 +struktur|struk=tur|6 +studsa|stud=sa|6 +stängs|stängs|6 +stängt|stängt|6 +svagare|sva=ga=re|6 +svartceller|svart=cel=ler|6 +svänga|svänga|6 +särskild|sär=skild|6 +tappade|tap=pa=de|6 +Tillbaka|Till=ba=ka|6 +tillnärmelsevis|till=när=mel=se=vis|6 +tillverkat|till=ver=kat|6 +toppen|top=pen|6 +trasig|tra=sig|6 +triangel|tri=ang=el|6 +Trycket|Tryck=et|6 +tungmetaller|tung=me=tal=ler|6 +tunnan|tun=nan|6 +tupplur|tupp=lur|6 +tusentals|tu=sen=tals|6 +tvivlar|tviv=lar|6 +tvåtusen|två=tu=sen|6 +tänkte|tänk=te|6 +upphöjt|upp=höjt|6 +uppmärksamhet|upp=märk=sam=het|6 +uppmärksamheten|upp=märk=sam=he=ten|6 +uppstå|upp=stå|6 +uppstår|upp=står|6 +utanpå|ut=an=på|6 +utblåsningen|ut=blås=ning=en|6 +utnyttjar|ut=nytt=jar|6 +utrustningen|ut=rust=ning=en|6 +vakuumkammare|va=ku=um=kam=ma=re|6 +veckorna|veck=or=na|6 +verktyget|verk=ty=get|6 +verktygsbältet|verk=tygs=bäl=tet|6 +viktigare|vik=ti=ga=re|6 +villiga|vil=li=ga|6 +vokabulär|vo=ka=bu=lär|6 +värmeenergi|vär=me=ener=gi|6 +väsande|vä=san=de|6 +Äntligen|Änt=li=gen|6 +återgäldar|åter=gäl=dar|6 +återstår|åter=står|6 +ögonbrynen|ögon=bry=nen|6 +öronen|öro=nen|6 +översta|övers=ta|6 +acceptera|ac=cep=te=ra|5 +ackord|ac=kord|5 +ansvaret|an=sva=ret|5 +antalet|an=ta=let|5 +artigt|ar=tigt|5 +avbryta|av=bry=ta|5 +avläsningarna|av=läs=ning=ar=na|5 +bandet|ban=det|5 +barnen|bar=nen|5 +bassängen|bas=säng=en|5 +befunnit|be=fun=nit|5 +begrepp|be=grepp|5 +beklagar|be=kla=gar|5 +betrakta|be=trak=ta|5 +beträffar|be=träf=far|5 +bilderna|bil=der=na|5 +biosfär|bio=sfär|5 +bläddrar|blädd=rar|5 +blåser|blå=ser|5 +bryter|bry=ter|5 +bränner|brän=ner|5 +bränsletank|brän=sle=tank|5 +bygger|byg=ger|5 +byggnad|bygg=nad|5 +bältet|bäl=tet|5 +böcker|böck=er|5 +bönpåse|bön=på=se|5 +centrifugskärmen|cen=tri=fug=skär=men|5 +centrum|cen=trum|5 +cirkeln|cir=keln|5 +delarna|de=lar=na|5 +densamma|den=sam=ma|5 +detaljer|de=tal=jer|5 +detsamma|det=sam=ma|5 +djuren|dju=ren|5 +dragit|dra=git|5 +därmed|där=med|5 +döende|dö=en=de|5 +Dörren|Dör=ren|5 +Easton|Eas=ton|5 +element|ele=ment|5 +Enkelt|En=kelt|5 +enklare|enk=la=re|5 +europeiska|eu=ro=pe=is=ka|5 +exakta|ex=ak=ta|5 +exempel|ex=em=pel|5 +expert|ex=pert|5 +experter|ex=per=ter|5 +fastnar|fast=nar|5 +filtren|filt=ren|5 +fingrarna|fing=rar=na|5 +fjärran|fjär=ran|5 +forsar|for=sar|5 +framgång|fram=gång|5 +framgångsrikt|fram=gångs=rikt|5 +Francisco|Fran=ci=sco|5 +frekvensen|fre=kven=sen|5 +frånsett|från=sett|5 +fulländat|full=än=dat|5 +Fungerar|Fun=ge=rar|5 +fysiskt|fy=siskt|5 +färdigheter|fär=dig=he=ter|5 +färgen|fär=gen|5 +förekommer|fö=re=kom=mer|5 +Förhoppningsvis|För=hopp=nings=vis|5 +förklarat|för=kla=rat|5 +förlorat|för=lo=rat|5 +försiktig|för=sik=tig|5 +Förstås|För=stås|5 +försvinna|för=svin=na|5 +Försökte|För=sök=te|5 +förtjänar|för=tjä=nar|5 +förvånad|för=vå=nad|5 +förvånar|för=vå=nar|5 +förändrats|för=änd=rats|5 +förånga|för=ånga|5 +förångas|för=ång=as|5 +garantera|ga=ran=te=ra|5 +garderoben|gar=de=ro=ben|5 +gissning|giss=ning|5 +grimaserar|gri=ma=se=rar|5 +hamnar|ham=nar|5 +handbojorna|hand=bo=jor=na|5 +handflatan|hand=fla=tan|5 +Hejsan|Hej=san|5 +hemvärld|hem=värld|5 +himlade|him=la=de|5 +hindra|hind=ra|5 +hjälpte|hjälp=te|5 +hjärnan|hjär=nan|5 +Hoppas|Hop=pas|5 +hungrig|hung=rig|5 +hysteriskt|hy=ste=riskt|5 +häftigt|häf=tigt|5 +höljet|höl=jet|5 +infekterade|in=fek=te=ra=de|5 +ingenstans|ing=en=stans|5 +installerade|in=stal=le=ra=de|5 +installerat|in=stal=le=rat|5 +inställningen|in=ställ=ning=en|5 +intelligent|in=tel=li=gent|5 +interagera|in=ter=ag=e=ra|5 +internationell|in=ter=na=tio=nell|5 +intresserad|in=tres=se=rad|5 +isolerade|iso=le=ra=de|5 +jobbigt|job=bigt|5 +Jordiska|Jor=dis=ka|5 +jösses|jös=ses|5 +kanterna|kan=ter=na|5 +kastar|kas=tar|5 +katedern|ka=te=dern|5 +kemikalier|ke=mi=ka=li=er|5 +kemisk|ke=misk|5 +kilogram|ki=lo=gram|5 +kinesisk|ki=ne=sisk|5 +klyftiga|klyf=ti=ga|5 +kolliderar|kol=li=de=rar|5 +kommunikation|kom=mu=ni=ka=tion|5 +komplett|kom=plett|5 +komplexa|kom=plexa|5 +komplext|kom=plext|5 +konferensrum|kon=fe=rens=rum|5 +kopiera|ko=pi=e=ra|5 +korkad|kor=kad|5 +kosmiska|kos=mis=ka|5 +kravlar|krav=lar|5 +kritiska|kri=tis=ka|5 +kroppar|krop=par|5 +kräkas|krä=kas|5 +kvadratmeter|kvad=rat=me=ter|5 +kvalificerad|kva=li=fi=ce=rad|5 +kämpar|käm=par|5 +känsliga|käns=li=ga|5 +laboratorier|la=bo=ra=to=ri=er|5 +laboratorieutrustning|la=bo=ra=to=rie=u=t=rust=ning|5 +lampan|lam=pan|5 +lamporna|lam=por=na|5 +lediga|le=di=ga|5 +ledningar|led=ning=ar|5 +ledsen|led=sen|5 +likadana|li=ka=da=na|5 +liknande|lik=nan=de|5 +livlinorna|liv=li=nor=na|5 +ljuden|lju=den|5 +ljusblixt|ljus=blixt|5 +Ljuset|Lju=set|5 +ljusstark|ljus=stark|5 +lugnar|lug=nar|5 +lukten|luk=ten|5 +lutning|lut=ning|5 +Lycklig|Lyck=lig|5 +lyfter|lyf=ter|5 +länkarna|län=kar=na|5 +långsam|lång=sam|5 +manuell|ma=nu=ell|5 +marginal|mar=gi=nal|5 +maskin|ma=skin|5 +medvetna|med=vet=na|5 +meters|me=ters|5 +mikrober|mik=ro=ber|5 +mikrometer|mik=ro=me=ter|5 +mikroskopiska|mik=ro=sko=pis=ka|5 +miljön|mil=jön|5 +minskat|mins=kat|5 +mittpunkt|mitt=punkt|5 +molekyler|mo=le=ky=ler|5 +morrhår|morr=hår|5 +muskler|musk=ler|5 +mänskligt|mänsk=ligt|5 +märker|mär=ker|5 +Måttbandet|Mått=ban=det|5 +mönster|möns=ter|5 +natten|nat=ten|5 +naturen|na=tu=ren|5 +navigationspanelen|na=vi=ga=tions=pa=ne=len|5 +Newton|Newton|5 +nickar|nick=ar|5 +numera|nu=me=ra|5 +nyheter|ny=he=ter|5 +nämnvärt|nämn=värt|5 +oregelbunden|ore=gel=bun=den|5 +osannolikt|osan=no=likt|5 +parallellt|pa=ral=lellt|5 +partner|part=ner|5 +pengar|peng=ar|5 +Petrovaproblemet|Pe=tro=va=pro=ble=met|5 +pillar|pil=lar|5 +pinnen|pin=nen|5 +planerat|pla=ne=rat|5 +porten|por=ten|5 +pratade|pra=ta=de|5 +predator|pre=da=tor|5 +prickar|prick=ar|5 +proteiner|pro=te=i=ner|5 +protonerna|pro=to=ner=na|5 +provröret|prov=rö=ret|5 +prylarna|pry=lar=na|5 +pålitlig|på=lit=lig|5 +påverka|på=ver=ka|5 +radarn|ra=darn|5 +rapporten|rap=por=ten|5 +reagera|re=a=ge=ra|5 +realtid|re=al=tid|5 +reparera|re=pa=re=ra|5 +respektive|re=spek=ti=ve|5 +riktar|rik=tar|5 +riskerna|ris=ker=na|5 +ropade|ro=pa=de|5 +Rummet|Rum=met|5 +rumstemperatur|rums=tem=pe=ra=tur|5 +ruskade|rus=ka=de|5 +rymddräkter|rymd=dräk=ter|5 +Rymden|Rym=den|5 +saknar|sak=nar|5 +samling|sam=ling|5 +siffrorna|siff=ror=na|5 +självfallet|själv=fal=let|5 +Självfallet|Själv=fal=let|5 +sjätte|sjät=te|5 +skadad|ska=dad|5 +skadat|ska=dat|5 +skalbaggar|skal=bag=gar|5 +skillnaden|skill=na=den|5 +skjuta|skju=ta|5 +Skrovet|Skro=vet|5 +skälver|skäl=ver|5 +skåpet|skå=pet|5 +sköter|skö=ter|5 +slocknar|slock=nar|5 +slumpmässiga|slump=mäs=si=ga|5 +slussdörren|sluss=dör=ren|5 +slussporten|sluss=por=ten|5 +släpar|slä=par|5 +smartare|smar=ta=re|5 +smyger|smy=ger|5 +snurrade|snur=ra=de|5 +snyftar|snyf=tar|5 +solenergi|so=l=ener=gi|5 +solljus|sol=ljus|5 +sovrummet|sov=rum=met|5 +speciellt|spe=ci=ellt|5 +spektralsignatur|spekt=ral=sig=na=tur|5 +Spindrift|Spindrift|5 +spolar|spo=lar|5 +spolarna|spo=lar=na|5 +sprickan|sprick=an|5 +spruta|spru=ta|5 +spännande|spän=nan=de|5 +staterna|sta=ter=na|5 +stirra|stir=ra|5 +storlek|stor=lek|5 +sträckte|sträck=te|5 +ställde|ställ=de|5 +ställning|ställ=ning|5 +stämde|stäm=de|5 +Svaret|Sva=ret|5 +svårare|svå=ra=re|5 +svårighet|svå=rig=het|5 +symbol|sym=bol|5 +synnerhet|syn=ner=het|5 +syssla|syss=la|5 +sänder|sän=der|5 +tabletterna|ta=blet=ter=na|5 +takten|tak=ten|5 +teleskopskärmen|te=le=skop=skär=men|5 +testar|tes=tar|5 +tillbringa|till=bringa|5 +Tillräckligt|Till=räck=ligt|5 +Tillsammans|Till=sam=mans|5 +Tjatte|Tjat=te|5 +totala|to=ta=la|5 +totalt|to=talt|5 +trycks|trycks|5 +tryckte|tryck=te|5 +tunnelväggarna|tun=nel=väg=gar=na|5 +tvinga|tvinga|5 +tystnad|tyst=nad|5 +tänder|tän=der|5 +universum|uni=ver=sum|5 +Uppfattat|Upp=fat=tat|5 +upphetsad|upp=het=sad|5 +upplevt|upp=levt|5 +upprepade|upp=re=pa=de|5 +uppskattar|upp=skat=tar|5 +ursäkt|ur=säkt|5 +utomjordingarna|ut=om=jor=ding=ar=na|5 +uttråkad|ut=trå=kad|5 +utvecklat|ut=veck=lat|5 +vakuumkammaren|va=ku=um=kam=ma=ren|5 +vardera|var=de=ra|5 +ventiler|ven=ti=ler|5 +verkstad|verk=stad|5 +verktygen|verk=ty=gen|5 +viktig|vik=tig|5 +vinkade|vin=ka=de|5 +Vinkelanomali|Vin=ke=la=no=ma=li|5 +volymen|vo=ly=men|5 +Väggarna|Väg=gar=na|5 +Välkommen|Väl=kom=men|5 +väteatomer|vä=te=ato=mer|5 +växter|väx=ter|5 +våglängden|våg=läng=den|5 +walkie|wal=kie|5 +Zeeland|Zee=land|5 +älskar|äls=kar|5 +öppning|öpp=ning|5 +överens|över=ens|5 +överenskommelse|över=ens=kom=mel=se|5 +aktivera|ak=ti=ve=ra|4 +aktivt|ak=tivt|4 +allvarlig|all=var=lig|4 +allvarligt|all=var=ligt|4 +alternativen|al=ter=na=ti=ven|4 +amerikan|ame=ri=kan|4 +analysen|ana=ly=sen|4 +analysera|ana=ly=se=ra|4 +analyserar|ana=ly=se=rar|4 +anstränga|an=stränga|4 +antagligen|an=tag=li=gen|4 +Antagligen|An=tag=li=gen|4 +anteckning|an=teck=ning|4 +användes|an=vän=des|4 +används|an=vänds|4 +apokalypsen|apo=ka=lyp=sen|4 +arbetat|ar=be=tat|4 +artificiella|ar=ti=fi=ci=el=la|4 +astrofagdrivet|ast=ro=fag=dri=vet|4 +astrofagen|ast=ro=fa=gen|4 +Astrofagernas|Ast=ro=fa=ger=nas|4 +astronomisk|ast=ro=no=misk|4 +avsedda|av=sed=da|4 +avslutad|av=slu=tad|4 +bakgrunden|bak=grun=den|4 +bakterier|bak=te=ri=er|4 +befogenheter|be=fo=gen=he=ter|4 +befälhavare|be=fäl=ha=va=re|4 +begripa|be=gri=pa|4 +begränsad|be=grän=sad|4 +begränsade|be=grän=sa=de|4 +behövdes|be=höv=des|4 +bekräftat|be=kräf=tat|4 +bekvämt|be=kvämt|4 +berodde|be=rod=de|4 +beroende|be=ro=en=de|4 +berätta|be=rät=ta|4 +besättningsmedlem|be=sätt=nings=med=lem|4 +biblioteket|bib=li=o=te=ket|4 +bildar|bil=dar|4 +Bilden|Bil=den|4 +bilder|bil=der|4 +biologiska|bio=lo=gis=ka|4 +biologiskt|bio=lo=giskt|4 +blinkande|blin=kan=de|4 +blottar|blot=tar|4 +bokstäver|bok=stä=ver|4 +borren|bor=ren|4 +brantare|bran=ta=re|4 +brittiska|brit=tis=ka|4 +bränna|brän=na|4 +bränslekapseln|brän=s=le=kap=seln|4 +bråkdels|bråk=dels|4 +bubblan|bubb=lan|4 +burrito|bur=ri=to|4 +civilisation|ci=vi=li=sa=tion|4 +cockpiten|cock=pi=ten|4 +cykeln|cy=keln|4 +cylinderns|cy=lin=derns|4 +Cáceres|Cá=ce=res|4 +dagliga|dag=li=ga|4 +densitet|den=si=tet|4 +detaljerad|de=tal=je=rad|4 +digitalt|di=gi=talt|4 +Dimitrijs|Di=mit=rijs|4 +diverse|di=ver=se|4 +dollar|dol=lar|4 +dricka|dricka|4 +droppe|drop=pe|4 +dröjer|drö=jer|4 +dämpat|däm=pat|4 +dödande|dö=dan=de|4 +dörrar|dör=rar|4 +dörrarna|dör=rar=na|4 +effekten|ef=fek=ten|4 +effekter|ef=fek=ter|4 +effektiv|ef=fek=tiv|4 +eleganta|ele=gan=ta|4 +Enbart|En=bart|4 +encelliga|encel=li=ga|4 +eridiernas|eri=di=er=nas|4 +exploderar|ex=plo=de=rar|4 +explosion|ex=plo=sion|4 +explosionen|ex=plo=sio=nen|4 +faktum|fak=tum|4 +Faktum|Fak=tum|4 +farkost|far=kost|4 +fickan|fick=an|4 +filtret|filt=ret|4 +flagga|flag=ga|4 +flämtade|fläm=ta=de|4 +Fnatte|Fnat=te|4 +fokuserar|fo=ku=se=rar|4 +formar|for=mar|4 +format|for=mat|4 +Forrester|For=res=ter|4 +fruktansvärt|fruk=tans=värt|4 +främst|främst|4 +fullbordad|full=bor=dad|4 +funderat|fun=de=rat|4 +fyrtiofem|fyr=tio=fem|4 +färdiga|fär=di=ga|4 +föddes|föd=des|4 +fönstren|fönst=ren|4 +förankrad|för=ank=rad|4 +förbereda|för=be=re=da|4 +förbundna|för=bund=na|4 +fördel|för=del|4 +förfader|för=fa=der|4 +förmågan|för=må=gan|4 +förstöra|för=stö=ra|4 +förstörd|för=störd|4 +försvann|för=svann|4 +förteckning|för=teck=ning|4 +förutsätter|för=ut=sät=ter|4 +förvirrad|för=vir=rad|4 +förväg|för=väg|4 +förvånande|för=vå=nan=de|4 +förvåning|för=vå=ning|4 +gapade|ga=pa=de|4 +genetiska|ge=ne=tis=ka|4 +genever|ge=ne=ver|4 +genomfört|ge=nom=fört|4 +glödlampa|glöd=lam=pa|4 +gnuggar|gnug=gar|4 +gnutta|gnut=ta|4 +Gravitation|Gra=vi=ta=tion|4 +grejor|gre=jor|4 +grupper|grup=per|4 +halsband|hals=band|4 +halsen|hal=sen|4 +hammaren|ham=ma=ren|4 +handskar|hands=kar|4 +handskarna|hands=kar=na|4 +hangarfartyget|han=gar=far=ty=get|4 +hangarfartygets|han=gar=far=ty=gets|4 +HASTIGHET|HAS=TIG=HET|4 +Hastigheten|Has=tig=he=ten|4 +hejdade|hej=da=de|4 +helikopter|he=li=kop=ter|4 +helikoptern|he=li=kop=tern|4 +hellre|hell=re|4 +helsike|hel=si=ke|4 +helvetet|hel=ve=tet|4 +hemskt|hemskt|4 +Hennes|Hen=nes|4 +heroin|he=ro=in|4 +hexagonerna|hexa=go=ner=na|4 +himlen|him=len|4 +hjulet|hju=let|4 +hjälpt|hjälpt|4 +hjälte|hjäl=te|4 +hoppades|hop=pa=des|4 +hopplöst|hopp=löst|4 +hundraprocentigt|hun=dra=pro=cen=tigt|4 +hundratusen|hun=dra=tu=sen|4 +hunnit|hun=nit|4 +huvudskärmen|hu=vud=skär=men|4 +hälfterna|hälf=ter=na|4 +hängde|häng=de|4 +hållning|håll=ning|4 +iaktta|iakt=ta|4 +ifrågasätta|ifrå=ga=sät=ta|4 +imponerad|im=po=ne=rad|4 +inbyggda|in=bygg=da|4 +infektera|in=fek=te=ra|4 +Information|In=for=ma=tion|4 +infraröd|in=fra=röd|4 +ingenjörer|in=gen=jö=rer|4 +ingående|in=gå=en=de|4 +inklusive|in=klu=si=ve|4 +innebar|in=ne=bar|4 +innehålla|in=ne=hål=la|4 +innehöll|in=ne=höll|4 +insamlingsenheten|in=sam=lings=en=he=ten|4 +installerar|in=stal=le=rar|4 +intervall|in=ter=vall|4 +intresserar|in=tres=se=rar|4 +inträffa|in=träf=fa|4 +inverkan|in=ver=kan|4 +irriterade|ir=ri=te=ra=de|4 +jetplan|jet=plan|4 +jobbat|job=bat|4 +kalkylblad|kal=kyl=blad|4 +kapsel|kap=sel|4 +kapseln|kap=seln|4 +kapslar|kaps=lar|4 +kapslarna|kaps=lar=na|4 +kapten|kap=ten|4 +karantänzonen|ka=ran=tänzo=nen|4 +kastas|kas=tas|4 +kilometers|ki=lo=me=ters|4 +klassrum|klass=rum|4 +klickande|klick=an=de|4 +Klockan|Klock=an|4 +klämma|kläm=ma|4 +klämman|kläm=man|4 +Knatte|Knat=te|4 +koldioxiden|kol=di=ox=i=den|4 +kollegor|kol=le=gor|4 +kollidera|kol=li=de=ra|4 +kolsvart|kol=svart|4 +komaresistent|ko=ma=re=sis=tent|4 +kombination|kom=bi=na=tion|4 +kommendör|kom=men=dör|4 +koncentrera|kon=cen=tre=ra|4 +koncentrerar|kon=cen=tre=rar|4 +konstiga|kons=ti=ga|4 +konstruerade|kon=stru=e=ra=de|4 +konstruerar|kon=stru=e=rar|4 +konsumerar|kon=su=me=rar|4 +kontrollen|kon=trol=len|4 +kontrollerade|kon=trol=le=ra=de|4 +kontrollrum|kon=troll=rum|4 +kontrollskärmen|kon=troll=skär=men|4 +koppla|kopp=la|4 +korridoren|kor=ri=do=ren|4 +krafter|kraf=ter|4 +krafterna|kraf=ter=na|4 +kragen|kra=gen|4 +kramar|kra=mar|4 +kretsar|kret=sar|4 +kroppsspråk|kropps=språk|4 +krävdes|kräv=des|4 +kunskaper|kun=ska=per|4 +kursen|kur=sen|4 +kusligt|kus=ligt|4 +kvadrat|kvad=rat|4 +kvinnor|kvin=nor|4 +kväveresistenta|kvä=ve=re=sis=ten=ta|4 +laboratoriet|la=bo=ra=to=ri=et|4 +landade|lan=da=de|4 +ledning|led=ning|4 +ledtråd|led=tråd|4 +Likadant|Li=ka=dant|4 +linserna|lin=ser=na|4 +livscykel|livs=cy=kel|4 +livsmedel|livs=me=del|4 +lossad|los=sad|4 +Luftslussen|Luft=slus=sen|4 +lysdioder|lys=dio=der|4 +lyssna|lyss=na|4 +lyssnar|lyss=nar|4 +läckor|läck=or|4 +lägenhet|lä=gen=het|4 +längdaxel|längd=ax=el|4 +läskigt|läs=kigt|4 +långsamma|lång=sam=ma|4 +magiska|ma=gis=ka|4 +magneter|mag=ne=ter|4 +mannen|man=nen|4 +manuellt|ma=nu=ellt|4 +markörer|mar=kö=rer|4 +Martin|Martin|4 +medicinsk|me=di=cinsk|4 +medicinskt|me=di=cinskt|4 +medvetande|med=ve=tan=de|4 +medvetandet|med=ve=tan=det|4 +mejlet|mej=let|4 +mejseln|mej=seln|4 +mekaniska|me=ka=nis=ka|4 +Meknikov|Mek=ni=kov|4 +mellanrum|mel=lan=rum|4 +mening|me=ning|4 +metanet|me=ta=net|4 +Michael|Mi=chael|4 +migrerar|mi=gre=rar|4 +Miljarder|Mil=jar=der|4 +milligram|mil=li=gram|4 +milliliter|mil=li=li=ter|4 +minimera|mi=ni=me=ra|4 +minimum|mi=ni=mum|4 +misstänkte|miss=tänk=te|4 +missväxt|miss=växt|4 +mojängen|mo=jäng=en|4 +monterad|mon=te=rad|4 +morgonen|mor=go=nen|4 +muskulösa|mus=ku=lö=sa|4 +muttrar|mutt=rar|4 +mysterium|mys=te=ri=um|4 +mystiska|mys=tis=ka|4 +Människors|Män=ni=skors|4 +måltiden|mål=ti=den|4 +måttband|mått=band|4 +möjligen|möj=li=gen|4 +nederst|ne=derst|4 +nyfiken|ny=fi=ken|4 +nyheten|ny=he=ten|4 +närmade|när=ma=de|4 +nätverk|nät=verk|4 +observationsstatus|ob=ser=va=tions=sta=tus|4 +observerat|ob=ser=ve=rat|4 +odlingstankar|od=lingstan=kar|4 +oförändrad|oför=änd=rad|4 +Olesija|Ole=si=ja|4 +olycka|olycka|4 +olyckan|olyck=an|4 +omständigheter|om=stän=dig=he=ter|4 +omvandla|om=vand=la|4 +ordbok|ord=bok|4 +ordboken|ord=bo=ken|4 +ordnar|ord=nar|4 +orimligt|orim=ligt|4 +orsakar|or=sa=kar|4 +ovanför|ovan=för|4 +ovanligt|ovan=ligt|4 +ovanpå|ovan=på|4 +Panelen|Pa=ne=len|4 +papperen|pap=pe=ren|4 +pappersark|pap=pers=ark|4 +parallelltrapets|pa=ral=lell=tra=pets|4 +personliga|per=son=li=ga|4 +personligen|per=son=li=gen|4 +Petrova|Pe=tro=va|4 +placera|pla=ce=ra|4 +planetens|pla=ne=tens|4 +plockat|plock=at|4 +plågsamt|plåg=samt|4 +plötsliga|plöts=li=ga|4 +portabla|por=tab=la|4 +poängen|po=äng=en|4 +Praktiskt|Prak=tiskt|4 +pratat|pra=tat|4 +predatorn|pre=da=torn|4 +primater|pri=ma=ter|4 +primär|pri=mär|4 +process|pro=cess|4 +producerar|pro=du=ce=rar|4 +programmet|pro=gram=met|4 +pytteliten|pyt=te=li=ten|4 +pågick|på=gick|4 +rektangulär|rek=tan=gu=lär|4 +relativa|re=la=ti=va|4 +Reportern|Re=por=tern|4 +reserv|re=serv|4 +Resten|Res=ten|4 +resultaten|re=sul=ta=ten|4 +ribborna|rib=bor=na|4 +riktiga|rik=ti=ga|4 +riktningar|rikt=ning=ar|4 +rimlig|rim=lig|4 +ringer|ring=er|4 +Roboten|Ro=bo=ten|4 +Roskosmos|Roskos=mos|4 +roterande|ro=te=ran=de|4 +rubriken|ru=bri=ken|4 +rullade|rul=la=de|4 +rummen|rum=men|4 +ryggade|ryg=ga=de|4 +rymdstationen|rymd=sta=tio=nen|4 +Ryssarna|Rys=sar=na|4 +räckhåll|räck=håll|4 +räddning|rädd=ning|4 +saboterar|sa=bo=te=rar|4 +Sahara|Sa=ha=ra|4 +Saharaöknen|Sa=ha=ra=ök=nen|4 +saknade|sak=na=de|4 +samlade|sam=la=de|4 +sammansatt|sam=man=satt|4 +samtalet|sam=ta=let|4 +Samtidigt|Sam=ti=digt|4 +sekundkvadrat|se=kund=kvad=rat|4 +sekvensen|se=kven=sen|4 +sidorna|si=dor=na|4 +siffran|siff=ran|4 +silvertejp|sil=ver=tejp|4 +situation|si=tu=a=tion|4 +sjutusen|sju=tu=sen|4 +skadade|ska=da=de|4 +skalan|ska=lan|4 +skallen|skal=len|4 +skapar|ska=par|4 +skilda|skil=da|4 +skilja|skil=ja|4 +skogen|sko=gen|4 +skrovrobot|skrov=ro=bot|4 +skruva|skru=va|4 +skruven|skru=ven|4 +skydda|skyd=da|4 +skyddade|skyd=da=de|4 +skynda|skyn=da|4 +skyndar|skyn=dar|4 +slocknade|slock=na=de|4 +slutade|slu=ta=de|4 +slutat|slu=tat|4 +släkte|släk=te|4 +smakar|sma=kar|4 +Smärtan|Smär=tan|4 +Snarare|Sna=ra=re|4 +Soldaten|Sol=da=ten|4 +Somliga|Som=li=ga|4 +sovsal|sov=sal|4 +specialist|spe=ci=a=list|4 +specifikt|spe=ci=fikt|4 +spekulera|spe=ku=le=ra|4 +Spencer|Spencer|4 +spindel|spin=del|4 +splitter|split=ter|4 +sprider|spri=der|4 +sprutar|spru=tar|4 +spränga|spränga|4 +stannade|stan=na=de|4 +starta|star=ta|4 +stegpinne|steg=pin=ne|4 +steriliserar|ste=ri=li=se=rar|4 +stjärnsystem|stjärn=sys=tem|4 +stoppa|stop=pa|4 +stoppur|stopp=ur|4 +streck|streck|4 +Strunt|Strunt|4 +strålningen|strål=ning=en|4 +strömbrytare|ström=bry=ta=re|4 +studerar|stu=de=rar|4 +studie|stu=die|4 +styrbords|styr=bords|4 +ställa|stäl=la|4 +Stämmer|Stäm=mer|4 +svajar|sva=jar|4 +svälta|sväl=ta|4 +Svårare|Svå=ra=re|4 +syftet|syf=tet|4 +synlig|syn=lig|4 +sällsynta|säll=syn=ta|4 +sängar|säng=ar|4 +Såvitt|Så=vitt|4 +sörjan|sör=jan|4 +tabletter|ta=blet=ter|4 +talkie|tal=kie|4 +taumöbabajs|tau=mö=ba=bajs|4 +taumöbalarmet|tau=mö=ba=larm=et|4 +tejpade|tej=pa=de|4 +tekniken|tek=ni=ken|4 +teknologi|tek=no=lo=gi|4 +termisk|ter=misk|4 +Texten|Tex=ten|4 +tillbringat|till=bring=at|4 +tillräcklig|till=räck=lig|4 +tiotusentals|tio=tu=sen=tals|4 +toaletten|toa=let=ten|4 +trassla|trass=la|4 +Trettio|Tret=tio|4 +Trevligt|Trev=ligt|4 +triangeln|tri=ang=eln|4 +trummade|trum=ma=de|4 +träffat|träf=fat|4 +tränga|tränga|4 +träning|trä=ning|4 +trådar|trå=dar|4 +tråkigt|trå=kigt|4 +tummen|tum=men|4 +turbin|tur=bin|4 +tvingar|tving=ar|4 +tyvärr|ty=värr|4 +tänkas|tän=kas|4 +tänkbart|tänk=bart|4 +undrade|und=ra=de|4 +undvika|und=vi=ka|4 +Ungarna|Ung=ar=na|4 +uniform|uni=form|4 +uniformer|uni=for=mer|4 +uppdrag|upp=drag|4 +uppfinna|upp=fin=na|4 +upphöjda|upp=höj=da|4 +uppmärksam|upp=märk=sam|4 +upprepas|upp=re=pas|4 +Uppriktigt|Upp=rik=tigt|4 +upprörd|upp=rörd|4 +uppsatsen|upp=sat=sen|4 +upptagen|upp=ta=gen|4 +utforska|ut=fors=ka|4 +utplåning|ut=plå=ning|4 +utsatt|ut=satt|4 +utspridda|ut=sprid=da|4 +utsättas|ut=sät=tas|4 +uttrycka|ut=trycka|4 +utvecklades|ut=veck=la=des|4 +Utöver|Ut=över|4 +vacker|vack=er|4 +Varifrån|Var=i=från|4 +varning|var=ning|4 +ventil|ven=til|4 +ventilerna|ven=ti=ler=na|4 +verktygsbälte|verk=tygs=bäl=te|4 +vertikala|ver=ti=ka=la|4 +vetenskapligt|ve=ten=skap=ligt|4 +vickade|vick=a=de|4 +vidgas|vid=gas|4 +viktigast|vik=ti=gast|4 +vinkla|vink=la|4 +Vinschen|Vin=schen|4 +visarna|vi=sar=na|4 +vägrade|väg=ra=de|4 +väller|väl=ler|4 +värdelös|vär=de=lös|4 +världar|värl=dar|4 +värmer|vär=mer|4 +väskan|väs=kan|4 +växande|väx=an=de|4 +whisky|whis=ky|4 +xenonitbehållare|xe=no=nit=be=hål=la=re|4 +ändrar|änd=rar|4 +åratal|åra=tal|4 +återgå|åter=gå|4 +överlevare|över=le=va=re|4 +Överst|Överst|4 +övervaka|över=va=ka|4 +absorberas|ab=sor=be=ras|3 +absurd|ab=surd|3 +absurda|ab=sur=da|3 +aggressiv|ag=gres=siv|3 +akademiska|aka=de=mis=ka|3 +aktiva|ak=ti=va|3 +aktiverade|ak=ti=ve=ra=de|3 +aktiveras|ak=ti=ve=ras|3 +Aldrig|Ald=rig|3 +Alltid|All=tid|3 +Alternativ|Al=ter=na=tiv|3 +amerikanerna|ame=ri=ka=ner=na|3 +analog|ana=log|3 +anläggning|an=lägg=ning|3 +anmält|an=mält|3 +annanstans|an=nan=stans|3 +anpassa|an=pas=sa|3 +anrikade|an=ri=ka=de|3 +anstränger|an=sträng=er|3 +antecknar|an=teck=nar|3 +antennen|an=ten=nen|3 +antibiotika|an=ti=bi=o=ti=ka|3 +antydan|an=ty=dan|3 +användbar|an=vänd=bar|3 +Använder|An=vän=der|3 +Apparat|Ap=pa=rat|3 +Apropå|Apro=på|3 +arbetet|ar=be=tet|3 +arbetsbänken|ar=bets=bän=ken|3 +artificiell|ar=ti=fi=ci=ell|3 +astrofagens|ast=ro=fa=gens|3 +astrofagpopulationen|ast=ro=fag=po=pu=la=tio=nen|3 +astrofagprover|ast=ro=fag=pro=ver|3 +astronaut|ast=ro=naut|3 +astronomer|ast=ro=no=mer|3 +astronomiska|ast=ro=no=mis=ka|3 +atmosfärs|at=mo=sfärs|3 +atmosfärtryck|at=mo=sfär=tryck|3 +Avbryt|Av=bryt|3 +avbröt|av=bröt|3 +avlägsna|av=lägs=na|3 +avslutar|av=slu=tar|3 +avstängda|av=stäng=da|3 +Avståndet|Av=stån=det|3 +avviker|av=vi=ker|3 +avvisas|av=vi=sas|3 +babords|ba=bords|3 +backar|back=ar|3 +badrum|bad=rum|3 +baktill|bak=till|3 +bandage|ban=da=ge|3 +batteri|bat=te=ri|3 +Beatles|Be=at=les|3 +bedöma|be=dö=ma|3 +bedömer|be=dö=mer|3 +behagligt|be=hag=ligt|3 +behandskade|be=hands=ka=de|3 +behåller|be=hål=ler|3 +behållit|be=hål=lit|3 +Behöver|Be=hö=ver|3 +belysning|be=lys=ning|3 +berättade|be=rät=ta=de|3 +beskriva|be=skri=va|3 +beskrivning|be=skriv=ning|3 +bestod|be=stod|3 +bestämd|be=stämd|3 +besväret|be=svä=ret|3 +besynnerligt|be=syn=ner=ligt|3 +besättningsavdelningen|be=sätt=nings=av=del=ning=en|3 +besättningsmedlemmarna|be=sätt=nings=med=lem=mar=na|3 +besöka|be=sö=ka|3 +betydande|be=ty=dan=de|3 +betydde|be=tyd=de|3 +betydelse|be=ty=del=se|3 +Betydligt|Be=tyd=ligt|3 +bevisa|be=vi=sa|3 +bevisar|be=vi=sar|3 +bisarrt|bi=sarrt|3 +Blandar|Blan=dar|3 +blodet|blo=det|3 +blänkande|blän=kan=de|3 +Bollen|Bol=len|3 +bollens|bol=lens|3 +bombarderas|bom=bar=de=ras|3 +bordets|bor=dets|3 +borrhuvudet|borr=hu=vu=det|3 +bortom|bort=om|3 +brinner|brin=ner|3 +brukade|bru=ka=de|3 +brydde|bryd=de|3 +brännskador|bränn=ska=dor|3 +Bränsle|Brän=sle|3 +bränsleenheter|brän=s=le=en=he=ter|3 +bränsleförrådet|brän=s=le=för=rå=det|3 +Bränslet|Brän=slet|3 +bränsletanken|brän=sle=tan=ken|3 +bulten|bul=ten|3 +byggnaden|bygg=na=den|3 +byggts|byggts|3 +bänkar|bän=kar|3 +bönpåsar|bön=på=sar|3 +Canton|Can=ton|3 +cellen|cel=len|3 +centrifugering|cen=tri=fu=ge=ring|3 +centripetalkraften|cen=tri=pe=tal=kraf=ten|3 +cirklar|cirk=lar|3 +citronsyracykeln|ci=tron=sy=ra=cy=keln|3 +cylindrarna|cy=lind=rar=na|3 +Dagens|Da=gens|3 +dagens|da=gens|3 +dammar|dam=mar|3 +datorskärmen|da=tor=skär=men|3 +decimeters|de=ci=me=ters|3 +delvis|del=vis|3 +desperata|de=spe=ra=ta|3 +dialogrutor|dia=lo=gru=tor|3 +diamant|dia=mant|3 +digital|di=gi=tal|3 +diskutera|dis=ku=te=ra|3 +djungeln|djung=eln|3 +djupare|dju=pa=re|3 +domare|do=ma=re|3 +dominerande|do=mi=ne=ran=de|3 +drabbas|drab=bas|3 +Driver|Dri=ver|3 +drivkraft|driv=kraft|3 +droppar|drop=par|3 +dräktens|dräk=tens|3 +dubbelkollar|dub=bel=kol=lar|3 +dubbla|dubb=la|3 +dubblas|dubb=las|3 +dunkar|dun=kar|3 +däcket|däck=et|3 +däremot|där=emot|3 +efteråt|ef=ter=åt|3 +egenskaper|egen=ska=per|3 +Egentligen|Egent=li=gen|3 +ekologi|eko=lo=gi|3 +elementen|ele=men=ten|3 +enerverande|ener=ve=ran=de|3 +ensamhet|en=sam=het|3 +enskild|en=skild|3 +Eridiskt|Eri=diskt|3 +ersatt|er=satt|3 +etiketterade|eti=ket=te=ra=de|3 +Evolutionen|Evo=lu=tio=nen|3 +evolutionära|evo=lu=tio=nä=ra|3 +existerar|ex=i=ste=rar|3 +experimenten|ex=pe=ri=men=ten|3 +exponentiell|ex=po=nen=ti=ell|3 +exponentiellt|ex=po=nen=ti=ellt|3 +extrem|ex=trem|3 +fallen|fal=len|3 +fanken|fan=ken|3 +fartyg|far=tyg|3 +fastskruvade|fast=skru=va=de|3 +felaktiga|fel=ak=ti=ga|3 +felsteg|fel=steg|3 +fiende|fi=en=de|3 +filten|fil=ten|3 +finner|fin=ner|3 +fjärrkontroll|fjärr=kon=troll|3 +fläckar|fläck=ar|3 +fläckigt|fläck=igt|3 +fnyste|fnys=te|3 +fokuserad|fo=ku=se=rad|3 +forskargrupp|fors=kar=grupp|3 +forskningsrapporter|forsk=nings=rap=por=ter|3 +fotoner|fo=to=ner|3 +framme|fram=me|3 +framtid|fram=tid|3 +fruktansvärd|fruk=tans=värd|3 +frusen|fru=sen|3 +främre|främ=re|3 +fräsen|frä=sen|3 +frånvarande|från=va=ran=de|3 +fumlar|fum=lar|3 +funkade|fun=ka=de|3 +funkar|fun=kar|3 +funktionell|funk=tio=nell|3 +funnits|fun=nits|3 +fyratusen|fy=ra=tu=sen|3 +fyrkantigt|fyr=kan=tigt|3 +fängelse|fäng=el=se|3 +fängelsecell|fäng=el=se=cell|3 +färder|fär=der|3 +färdigt|fär=digt|3 +färgerna|fär=ger=na|3 +fästad|fäs=tad|3 +förberedde|för=be=red=de|3 +förbinder|för=bin=der|3 +förbluffande|för=bluf=fan=de|3 +fördömda|för=döm=da|3 +fördömt|för=dömt|3 +föredrar|fö=re=drar|3 +föremålen|fö=re=må=len|3 +föreställde|fö=re=ställ=de|3 +företag|fö=re=tag|3 +förhoppningsvis|för=hopp=nings=vis|3 +Förklara|För=kla=ra|3 +förlorar|för=lo=rar|3 +förpackningen|för=pack=ning=en|3 +förslag|för=slag|3 +Första|Förs=ta|3 +förstoring|för=sto=ring|3 +försäkrar|för=säk=rar|3 +försökt|för=sökt|3 +Förtjust|För=tjust|3 +förtjusta|för=tju=sta|3 +förtvivlat|för=tviv=lat|3 +förvarar|för=va=rar|3 +förvarning|för=var=ning|3 +förvirrande|för=vir=ran=de|3 +förändrade|för=änd=ra=de|3 +förökar|för=ö=kar|3 +gemensam|ge=men=sam|3 +generationen|ge=ne=ra=tio=nen|3 +generationer|ge=ne=ra=tio=ner|3 +generera|ge=ne=re=ra|3 +genomförde|ge=nom=för=de|3 +genomsnitt|ge=nom=snitt|3 +genomsnittliga|ge=nom=snitt=li=ga|3 +Genève|Genève|3 +gester|ges=ter|3 +gigantisk|gi=gan=tisk|3 +gissningar|giss=ning=ar|3 +gissningsvis|giss=nings=vis|3 +gjordes|gjor=des|3 +Glaset|Gla=set|3 +glasögonen|glas=ögo=nen|3 +global|glo=bal|3 +globala|glo=ba=la|3 +glöder|glö=der|3 +glömma|glöm=ma|3 +grannar|gran=nar|3 +granskar|grans=kar|3 +Grejen|Gre=jen|3 +grejen|gre=jen|3 +grekiska|gre=kis=ka|3 +grundligt|grund=ligt|3 +gummit|gum=mit|3 +gällande|gäl=lan=de|3 +gänget|gäng=et|3 +halsbandet|hals=ban=det|3 +Halvvägs|Halv=vägs|3 +hammare|ham=ma=re|3 +hamnat|ham=nat|3 +handfull|hand=full|3 +handla|hand=la|3 +handleden|hand=le=den|3 +hangardäcket|han=gar=däck=et|3 +hanterar|han=te=rar|3 +harmlöst|harm=löst|3 +hasade|ha=sa=de|3 +hastigt|has=tigt|3 +Hawaii|Ha=waii|3 +Helikoptern|He=li=kop=tern|3 +helium|he=li=um|3 +heltäckande|hel=täck=an=de|3 +hemfärden|hem=fär=den|3 +hemresan|hem=re=san|3 +herregud|her=re=gud|3 +hetare|he=ta=re|3 +Hettan|Het=tan|3 +hettar|het=tar|3 +hexagoner|hexa=go=ner|3 +hexagonväggen|hexa=gon=väg=gen|3 +hindrar|hind=rar|3 +Hittills|Hit=tills|3 +hjärnor|hjär=nor|3 +hoppats|hop=pats|3 +Hopplöst|Hopp=löst|3 +Hoppsan|Hopp=san|3 +huvudmotorerna|hu=vud=mo=to=rer=na|3 +huvudsak|hu=vud=sak|3 +huvudände|hu=vud=än=de|3 +hylsnyckeln|hyls=nyck=eln|3 +hällde|häll=de|3 +häller|häl=ler|3 +hänsyn|hän=syn|3 +Härifrån|Här=i=från|3 +hårddiskar|hård=dis=kar|3 +höfterna|höf=ter=na|3 +högljudd|hög=ljudd|3 +högstadielärare|hög=sta=die=lä=ra=re|3 +högupplöst|hö=gupp=löst|3 +höjden|höj=den|3 +hördes|hör=des|3 +ibland|ibland|3 +idealiska|ide=a=lis=ka|3 +identisk|iden=tisk|3 +inblandade|in=blan=da=de|3 +infekterades|in=fek=te=ra=des|3 +infekterats|in=fek=te=rats|3 +ingången|in=gång=en|3 +innehåll|in=ne=håll|3 +insekt|in=sekt|3 +insisterade|in=si=ste=ra=de|3 +installera|in=stal=le=ra|3 +instruktioner|in=struk=tio=ner|3 +instämmer|in=stäm=mer|3 +internationella|in=ter=na=tio=nel=la|3 +intressanta|in=tres=san=ta|3 +intresse|in=tres=se|3 +invändningar|in=vänd=ning=ar|3 +irriterar|ir=ri=te=rar|3 +isolering|iso=le=ring|3 +japanska|ja=pans=ka|3 +Justera|Ju=ste=ra|3 +justerade|ju=ste=ra=de|3 +jämför|jäm=för|3 +kaffet|kaf=fet|3 +kalkylbladet|kal=kyl=bla=det|3 +kamerabild|ka=me=ra=bild|3 +kanadensare|ka=na=den=sa=re|3 +kandidaterna|kan=di=da=ter=na|3 +kanter|kan=ter|3 +karriär|kar=ri=är|3 +kartan|kar=tan|3 +Kassera|Kas=se=ra|3 +katalog|ka=ta=log|3 +katalogiserar|ka=ta=lo=gi=se=rar|3 +kateter|ka=te=ter|3 +Kedjan|Ked=jan|3 +kemiska|ke=mis=ka|3 +kinesiskt|ki=ne=siskt|3 +klagar|kla=gar|3 +klassen|klas=sen|3 +klimatolog|kli=ma=to=log|3 +klistret|klist=ret|3 +kloten|klo=ten|3 +klyftigare|klyf=ti=ga=re|3 +klämmer|kläm=mer|3 +knäpper|knäp=per|3 +koldioxidens|kol=di=ox=i=dens|3 +kolmörkt|kol=mörkt|3 +komaresistens|ko=ma=re=sis=tens|3 +komaresistenta|ko=ma=re=sis=ten=ta|3 +kompatibla|kom=pa=tib=la|3 +komponenterna|kom=po=nen=ter=na|3 +konstanta|kon=stan=ta|3 +konstruerats|kon=stru=e=rats|3 +konstruktion|kon=struk=tion|3 +kontor|kon=tor|3 +KONTROLL|KON=TROLL|3 +Kontrollrummet|Kon=troll=rum=met|3 +kontrollspaken|kon=troll=spa=ken|3 +kopior|ko=pi=or|3 +kopplar|kopp=lar|3 +kopplat|kopp=lat|3 +kroppens|krop=pens|3 +kroppstemperatur|kropps=tem=pe=ra=tur|3 +kränger|kräng=er|3 +krävde|kräv=de|3 +kubikroten|ku=bik=ro=ten|3 +kulorna|ku=lor=na|3 +kultur|kul=tur|3 +kunskap|kun=skap|3 +kvadratsekund|kvad=rat=se=kund|3 +kvarts|kvarts|3 +kvinnan|kvin=nan|3 +kännas|kän=nas|3 +känslan|käns=lan|3 +kärnreaktor|kärn=re=ak=tor|3 +labbtaket|labb=ta=ket|3 +labbutrustningen|lab=b=ut=rust=ning=en|3 +landar|lan=dar|3 +laterala|la=te=ra=la|3 +ledande|le=dan=de|3 +ledigt|le=digt|3 +lektionen|lek=tio=nen|3 +lindar|lin=dar|3 +lindrigt|lind=rigt|3 +livsformen|livs=for=men|3 +ljusare|lju=sa=re|3 +ljusasken|lju=sas=ken|3 +ljusenergi|ljusener=gi|3 +ljusfläck|ljus=fläck|3 +ljusglimt|ljus=glimt|3 +ljusvåglängder|ljus=våg=läng=der|3 +luftflöde|luft=flö=de|3 +luftslussdörren|luft=sluss=dör=ren|3 +lufttryck|luft=tryck|3 +lufttätt|luft=tätt|3 +Lukten|Luk=ten|3 +lungor|lung=or|3 +lungorna|lung=or=na|3 +Lungorna|Lung=or=na|3 +Luther|Lut=her|3 +lysande|ly=san=de|3 +lysdiod|lys=diod|3 +lyssnade|lyss=na=de|3 +lämplig|lämp=lig|3 +länkade|län=ka=de|3 +lättnad|lätt=nad|3 +låtsas|låt=sas|3 +lömskt|lömskt|3 +lösningar|lös=ning=ar|3 +magneterna|mag=ne=ter=na|3 +MANUELL|MA=NU=ELL|3 +manövern|ma=nö=vern|3 +Marissa|Ma=ris=sa|3 +markerar|mar=ke=rar|3 +markeringarna|mar=ke=ring=ar=na|3 +massacentrum|mas=sa=cen=trum|3 +massiva|mas=si=va|3 +materia|ma=te=ria|3 +maximal|max=i=mal|3 +meddelar|med=de=lar|3 +medicin|me=di=cin|3 +mentala|men=ta=la|3 +metallegeringar|me=tal=le=ge=ring=ar|3 +metaller|me=tal=ler|3 +metallplatta|me=tall=plat=ta|3 +metallrör|me=tall=rör|3 +metallskenan|me=tall=s=ke=nan|3 +metamorfa|me=ta=mor=fa|3 +metoder|me=to=der|3 +metriskt|met=riskt|3 +middag|mid=dag|3 +mikrobiolog|mik=ro=bi=o=log|3 +mikrogram|mik=ro=gram|3 +mikrovågor|mik=ro=vå=gor|3 +Mindre|Mind=re|3 +mineraler|mi=ne=ra=ler|3 +minimal|mi=ni=mal|3 +miniodlingar|mi=ni=od=ling=ar|3 +Miniodlingarna|Mi=ni=od=ling=ar=na|3 +minister|mi=nis=ter|3 +minnen|min=nen|3 +Minska|Mins=ka|3 +minskas|mins=kas|3 +missat|mis=sat|3 +Misslyckades|Miss=lyck=a=des|3 +misslyckande|miss=lyck=an=de|3 +mittlinje|mitt=lin=je|3 +mittpunkten|mitt=punk=ten|3 +modifiera|mo=di=fi=e=ra|3 +molekylerna|mo=le=ky=ler=na|3 +monumentalt|mo=nu=men=talt|3 +morrhåren|morr=hå=ren|3 +motorcykel|mo=tor=cy=kel|3 +motsatsen|mot=sat=sen|3 +motstår|mot=står|3 +motsvarande|mot=sva=ran=de|3 +motvikt|mot=vikt|3 +männen|män=nen|3 +Mänskligheten|Mänsk=lig=he=ten|3 +märklig|märk=lig|3 +märkliga|märk=li=ga|3 +måttenheter|måt=ten=he=ter|3 +möjligheten|möj=lig=he=ten|3 +Möjligt|Möj=ligt|3 +mörkare|mör=ka=re|3 +mörkret|mörk=ret|3 +nackstödet|nack=stö=det|3 +namnen|nam=nen|3 +nanometer|na=no=me=ter|3 +nationella|na=tio=nel=la|3 +naturligt|na=tur=ligt|3 +naturvetenskap|na=tur=ve=ten=skap|3 +navigation|na=vi=ga=tion|3 +Navigation|Na=vi=ga=tion|3 +navigera|na=vi=ge=ra|3 +nedanför|ne=dan=för|3 +Neutriner|Ne=u=tri=ner|3 +nittionio|nit=tio=nio|3 +Nitton|Nit=ton|3 +noskon|no=s=kon|3 +nyanser|ny=an=ser|3 +nödventilen|nöd=ven=ti=len|3 +nödvändig|nöd=vän=dig|3 +obehagliga|obe=hag=li=ga|3 +obehagligt|obe=hag=ligt|3 +objektets|ob=jek=tets|3 +observerad|ob=ser=ve=rad|3 +observerar|ob=ser=ve=rar|3 +odlingar|od=ling=ar|3 +Odlingarna|Od=ling=ar=na|3 +ogenomskinliga|oge=nom=skin=li=ga|3 +omedelbar|ome=del=bar|3 +omfattande|om=fat=tan=de|3 +omkrets|om=krets|3 +områden|om=rå=den|3 +omtänksamt|om=tänk=samt|3 +ordnat|ord=nat|3 +oregelbundna|ore=gel=bund=na|3 +oroligt|oro=ligt|3 +oräkneliga|oräk=ne=li=ga|3 +osannolik|osan=no=lik|3 +otrolig|otro=lig|3 +outtalad|out=ta=lad|3 +Ovanför|Ovan=för|3 +oxider|ox=i=der|3 +paneler|pa=ne=ler|3 +paniken|pa=ni=ken|3 +panorera|pa=no=re=ra|3 +passande|pas=san=de|3 +passar|pas=sar|3 +passera|pas=se=ra|3 +passerat|pas=se=rat|3 +pendeln|pen=deln|3 +pendlingar|pend=ling=ar|3 +pennan|pen=nan|3 +Petrovaskopets|Pe=tro=vas=ko=pets|3 +pilade|pi=la=de|3 +Pilatus|Pi=la=tus|3 +pillade|pil=la=de|3 +piloten|pi=lo=ten|3 +pilotstol|pi=lot=stol|3 +pipetter|pi=pet=ter|3 +placerad|pla=ce=rad|3 +placerat|pla=ce=rat|3 +planen|pla=nen|3 +Planet|Pla=net|3 +plastbehållare|plast=be=hål=la=re|3 +plattan|plat=tan|3 +pluggen|plug=gen|3 +polare|po=la=re|3 +positivt|po=si=tivt|3 +praktiska|prak=tis=ka|3 +Predator|Pre=da=tor|3 +present|pre=sent|3 +president|pre=si=dent|3 +Presidenten|Pre=si=den=ten|3 +presidenten|pre=si=den=ten|3 +primärbesättningen|pri=mär=be=sätt=ning=en|3 +privat|pri=vat|3 +procedur|pro=ce=dur|3 +procents|pro=cents|3 +produceras|pro=du=ce=ras|3 +programvara|pro=gram=va=ra|3 +provglas|prov=glas|3 +provlådan|prov=lå=dan|3 +prövade|prö=va=de|3 +prövar|prö=var|3 +pumpade|pum=pa=de|3 +pumpas|pum=pas|3 +pumpen|pum=pen|3 +pålitliga|på=lit=li=ga|3 +påverkade|på=ver=ka=de|3 +påverkas|på=ver=kas|3 +påverkats|på=ver=kats|3 +radien|ra=di=en|3 +radiosignal|ra=dio=sig=nal|3 +Rapportera|Rap=por=te=ra|3 +rastrummet|ra=strum=met|3 +reaktion|re=ak=tion|3 +reaktionstid|re=ak=tions=tid|3 +regionen|re=gi=o=nen|3 +regler|reg=ler|3 +rensar|ren=sar|3 +respekt|re=spekt|3 +respons|re=spons|3 +riktat|rik=tat|3 +rimligtvis|rim=ligt=vis|3 +riskabelt|ris=ka=belt|3 +ritningen|rit=ning=en|3 +ritual|ri=tu=al|3 +rotationshuvudet|ro=ta=tions=hu=vu=det|3 +roteras|ro=te=ras|3 +rotorn|ro=torn|3 +ruttna|rutt=na|3 +ryckning|ryck=ning|3 +ryggluckan|rygg=luck=an|3 +ryggpansaret|rygg=pan=sa=ret|3 +ryggsköld|rygg=sköld|3 +rymdens|rym=dens|3 +rymdskeppet|rymd=skep=pet|3 +rymdstationerna|rymd=sta=tio=ner=na|3 +rynkade|ryn=ka=de|3 +ryssar|rys=sar|3 +Ryssen|Rys=sen|3 +räckvidd|räck=vidd|3 +räddade|räd=da=de|3 +räddare|räd=da=re|3 +räkning|räk=ning|3 +rättare|rät=ta=re|3 +röntgenspektrometer|rönt=gen=spekt=ro=me=ter|3 +rörliga|rör=li=ga|3 +sabotera|sa=bo=te=ra|3 +samfundet|sam=fun=det|3 +sanningen|san=ning=en|3 +Sarkasm|Sar=kasm|3 +Saskatchewan|Sas=kat=chewan|3 +sekunderna|se=kun=der=na|3 +sekunders|se=kun=ders|3 +sensorer|sen=so=rer|3 +sexuella|sex=u=el=la|3 +signal|sig=nal|3 +Situationen|Si=tu=a=tio=nen|3 +situationer|si=tu=a=tio=ner|3 +Själva|Själ=va|3 +Skaffa|Skaf=fa|3 +skiftnyckel|skift=nyck=el|3 +skiljer|skil=jer|3 +skjortan|skjor=tan|3 +skopet|sko=pet|3 +skottet|skot=tet|3 +skrapa|skra=pa|3 +skriker|skri=ker|3 +skrivbord|skriv=bord|3 +skrivtecken|skriv=teck=en|3 +Skrovroboten|Skrov=ro=bo=ten|3 +skruvhålen|skruv=hå=len|3 +skruvmejsel|skruv=mej=sel|3 +skrymsle|skryms=le|3 +skräckinjagande|skräck=in=ja=gan=de|3 +skräpet|skrä=pet|3 +skyddat|skyd=dat|3 +skyddsdräkten|skydds=dräk=ten|3 +skyldig|skyl=dig|3 +skämtar|skäm=tar|3 +skölden|sköl=den|3 +sköljer|sköl=jer|3 +slangar|slang=ar|3 +slangarna|slang=ar=na|3 +slappna|slapp=na|3 +slingrar|sling=rar|3 +slumpmässig|slump=mäs=sig|3 +Slutet|Slu=tet|3 +släppt|släppt|3 +slöseri|slö=se=ri|3 +smarta|smar=ta|3 +Snabbt|Snabbt|3 +sneglar|sneg=lar|3 +snurrande|snur=ran=de|3 +snälla|snäl=la|3 +snöret|snö=ret|3 +social|so=ci=al|3 +soffan|sof=fan|3 +solfläckar|sol=fläck=ar|3 +solljuset|sol=lju=set|3 +solskivans|sol=ski=vans|3 +sortens|sor=tens|3 +specifik|spe=ci=fik|3 +spektrometern|spekt=ro=me=tern|3 +spelade|spe=la=de|3 +spelat|spe=lat|3 +Spindriften|Spindrif=ten|3 +spindriftskontrollerna|spindrifts=kon=trol=ler=na|3 +sponken|spon=ken|3 +spricka|spricka|3 +sprickor|sprick=or|3 +springa|springa|3 +sprutan|spru=tan|3 +spårgaser|spår=ga=ser|3 +stabilisera|sta=bi=li=se=ra|3 +stadig|sta=dig|3 +stammen|stam=men|3 +starten|star=ten|3 +staternas|sta=ter=nas|3 +statisk|sta=tisk|3 +steniga|ste=ni=ga|3 +sticka|sticka|3 +stimulus|sti=mu=lus|3 +stjärnans|stjär=nans|3 +Stjärnor|Stjär=nor|3 +stoftpartiklar|stoft=par=tik=lar|3 +stolarna|sto=lar=na|3 +Stolen|Sto=len|3 +struntade|strun=ta=de|3 +sträcka|sträcka|3 +strålar|strå=lar|3 +studier|stu=di=er|3 +styrkan|styr=kan|3 +styrsystemet|styr=sys=te=met|3 +ständigt|stän=digt|3 +stålliknande|stål=lik=nan=de|3 +stållådan|stållå=dan|3 +stöter|stö=ter|3 +superdatorer|su=per=da=to=rer|3 +svalnar|sval=nar|3 +svartcellerna|svart=cel=ler=na|3 +svaveldioxid|sva=vel=di=ox=id|3 +svider|svi=der|3 +svordomar|svor=do=mar|3 +sväljer|sväl=jer|3 +svärtan|svär=tan|3 +svävande|svä=van=de|3 +svåraste|svå=ras=te|3 +symboler|sym=bo=ler|3 +symbolerna|sym=bo=ler=na|3 +Synligt|Syn=ligt|3 +synnerligen|syn=ner=li=gen|3 +Systemet|Sys=te=met|3 +säkerhet|sä=ker=het|3 +sällan|säl=lan|3 +sällskap|säll=skap|3 +sämsta|säms=ta|3 +sängarna|säng=ar=na|3 +sängkläderna|säng=klä=der=na|3 +Särskilt|Sär=skilt|3 +Sätter|Sät=ter|3 +Sådana|Så=da=na|3 +såvida|så=vi=da|3 +sökning|sök=ning|3 +Tanken|Tan=ken|3 +tauljus|tau=ljus|3 +tauljuset|tau=lju=set|3 +taumöbainfektion|tau=mö=ba=in=fek=tion|3 +taumöbornas|tau=mö=bor=nas|3 +teamet|tea=met|3 +tekniker|tek=ni=ker|3 +teknikerna|tek=ni=ker=na|3 +teleskop|te=le=skop|3 +temperaturer|tem=pe=ra=tu=rer|3 +tennisboll|ten=nis=boll|3 +terabyte|te=ra=by=te|3 +tester|tes=ter|3 +testet|tes=tet|3 +tickar|tick=ar|3 +tidsdilation|tids=di=la=tion|3 +tidsdilationen|tids=di=la=tio=nen|3 +tillbringade|till=bring=a=de|3 +tillfredsställande|till=freds=stäl=lan=de|3 +tillgängliga|till=gäng=li=ga|3 +tillgängligt|till=gäng=ligt|3 +tingesten|ting=es=ten|3 +tjugofem|tju=go=fem|3 +Tjugonio|Tju=go=nio|3 +tjugoniotusen|tju=go=nio=tu=sen|3 +Tjugosex|Tju=go=sex|3 +tonerna|to=ner=na|3 +tornet|tor=net|3 +Totalt|To=talt|3 +trakten|trak=ten|3 +trappan|trap=pan|3 +trasslar|trass=lar|3 +tredjedel|tred=je=del|3 +tryckt|tryckt|3 +trygga|tryg=ga|3 +tråden|trå=den|3 +Tråkigt|Trå=kigt|3 +trångt|trångt|3 +tröghet|trög=het|3 +tuggar|tug=gar|3 +tunnelns|tun=nelns|3 +tunnlar|tunn=lar|3 +tursamt|tur=samt|3 +tusendel|tu=sen=del|3 +tvingas|tving=as|3 +tvätta|tvät=ta|3 +tvåhundrasexton|två=hund=ra=sex=ton|3 +tydligare|tyd=li=ga=re|3 +tydligt|tyd=ligt|3 +tyngdlös|tyngd=lös|3 +tyngre|tyng=re|3 +täckta|täck=ta|3 +tänkande|tän=kan=de|3 +tänkmaskin|tänk=ma=skin|3 +Tårarna|Tå=rar=na|3 +ultraviolett|ult=ra=vi=o=lett|3 +underbara|un=der=ba=ra|3 +undersöker|un=der=sö=ker|3 +uppenbara|uppen=ba=ra|3 +uppenbarligen|uppen=bar=li=gen|3 +Uppenbarligen|Uppen=bar=li=gen|3 +uppfann|upp=fann|3 +uppfinning|upp=fin=ning|3 +uppföra|upp=fö=ra|3 +uppgifter|upp=gif=ter|3 +uppifrån|upp=i=från|3 +upplyst|upp=lyst|3 +uppoffring|upp=off=ring|3 +upprepa|upp=re=pa|3 +uppskattningar|upp=skatt=ning=ar|3 +Uppskjutningen|Upp=skjut=ning=en|3 +upptagna|upp=tag=na|3 +upptäckter|upp=täck=ter|3 +utbrast|ut=brast|3 +utdata|ut=da=ta|3 +utlovat|ut=lo=vat|3 +utmaning|ut=ma=ning|3 +Utmärkt|Ut=märkt|3 +Utomjordingar|Ut=om=jor=ding=ar|3 +uträkning|ut=räk=ning|3 +utsikt|ut=sikt|3 +utskrifter|ut=skrif=ter|3 +utsträckta|ut=sträck=ta|3 +utstrålade|ut=strå=la=de|3 +utsätta|ut=sät=ta|3 +utsätts|ut=sätts|3 +utsöndrar|ut=sönd=rar|3 +uttryckt|ut=tryckt|3 +utvalda|ut=val=da|3 +utvecklad|ut=veck=lad|3 +utvecklar|ut=veck=lar|3 +utöver|ut=över|3 +vackert|vack=ert|3 +vakter|vak=ter|3 +vakterna|vak=ter=na|3 +Vakterna|Vak=ter=na|3 +valsång|val=sång|3 +varannan|var=an=nan|3 +varelser|va=rel=ser|3 +variant|va=ri=ant|3 +variation|va=ri=a=tion|3 +varierar|va=ri=e=rar|3 +varifrån|var=i=från|3 +varvet|var=vet|3 +vattnets|vatt=nets|3 +veckan|veck=an|3 +verkliga|verk=li=ga|3 +version|ver=sion|3 +vetenskapen|ve=ten=ska=pen|3 +vettigt|vet=tigt|3 +vettlöst|vett=löst|3 +viftade|vif=ta=de|3 +vilade|vi=la=de|3 +VINKELANOMALI|VIN=KE=LA=NO=MA=LI|3 +vinklade|vink=la=de|3 +Väggen|Väg=gen|3 +välbekanta|väl=be=kan=ta|3 +värden|vär=den|3 +värmekälla|vär=me=käl=la|3 +väsentligt|vä=sent=ligt|3 +väskor|väsk=or|3 +vätskor|väts=kor|3 +xenonitväggarna|xe=no=nit=väg=gar=na|3 +ynkrygg|ynk=rygg|3 +ytterdörren|yt=ter=dör=ren|3 +Ytterligare|Yt=ter=li=ga=re|3 +ytterluckan|yt=ter=luck=an|3 +ytterporten|yt=ter=por=ten|3 +äcklig|äck=lig|3 +äckligt|äck=ligt|3 +ändarna|än=dar=na|3 +ärligt|är=ligt|3 +Ärligt|Är=ligt|3 +återfinns|åter=finns|3 +ÅTERSTÅENDE|ÅTER=STÅ=EN=DE|3 +ödemark|öde=mark|3 +Ögonen|Ögo=nen|3 +önskemål|öns=ke=mål|3 +öppnades|öpp=na=des|3 +öppningar|öpp=ning=ar|3 +överflöd|över=flöd|3 +översätter|över=sät=ter|3 +absoluta|ab=so=lu=ta|2 +abstrakt|ab=strakt|2 +Absurt|Ab=surt|2 +Accelerationen|Ac=ce=le=ra=tio=nen|2 +accelererade|ac=ce=le=re=ra=de|2 +accepterar|ac=cep=te=rar|2 +adenosintrifosfat|ade=no=sintri=fos=fat|2 +administratör|ad=mi=ni=stra=tör|2 +Adrianmiljö|Ad=ri=an=mil=jö|2 +agerar|ag=e=rar|2 +akterdelen|ak=ter=de=len|2 +aktiverad|ak=ti=ve=rad|2 +Alldeles|All=de=les|2 +allmänna|all=män=na|2 +allmänt|all=mänt|2 +allvarliga|all=var=li=ga|2 +aluminiumplåt|alu=mi=ni=ump=låt|2 +andningsmask|and=nings=mask|2 +angrepp|an=grepp|2 +anmäla|an=mä=la|2 +anmälde|an=mäl=de|2 +Annars|An=nars|2 +anrika|an=ri=ka|2 +ansvarig|an=sva=rig|2 +antagit|an=ta=git|2 +anteckningsblock|an=teck=nings=block|2 +Antibiotika|An=ti=bi=o=ti=ka|2 +antika|an=ti=ka|2 +antimateria|an=ti=ma=te=ria|2 +antyder|an=ty=der|2 +använts|an=vänts|2 +apoapsis|apo=apsis|2 +apparater|ap=pa=ra=ter|2 +apparaterna|ap=pa=ra=ter=na|2 +Arbeta|Ar=be=ta|2 +Arbetar|Ar=be=tar|2 +arrangerade|ar=ran=ge=ra=de|2 +artighet|ar=tig=het|2 +asteroider|as=te=ro=i=der|2 +astrofagbränsle|ast=ro=fag=brän=sle|2 +Astrofagen|Ast=ro=fa=gen|2 +astrofagfyllda|ast=ro=fag=fyll=da|2 +astrofagglas|ast=ro=fagg=las|2 +astrofagpanelen|ast=ro=fag=pa=ne=len|2 +astrofagproblemet|ast=ro=fag=pro=ble=met|2 +astrofagsvets|ast=ro=fagsvets|2 +astrofagsvetsen|ast=ro=fagsvet=sen|2 +astronom|ast=ro=nom|2 +astronomerna|ast=ro=no=mer=na|2 +astronomi|ast=ro=no=mi|2 +atomerna|ato=mer=na|2 +attityd|at=ti=tyd|2 +attraherar|at=tra=he=rar|2 +autentiskt|au=ten=tiskt|2 +AUTOKORRIGERAS|AU=TO=KOR=RI=GE=RAS|2 +automatiska|au=to=ma=tis=ka|2 +avbalkning|av=balk=ning|2 +avbalkningar|av=balk=ning=ar|2 +avbrott|av=brott|2 +avbryter|av=bry=ter|2 +avfärdande|av=fär=dan=de|2 +avfärdar|av=fär=dar|2 +Avgjort|Av=gjort|2 +avklarat|av=kla=rat|2 +Avläsningarna|Av=läs=ning=ar=na|2 +avläsningen|av=läs=ning=en|2 +avlång|av=lång|2 +avrundade|av=run=da=de|2 +avsevärt|av=se=värt|2 +avskuren|av=sku=ren|2 +avskyr|av=skyr|2 +avslutat|av=slu=tat|2 +avstängd|av=stängd|2 +avstängningsventiler|av=stäng=nings=ven=ti=ler|2 +avtryckare|av=tryck=a=re|2 +avvara|av=va=ra|2 +backup|back=up|2 +backupsystem|back=up=sys=tem|2 +badrummet|bad=rum=met|2 +bakifrån|bak=i=från|2 +baksida|bak=si=da|2 +baksidan|bak=si=dan|2 +balans|ba=lans|2 +balansen|ba=lan=sen|2 +balansera|ba=lan=se=ra|2 +bandagen|ban=da=gen|2 +bandagerade|ban=da=ge=ra=de|2 +barnsliga|barns=li=ga|2 +baseras|ba=se=ras|2 +baserat|ba=se=rat|2 +batteriet|bat=te=ri=et|2 +beakta|be=ak=ta|2 +beboeliga|be=bo=e=li=ga|2 +bedrövligt|be=dröv=ligt|2 +beetle|be=et=le|2 +beetles|be=et=les|2 +Beetles|Be=et=les|2 +Befann|Be=fann|2 +befolkningen|be=folk=ning=en|2 +befälet|be=fä=let|2 +begagnar|be=gag=nar|2 +begränsa|be=grän=sa|2 +behandla|be=hand=la|2 +behandling|be=hand=ling|2 +behövas|be=hö=vas|2 +Beklagar|Be=kla=gar|2 +bekräftade|be=kräf=ta=de|2 +bekräftelse|be=kräf=tel=se|2 +bekväma|be=kvä=ma|2 +bekvämare|be=kvä=ma=re|2 +belysningen|be=lys=ning=en|2 +beläget|be=lä=get|2 +beordrar|be=ord=rar|2 +Beroende|Be=ro=en=de|2 +beräknade|be=räk=na=de|2 +beräkning|be=räk=ning|2 +berörd|be=rörd|2 +besannas|be=san=nas|2 +Beslutet|Be=slu=tet|2 +bestämda|be=stäm=da|2 +bestämde|be=stäm=de|2 +bestämma|be=stäm=ma|2 +besvarar|be=sva=rar|2 +besättningsenheten|be=sätt=nings=en=he=ten|2 +Besättningsenhetens|Be=sätt=nings=en=he=tens|2 +besättningskamrat|be=sätt=nings=kam=rat|2 +besättningsutrymmet|be=sätt=nings=ut=rym=met|2 +beteendet|be=te=en=det|2 +betjänar|be=tjä=nar|2 +betraktade|be=trak=ta=de|2 +betraktar|be=trak=tar|2 +betydelsefull|be=ty=del=se=full|2 +betydelsefullt|be=ty=del=se=fullt|2 +betydelselös|be=ty=del=se=lös|2 +Betyder|Be=ty=der|2 +bevisade|be=vi=sa=de|2 +bibehåller|bi=be=hål=ler|2 +biljett|bil=jett|2 +binder|bin=der|2 +biologer|bio=lo=ger|2 +bisarr|bi=sarr|2 +bjuder|bju=der|2 +blanka|blan=ka|2 +blekgröna|blek=grö=na|2 +bleknade|blek=na=de|2 +blickstilla|blick=stil=la|2 +blinka|blin=ka|2 +blinkade|blin=ka=de|2 +Blixten|Blix=ten|2 +blockerar|block=e=rar|2 +blodprov|blod=prov|2 +blotta|blot=ta|2 +blygsam|blyg=sam|2 +bländande|blän=dan=de|2 +bokade|bo=ka=de|2 +bokstaven|bok=sta=ven|2 +bomullspinne|bom=ull=s=pin=ne|2 +bomullspinnen|bom=ull=s=pin=nen|2 +Bomullspinnen|Bom=ull=s=pin=nen|2 +bostäder|bo=stä=der|2 +bredast|bre=dast|2 +Bredvid|Bred=vid|2 +brickor|brick=or|2 +British|Bri=tish|2 +britsen|brit=sen|2 +Brukar|Bru=kar|2 +brutal|bru=tal|2 +brutala|bru=ta=la|2 +brutalt|bru=talt|2 +brännsåren|bränn=så=ren|2 +Bränslekapslarna|Brän=s=le=kaps=lar=na|2 +bränsleledning|brän=s=le=led=ning|2 +bränsleledningar|brän=s=le=led=ning=ar|2 +bränslesystemet|brän=sle=sys=te=met|2 +Bränsletankarna|Brän=sle=tan=kar=na|2 +brådska|bråds=ka|2 +bröder|brö=der|2 +buckla|buck=la|2 +bultarna|bul=tar=na|2 +bunker|bun=ker|2 +bunten|bun=ten|2 +butter|but=ter|2 +bökiga|bö=ki=ga|2 +Börjar|Bör=jar|2 +Centauri|Cent=au=ri|2 +centimeters|cen=ti=me=ters|2 +Centrifug|Cen=tri=fug|2 +centrifugalgravitation|cen=tri=fu=gal=gra=vi=ta=tion|2 +centrifugalkonfiguration|cen=tri=fu=gal=kon=fi=gu=ra=tion|2 +Centrifugalkraften|Cen=tri=fu=gal=kraf=ten|2 +centrifugkonfiguration|cen=tri=fug=kon=fi=gu=ra=tion|2 +centrifugkonfigurationen|cen=tri=fug=kon=fi=gu=ra=tio=nen|2 +centripetalkraft|cen=tri=pe=tal=kraft|2 +chefsforskarna|chefs=fors=kar=na|2 +chockad|chock=ad|2 +cirklarna|cirk=lar=na|2 +cirkulationssystem|cir=ku=la=tions=sys=tem|2 +cirkulationssystemet|cir=ku=la=tions=sys=te=met|2 +Columbia|Co=lum=bia|2 +coolaste|coo=las=te|2 +cylindrar|cy=lind=rar|2 +cylindrisk|cy=lind=risk|2 +datorerna|da=to=rer=na|2 +datorprogram|da=tor=pro=gram|2 +decelererar|de=ce=le=re=rar|2 +decennium|de=cen=ni=um|2 +definierar|de=fi=ni=e=rar|2 +degenerera|de=ge=ne=re=ra|2 +depression|de=pres=sion|2 +detaljerade|de=tal=je=ra=de|2 +detaljerna|de=tal=jer=na|2 +Diagrammet|Dia=gram=met|2 +dialekt|dia=lekt|2 +dialogruta|dia=lo=gru=ta|2 +digitala|di=gi=ta=la|2 +dingla|ding=la|2 +diplomaten|dip=lo=ma=ten|2 +diskuterade|dis=ku=te=ra=de|2 +djungel|djung=el|2 +djupanalyser|dju=p=ana=ly=ser|2 +djurens|dju=rens|2 +doktorander|dok=to=ran=der|2 +dokument|do=ku=ment|2 +Domaren|Do=ma=ren|2 +domstolsbetjänten|dom=stols=be=tjän=ten|2 +drabbar|drab=bar|2 +drabbats|drab=bats|2 +dragkraften|drag=kraf=ten|2 +dragningskraft|drag=nings=kraft|2 +drivmedel|driv=me=del|2 +drivmetod|driv=me=tod|2 +Dräkten|Dräk=ten|2 +dräkterna|dräk=ter=na|2 +dräktradion|dräkt=ra=di=on|2 +drämde|dräm=de|2 +dubbel|dub=bel|2 +duktig|duk=tig|2 +dunsar|dun=sar|2 +dussin|dus=sin|2 +dvärgbrytare|dvärg=bry=ta=re|2 +dygnen|dyg=nen|2 +dystert|dys=tert|2 +dystra|dyst=ra|2 +dåliga|då=li=ga|2 +dödlig|död=lig|2 +dödsfallen|döds=fal=len|2 +eftermiddag|ef=ter=mid=dag|2 +Efteråt|Ef=ter=åt|2 +egenskap|egen=skap|2 +egentlig|egent=lig|2 +egentliga|egent=li=ga|2 +Einstein|Ein=stein|2 +ekolokalisering|eko=lo=ka=li=se=ring|2 +ekosystemet|eko=sys=te=met|2 +elektrisk|el=ekt=risk|2 +elektriskt|el=ekt=riskt|2 +elektromagnetiska|el=ekt=ro=mag=ne=tis=ka|2 +elektron|el=ekt=ron|2 +elektronik|el=ekt=ro=nik|2 +elementär|ele=men=tär|2 +elliptiska|el=lip=tis=ka|2 +elände|elän=de|2 +emissioner|emis=sio=ner|2 +enades|ena=des|2 +Encelliga|Encel=li=ga|2 +endera|en=de=ra|2 +energilagringsmedium|ener=gi=lag=rings=me=di=um|2 +energimängder|ener=gi=m=äng=der|2 +enfald|en=fald|2 +engagerad|en=ga=ge=rad|2 +engelskt|eng=elskt|2 +engelsman|eng=els=man|2 +enheten|en=he=ten|2 +ensamma|en=sam=ma|2 +entusiastisk|en=tu=si=as=tisk|2 +epoxilimmar|epox=i=lim=mar|2 +erbjöd|er=bjöd|2 +erfarenhet|er=fa=ren=het|2 +ERFORDRAS|ER=FORD=RAS|2 +Eridanis|Eri=da=nis|2 +ersätta|er=sät=ta|2 +etikett|eti=kett|2 +etthundrasjuttiotre|ett=hund=ra=sjut=tio=tre|2 +Europa|Eu=ro=pa|2 +existera|ex=i=ste=ra|2 +exoplaneter|ex=o=pla=ne=ter|2 +expedition|ex=pe=di=tion|2 +Experiment|Ex=pe=ri=ment|2 +experimentera|ex=pe=ri=men=te=ra|2 +Experimentet|Ex=pe=ri=men=tet|2 +experten|ex=per=ten|2 +Explosion|Ex=plo=sion|2 +exponentiella|ex=po=nen=ti=el=la|2 +Faller|Fal=ler|2 +fallerar|fal=le=rar|2 +falnar|fal=nar|2 +Fantastiskt|Fan=tas=tiskt|2 +farlig|far=lig|2 +Farligt|Far=ligt|2 +Farväl|Far=väl|2 +fascinerande|fa=sci=ne=ran=de|2 +fastgjord|fast=gjord|2 +fastnat|fast=nat|2 +fattat|fat=tat|2 +FELAKTIG|FEL=AK=TIG|2 +felmarginalen|fel=mar=gi=na=len|2 +femtedel|fem=te=del|2 +Femtio|Fem=tio|2 +fenomenet|fe=no=me=net|2 +fiender|fi=en=der|2 +filter|fil=ter|2 +fingertopparna|fing=er=top=par=na|2 +fininställt|fin=in=ställt|2 +fjärdedel|fjär=de=del|2 +flammande|flam=man=de|2 +flammiga|flam=mi=ga|2 +flaska|flas=ka|2 +flinade|fli=na=de|2 +flockinstinkt|flock=in=stinkt|2 +flotta|flot=ta|2 +flygande|fly=gan=de|2 +flygdäck|flyg=däck|2 +flygvapnet|flyg=vap=net|2 +flygvapnets|flyg=vap=nets|2 +flyktvektor|flykt=vek=tor|2 +flyttat|flyt=tat|2 +fläckarna|fläck=ar=na|2 +fläcken|fläck=en|2 +Fläcken|Fläck=en|2 +flämtningar|flämt=ning=ar|2 +fokusera|fo=ku=se=ra|2 +foldern|fol=dern|2 +folien|fo=li=en|2 +formad|for=mad|2 +formeln|for=meln|2 +Forskare|Fors=ka=re|2 +forskaren|fors=ka=ren|2 +forskarvärlden|fors=kar=värl=den|2 +Forskningscentret|Forsk=nings=cen=tret|2 +forskningsrapport|forsk=nings=rap=port|2 +fortplantningen|fort=plant=ning=en|2 +fortplantningszon|fort=plant=nings=zon|2 +Fortsätt|Fort=sätt|2 +Framgång|Fram=gång|2 +Framme|Fram=me|2 +Framsteg|Fram=steg|2 +framstående|fram=stå=en=de|2 +frekvens|fre=kvens|2 +frigör|fri=gör|2 +friska|fris=ka|2 +frivilliga|fri=vil=li=ga|2 +frivilligt|fri=vil=ligt|2 +frukostburrito|fru=kost=bur=ri=to|2 +frustrerad|fru=stre=rad|2 +frustrerande|fru=stre=ran=de|2 +Frågan|Frå=gan|2 +frågat|frå=gat|2 +fullborda|full=bor=da|2 +fullbordar|full=bor=dar|2 +fullgjord|full=gjord|2 +fullkomligt|full=kom=ligt|2 +Fullständigt|Full=stän=digt|2 +fullända|full=än=da|2 +fulländade|full=än=da=de|2 +Fungerade|Fun=ge=ra=de|2 +fungerande|fun=ge=ran=de|2 +fungerat|fun=ge=rat|2 +funktionella|funk=tio=nel=la|2 +funnit|fun=nit|2 +futtiga|fut=ti=ga|2 +fyllda|fyll=da|2 +fyrbråk|fyr=bråk|2 +fyrkantig|fyr=kan=tig|2 +fyrtiosex|fyr=tio=sex|2 +fyrtiotvå|fyr=tio=två|2 +fysiologi|fy=si=o=lo=gi|2 +fängelsets|fäng=el=sets|2 +Färden|Fär=den|2 +färdig|fär=dig|2 +färdriktning|färd=rikt=ning|2 +Färgen|Fär=gen|2 +färska|färs=ka|2 +fånigt|få=nigt|2 +Följaktligen|Följ=akt=li=gen|2 +följas|föl=jas|2 +förbaskade|för=bas=ka=de|2 +förbenat|för=be=nat|2 +förbigår|för=bi=går|2 +förblev|för=blev|2 +Förbluffande|För=bluf=fan=de|2 +fördubblas|för=dubb=las|2 +föredragit|fö=re=dra=git|2 +föregående|fö=re=gå=en=de|2 +förekomma|fö=re=kom=ma|2 +förekommande|fö=re=kom=man=de|2 +föremålet|fö=re=må=let|2 +Föremålet|Fö=re=må=let|2 +förenar|för=enar|2 +förenkla|för=enk=la|2 +förestående|fö=re=stå=en=de|2 +företa|fö=re=ta|2 +förfärad|för=fä=rad|2 +förgäves|för=gä=ves|2 +förhandling|för=hand=ling|2 +förhandlingen|för=hand=ling=en|2 +förhållandet|för=hål=lan=det|2 +förklaringen|för=kla=ring=en|2 +förlist|för=list|2 +förlängningssladdar|för=läng=nings=slad=dar|2 +Förlåt|För=låt|2 +förlåt|för=låt|2 +förmedlar|för=med=lar|2 +förmodade|för=mo=da=de|2 +förmår|för=mår|2 +förnamn|för=namn|2 +förnuft|för=nuft|2 +förnybar|för=ny=bar|2 +förrådsrum|för=råds=rum|2 +förrådsrummet|för=råds=rum=met|2 +förrådsskåpet|för=råds=skå=pet|2 +församlade|för=sam=la=de|2 +Försegla|För=seg=la|2 +förseglingarna|för=seg=ling=ar=na|2 +förseglingen|för=seg=ling=en|2 +försett|för=sett|2 +förskingrade|för=sking=ra=de|2 +förskingrat|för=sking=rat|2 +Förstå|För=stå|2 +förstått|för=stått|2 +försvagas|för=sva=gas|2 +försvagningen|för=svag=ning=en|2 +försvar|för=svar|2 +försvaret|för=sva=ret|2 +försvarets|för=sva=rets|2 +Försöker|För=sö=ker|2 +försöksdjur|för=söks=djur|2 +FÖRUTSEDD|FÖR=UT=SEDD|2 +förutsägelser|för=ut=sä=gel=ser|2 +förvandlas|för=vand=las|2 +förvara|för=va=ra|2 +förvaringslådor|för=va=rings=lå=dor|2 +förverkliga|för=verk=li=ga|2 +förvirrade|för=vir=ra=de|2 +förvirringen|för=vir=ring=en|2 +förvisso|för=vis=so|2 +förväntningarna|för=vänt=ning=ar=na|2 +förvånansvärt|för=vå=nans=värt|2 +föräldrar|för=äld=rar|2 +förändra|för=änd=ra|2 +förändring|för=änd=ring|2 +förändringar|för=änd=ring=ar|2 +förångar|för=ång=ar|2 +förödande|för=ödan=de|2 +galaxen|ga=lax=en|2 +gammalt|gam=malt|2 +gapande|ga=pan=de|2 +garanterar|ga=ran=te=rar|2 +gascylindrar|ga=scy=lind=rar|2 +gasform|gas=form|2 +gaskromatografen|ga=skro=ma=to=gra=fen|2 +geggan|geg=gan|2 +gemensamma|ge=men=sam=ma|2 +generatorer|ge=ne=ra=to=rer|2 +Generatorn|Ge=ne=ra=torn|2 +genombrott|ge=nom=brott|2 +genombrutet|ge=nom=bru=tet|2 +genombrutna|ge=nom=brut=na|2 +genomförd|ge=nom=förd|2 +genomlida|ge=nom=li=da|2 +genomsnittstemperaturen|ge=nom=snitt=s=tem=pe=ra=tu=ren|2 +genomsyra|ge=nom=sy=ra|2 +genomträngande|ge=nom=träng=an=de|2 +genuint|ge=nu=int|2 +giftig|gif=tig|2 +gillade|gil=la=de|2 +girmotor|gir=mo=tor|2 +gissade|gis=sa=de|2 +gissat|gis=sat|2 +glasfönster|glas=föns=ter|2 +glasspinnesiffrorna|glass=pin=ne=siff=ror=na|2 +glasögon|glas=ögon|2 +glädje|gläd=je|2 +glänsande|glän=san=de|2 +glöden|glö=den|2 +glömde|glöm=de|2 +gnager|gna=ger|2 +grammatik|gram=ma=tik|2 +granne|gran=ne|2 +granskade|grans=ka=de|2 +granskning|gransk=ning|2 +grimaserade|gri=ma=se=ra=de|2 +gripverktyget|grip=verk=ty=get|2 +grumlar|grum=lar|2 +grunden|grun=den|2 +grundämne|grund=äm=ne|2 +grundämnen|grund=äm=nen|2 +gruppen|grup=pen|2 +gränslöst|gräns=löst|2 +gräver|grä=ver|2 +gråter|grå=ter|2 +Guinness|Gu=in=ness|2 +Guldlockszonen|Guld=locks=zo=nen|2 +gäspar|gäs=par|2 +gångerna|gång=er=na|2 +gångjärn|gång=järn|2 +gångjärnen|gång=jär=nen|2 +haltar|hal=tar|2 +halvmeter|halv=me=ter|2 +halvorna|hal=vor=na|2 +handflatorna|hand=fla=tor=na|2 +handhållna|hand=håll=na|2 +Hangarfartyg|Han=gar=far=tyg|2 +Harrison|Har=ri=son|2 +Hastighet|Has=tig=het|2 +hastigheter|has=tig=he=ter|2 +havsvatten|havs=vat=ten|2 +hejdat|hej=dat|2 +hektopascal|hek=to=pa=scal|2 +helhet|hel=het|2 +helikopterplatta|he=li=kop=ter=plat=ta|2 +hemligheter|hem=lig=he=ter|2 +hemska|hems=ka|2 +hemsystem|hem=sys=tem|2 +hermetiskt|her=me=tiskt|2 +Heroin|He=ro=in|2 +herrar|her=rar|2 +heterosexuella|he=te=ro=sex=u=el=la|2 +himlakroppar|him=la=krop=par|2 +historiska|his=to=ris=ka|2 +Hittat|Hit=tat|2 +hjälmen|hjäl=men|2 +holländska|hol=länds=ka|2 +hopknölade|hop=knö=la=de|2 +hoppade|hop=pa=de|2 +hoppat|hop=pat|2 +hoppet|hop=pet|2 +hopplös|hopp=lös|2 +hopsjunken|hop=sjun=ken|2 +hostar|hos=tar|2 +Houston|Hous=ton|2 +Hundra|Hun=dra|2 +hundratusentals|hun=dra=tu=sen=tals|2 +huvudförhandling|hu=vud=för=hand=ling|2 +huvudräkning|hu=vud=räk=ning|2 +huvudsakliga|hu=vud=sak=li=ga|2 +Huvudsakligen|Hu=vud=sak=li=gen|2 +huvudsakligen|hu=vud=sak=li=gen|2 +hyfsad|hyf=sad|2 +hylsan|hyl=san|2 +hyperventilerar|hy=per=ven=ti=le=rar|2 +hypotes|hy=po=tes|2 +hysterisk|hy=ste=risk|2 +häftig|häf=tig|2 +Hälften|Hälf=ten|2 +hämtat|häm=tat|2 +händelse|hän=del=se|2 +Häpnadsväckande|Häp=nads=väck=an=de|2 +härdar|här=dar|2 +härnäst|här=näst|2 +hävdade|häv=da=de|2 +hållas|hål=las|2 +hållen|hål=len|2 +hårdhänt|hård=hänt|2 +högljutt|hög=ljutt|2 +högtalare|hög=ta=la=re|2 +högteknologisk|hög=tek=no=lo=gisk|2 +höljena|höl=je=na|2 +höljets|höl=jets|2 +hörsel|hör=sel|2 +iakttagit|iakt=ta=git|2 +iakttog|iakt=tog|2 +identifierar|iden=ti=fi=e=rar|2 +identifierat|iden=ti=fi=e=rat|2 +idioten|idi=o=ten|2 +idiotisk|idi=o=tisk|2 +idiotiska|idi=o=tis=ka|2 +idiotiskt|idi=o=tiskt|2 +ifrågasätter|ifrå=ga=sät=ter|2 +ignorera|ig=no=re=ra|2 +ihålig|ihå=lig|2 +illamående|il=la=må=en=de|2 +imiterar|imi=te=rar|2 +Immaterialrättsalliansen|Im=ma=te=ri=al=rätt=s=al=li=an=sen|2 +immunsystem|im=mun=sys=tem|2 +improviserat|im=pro=vi=se=rat|2 +inbillade|in=bil=la=de|2 +inblandad|in=blan=dad|2 +inbromsningen|in=broms=ning=en|2 +indelat|in=de=lat|2 +individuella|in=di=vi=du=el=la|2 +inducera|in=du=ce=ra|2 +industriella|in=dust=ri=el=la|2 +inexakt|in=ex=akt|2 +infektion|in=fek=tion|2 +infektionen|in=fek=tio=nen|2 +informera|in=for=me=ra|2 +informerad|in=for=me=rad|2 +infrastruktur|in=fra=struk=tur|2 +ingenjörerna|in=gen=jö=rer=na|2 +ingick|in=gick|2 +ingång|in=gång|2 +initiera|ini=ti=e=ra|2 +injektionsspruta|in=jek=tions=spru=ta|2 +inkonsekvent|in=kon=se=kvent|2 +inledande|in=le=dan=de|2 +inleder|in=le=der|2 +inlett|in=lett|2 +inlåst|in=låst|2 +Innanför|In=nan=för|2 +innebära|in=ne=bä=ra|2 +innehållet|in=ne=hål=let|2 +innerdörren|in=ner=dör=ren|2 +innervägg|in=ner=vägg|2 +innesluten|in=ne=slu=ten|2 +inneslutet|in=ne=slu=tet|2 +inneslutna|in=ne=slut=na|2 +inrättat|in=rät=tat|2 +insida|in=si=da|2 +insisterar|in=si=ste=rar|2 +inspektera|in=spek=te=ra|2 +intakt|in=takt|2 +intakta|in=tak=ta|2 +intelligenser|in=tel=li=gen=ser|2 +intensiv|in=ten=siv|2 +interaktion|in=ter=ak=tion|2 +Internationella|In=ter=na=tio=nel=la|2 +interplanetarisk|in=ter=pla=ne=ta=risk|2 +interstellärt|in=ter=stel=lärt|2 +Intervallet|In=ter=val=let|2 +intighet|in=tig=het|2 +intravenös|in=tra=ve=nös|2 +intravenöst|in=tra=ve=nöst|2 +intresserade|in=tres=se=ra=de|2 +intresserat|in=tres=se=rat|2 +invändig|in=vän=dig|2 +Invändigt|In=vän=digt|2 +invändigt|in=vän=digt|2 +irriterad|ir=ri=te=rad|2 +isolator|iso=la=tor|2 +isolerad|iso=le=rad|2 +isolerat|iso=le=rat|2 +isvatten|is=vat=ten|2 +iögonfallande|iögon=fal=lan=de|2 +Jagare|Ja=ga=re|2 +jagburgare|jag=bur=ga=re|2 +jaktplan|jakt=plan|2 +japansk|ja=pansk|2 +jobbig|job=big|2 +joniseras|jo=ni=se=ras|2 +jordbruket|jord=bru=ket|2 +jordbruksmark|jord=bruks=mark|2 +jordbävning|jord=bäv=ning|2 +jordgravitation|jord=gra=vi=ta=tion|2 +jordsekunder|jord=se=kun=der|2 +Jupiter|Ju=pi=ter|2 +justera|ju=ste=ra|2 +justeringar|ju=ste=ring=ar|2 +justeringsmotorerna|ju=ste=rings=mo=to=rer=na|2 +jämförelsevis|jäm=fö=rel=se=vis|2 +Kabelskydd|Ka=bel=skydd|2 +kablar|kab=lar|2 +Kablarna|Kab=lar=na|2 +kalkylatorn|kal=ky=la=torn|2 +kallade|kal=la=de|2 +kalorier|ka=lo=ri=er|2 +Kamera|Ka=me=ra|2 +kamerans|ka=me=rans|2 +kamrater|kam=ra=ter|2 +kanadensarna|ka=na=den=sar=na|2 +kandidaten|kan=di=da=ten|2 +kapitulerade|ka=pi=tu=le=ra=de|2 +kardborrar|kard=bor=rar|2 +kartong|kar=tong|2 +katastrofalt|ka=ta=stro=falt|2 +katetern|ka=te=tern|2 +kazakhstanska|ka=zakh=stans=ka|2 +kedjans|ked=jans|2 +kedjelänk|ked=je=länk|2 +kedjelänkar|ked=je=län=kar|2 +kedjor|ked=jor|2 +kemikalie|ke=mi=ka=lie|2 +kemoterapi|ke=mo=te=ra=pi|2 +kinderna|kin=der=na|2 +Kineserna|Ki=ne=ser=na|2 +kinetisk|kin=e=tisk|2 +kinetiska|kin=e=tis=ka|2 +klagade|kla=ga=de|2 +klappar|klap=par|2 +klarare|kla=ra=re|2 +klassrummet|klass=rum=met|2 +kletiga|kle=ti=ga|2 +klickade|klick=a=de|2 +klimatförändringen|kli=mat=för=änd=ring=en|2 +klimatologer|kli=ma=to=lo=ger|2 +klingande|kling=an=de|2 +klister|klis=ter|2 +klistrar|klist=rar|2 +klumpig|klum=pig|2 +klumpigt|klum=pigt|2 +klämmor|kläm=mor|2 +knacka|knacka|2 +Knappt|Knappt|2 +knutit|knu=tit|2 +knäppa|knäp=pa|2 +knäppte|knäpp=te|2 +kokpunkt|kok=punkt|2 +kollade|kol=la=de|2 +kollat|kol=lat|2 +kollisioner|kol=li=sio=ner|2 +kolonier|ko=lo=ni=er|2 +komasörja|ko=ma=sör=ja|2 +kommande|kom=man=de|2 +kommandomodulen|kom=man=do=mo=du=len|2 +Kommendör|Kom=men=dör|2 +kommersiell|kom=mer=si=ell|2 +kommersiella|kom=mer=si=el=la|2 +kompensera|kom=pen=se=ra|2 +komplexitet|kom=plex=i=tet|2 +konferensrummet|kon=fe=rens=rum=met|2 +konflikt|kon=flikt|2 +konformade|kon=for=ma=de|2 +konsistens|kon=si=stens|2 +konstaterar|kon=sta=te=rar|2 +konstig|kons=tig|2 +Konstigare|Kons=ti=ga=re|2 +konstruktionsfel|kon=struk=tions=fel|2 +kontakter|kon=tak=ter|2 +kontinenten|kon=ti=nen=ten|2 +kontinuerliga|kon=ti=nu=er=li=ga|2 +kontrollpanel|kon=troll=pa=nel|2 +kontrollpanelerna|kon=troll=pa=ne=ler=na|2 +kontrollrumsbubbla|kon=troll=rums=bubb=la|2 +kontur|kon=tur|2 +kopierat|ko=pi=e=rat|2 +kopiösa|ko=pi=ö=sa|2 +kopplad|kopp=lad|2 +kopplade|kopp=la=de|2 +korkat|kor=kat|2 +Korrekt|Kor=rekt|2 +korridorer|kor=ri=do=rer|2 +kortare|kor=ta=re|2 +kortaste|kor=tas=te|2 +kostym|ko=stym|2 +krafsade|kraf=sa=de|2 +kriget|kri=get|2 +kringgå|kring=gå|2 +krisen|kri=sen|2 +krokar|kro=kar|2 +kropparna|krop=par=na|2 +kroppsöppning|kropp=söpp=ning|2 +kryssar|krys=sar|2 +kryssningshastighet|kryss=nings=has=tig=het|2 +kulturella|kul=tu=rel=la|2 +kulturen|kul=tu=ren|2 +kunnig|kun=nig|2 +kupolen|ku=po=len|2 +kupolens|ku=po=lens|2 +Kustlinjen|Kust=lin=jen|2 +kvadratkilometer|kvad=rat=ki=lo=me=ter|2 +kvicksilver|kvick=sil=ver|2 +kväveprocent|kvä=ve=pro=cent|2 +kväveresistens|kvä=ve=re=sis=tens|2 +kändes|kän=des|2 +Känner|Kän=ner|2 +känsel|kän=sel|2 +känslig|käns=lig|2 +känsligt|käns=ligt|2 +käppen|käp=pen|2 +käpprätt|käpp=rätt|2 +kärnvapen|kärn=va=pen|2 +köttet|köt=tet|2 +labben|lab=ben|2 +labbets|lab=bets|2 +labbrock|labb=rock|2 +labbrockar|labb=rock=ar|2 +labbstolen|labb=sto=len|2 +labbutrustning|lab=b=ut=rust=ning|2 +laboratoriebänk|la=bo=ra=to=rie=bänk|2 +laboratoriets|la=bo=ra=to=ri=ets|2 +laboratorieugn|la=bo=ra=to=rieu=gn|2 +labyrint|la=by=rint|2 +lagrade|lag=ra=de|2 +lagret|lag=ret|2 +lagringskapacitet|lag=rings=ka=pa=ci=tet|2 +lakanen|la=ka=nen|2 +Lamais|La=mais|2 +Lampan|Lam=pan|2 +landare|lan=da=re|2 +landat|lan=dat|2 +lappar|lap=par|2 +laptoppar|lap=top=par|2 +laptopparna|lap=top=par=na|2 +lastbil|last=bil|2 +lateralt|la=te=ralt|2 +latitud|la=ti=tud|2 +Leclercs|Leclercs|2 +ledningen|led=ning=en|2 +legering|le=ge=ring|2 +leksaker|lek=sa=ker|2 +leverera|le=ve=re=ra|2 +Liberia|Li=be=ria|2 +lidande|li=dan=de|2 +liggsår|ligg=sår|2 +likartade|lik=ar=ta=de|2 +Liksom|Lik=som|2 +limmet|lim=met|2 +linjer|lin=jer|2 +livlinefäste|liv=li=ne=fäs=te|2 +livsmedelsproduktion|livs=me=dels=pro=duk=tion|2 +LIVSUPPEHÅLL|LIVS=UP=PE=HÅLL|2 +ljudvågorna|ljud=vå=gor=na|2 +ljusblänk|ljus=blänk|2 +ljusbrunt|ljus=brunt|2 +ljusfiltren|ljus=filt=ren|2 +ljusglimten|ljus=glim=ten|2 +ljusminut|ljus=mi=nut|2 +ljusstyrka|ljus=styr=ka|2 +ljussvagare|ljus=sva=ga=re|2 +ljussäker|ljus=sä=ker|2 +lovande|lo=van=de|2 +luckans|luck=ans|2 +luckorna|luck=or=na|2 +luftslangen|luft=slang=en|2 +Luftslussporten|Luft=sluss=por=ten|2 +luftslussväggen|luft=sluss=väg=gen|2 +luftströmmen|luft=ström=men|2 +lufttemperatur|luft=tem=pe=ra=tur|2 +Lufttrycket|Luft=tryck=et|2 +lufttrycket|luft=tryck=et|2 +lufttät|luft=tät|2 +lufttäta|luft=tä=ta|2 +lugnande|lug=nan=de|2 +luktade|luk=ta=de|2 +Lyckas|Lyck=as|2 +Lysdioder|Lys=dio=der|2 +lägsta|lägs=ta|2 +läkande|lä=kan=de|2 +lämpad|läm=pad|2 +lämpligt|lämp=ligt|2 +länder|län=der|2 +länken|län=ken|2 +läpparna|läp=par=na|2 +läskig|läs=kig|2 +lättad|lät=tad|2 +lättast|lät=tast|2 +lätthet|lätt=het|2 +Långsamt|Lång=samt|2 +lökformiga|lök=for=mi=ga|2 +lösgör|lös=gör|2 +maggropen|maggro=pen|2 +magiskt|ma=giskt|2 +Magmatiska|Mag=ma=tis=ka|2 +magneten|mag=ne=ten|2 +magnetisk|mag=ne=tisk|2 +makaroner|ma=ka=ro=ner|2 +makter|mak=ter|2 +manliga|man=li=ga|2 +manöver|ma=nö=ver|2 +manövrera|ma=növ=re=ra|2 +manövrerar|ma=növ=re=rar|2 +marinhelikopter|ma=rin=he=li=kop=ter|2 +marinkåren|ma=rin=kåren|2 +markant|mar=kant|2 +markerad|mar=ke=rad|2 +masken|mas=ken|2 +maskiner|ma=ski=ner|2 +massivt|mas=sivt|2 +massproducera|mass=pro=du=ce=ra|2 +massvis|mass=vis|2 +matematiska|ma=te=ma=tis=ka|2 +matsmältningssystem|mat=smält=nings=sys=tem|2 +meddelade|med=de=la=de|2 +medför|med=för|2 +medhjälpare|med=hjäl=pa=re|2 +medicinering|me=di=ci=ne=ring|2 +medurs|med=urs|2 +medveten|med=ve=ten|2 +megajoule|me=ga=jou=le|2 +mejsel|mej=sel|2 +mekanisk|me=ka=nisk|2 +mellansträv|mel=lan=sträv|2 +meningsfullt|me=nings=fullt|2 +meningslös|me=nings=lös|2 +mentalt|men=talt|2 +merparten|mer=par=ten|2 +metallen|me=tal=len|2 +metallklot|me=tall=klot|2 +metallremsan|me=tall=rem=san|2 +metallskiva|me=tall=ski=va|2 +meterbreda|me=ter=bre=da|2 +migrera|mi=gre=ra|2 +mikrofon|mik=ro=fon|2 +mikrosekunder|mik=ro=se=kun=der|2 +mikroskopets|mik=ro=sko=pets|2 +militärerna|mi=li=tä=rer=na|2 +miljard|mil=jard|2 +miljöer|mil=jö=er|2 +miljöproblem|mil=jö=pro=blem|2 +minimalt|mi=ni=malt|2 +miniodlingen|mi=ni=od=ling=en|2 +miniräknare|mi=ni=räk=na=re|2 +minnesförlusten|min=nes=för=lus=ten|2 +minskade|mins=ka=de|2 +minskning|minsk=ning|2 +minskningen|minsk=ning=en|2 +mirakel|mi=ra=kel|2 +mischmasch|misch=masch|2 +missar|mis=sar|2 +misslyckades|miss=lyck=a=des|2 +Misslyckats|Miss=lyck=ats|2 +misstanke|miss=tan=ke|2 +misstänkt|miss=tänkt|2 +mittskärmen|mitt=skär=men|2 +modifierade|mo=di=fi=e=ra=de|2 +modifierat|mo=di=fi=e=rat|2 +modulerna|mo=du=ler=na|2 +molekyl|mo=le=kyl|2 +molnslöjor|moln=slö=jor|2 +monitor|mo=ni=tor|2 +monoton|mo=no=ton|2 +monterar|mon=te=rar|2 +Moskvatid|Mos=k=va=tid|2 +motoreffekt|mo=tor=ef=fekt|2 +Motorer|Mo=to=rer|2 +motorutblåsningen|mo=tor=ut=blås=ning=en|2 +motståndskraftiga|mot=stånds=kraf=ti=ga|2 +motståndslöst|mot=stånds=löst|2 +motsvarighet|mot=sva=rig=het|2 +mottagare|mot=ta=ga=re|2 +motverka|mot=ver=ka|2 +munter|mun=ter|2 +mängderna|mäng=der=na|2 +Människa|Män=ni=ska|2 +människan|män=ni=skan|2 +människokropp|män=ni=sko=kropp|2 +Människokroppen|Män=ni=sko=krop=pen|2 +människors|män=ni=skors|2 +människoskeppet|män=ni=sko=skep=pet|2 +Mänskliga|Mänsk=li=ga|2 +Mänsklighetens|Mänsk=lig=he=tens|2 +märkbar|märk=bar|2 +märkte|märk=te|2 +märkvärdigt|märk=vär=digt|2 +mätbar|mät=bar|2 +månaders|må=na=ders|2 +mångarmade|mång=ar=ma=de|2 +månlandningen|mån=land=ning=en|2 +möjlig|möj=lig|2 +mördande|mör=dan=de|2 +mördar|mör=dar|2 +mötesrummen|mö=tes=rum=men|2 +mötesrummet|mö=tes=rum=met|2 +namnlapp|namn=lapp|2 +nanoinjektionssprutor|na=noin=jek=tions=spru=tor|2 +naturkunskap|na=tur=kun=skap|2 +naturlig|na=tur=lig|2 +naturvetenskapslärare|na=tur=ve=ten=skaps=lä=ra=re|2 +nebulosor|ne=bu=lo=sor|2 +nedersta|ne=ders=ta|2 +nedräkningen|ned=räk=ning=en|2 +nedsänkt|ned=sänkt|2 +nedsänkta|ned=sänk=ta|2 +nerver|ner=ver|2 +neuralt|neu=ralt|2 +nittio|nit=tio|2 +nittionde|nit=tion=de|2 +nittiosex|nit=tio=sex|2 +nitton|nit=ton|2 +njutning|njut=ning|2 +Nobelpris|No=bel=pris|2 +Nordamerika|Nord=ame=ri=ka|2 +nordpol|nord=pol|2 +Normalt|Nor=malt|2 +normaltemperatur|nor=mal=tem=pe=ra=tur|2 +normalvarma|nor=mal=var=ma|2 +norskan|nors=kan|2 +noskonen|no=s=ko=nen|2 +Noterat|No=te=rat|2 +nuförtiden|nu=för=ti=den|2 +nyckeln|nyck=eln|2 +nyligen|ny=li=gen|2 +Nynnandet|Nyn=nan=det|2 +näringskedjan|nä=rings=ked=jan|2 +Näringstilldelningen|Nä=rings=till=del=ning=en|2 +nävarna|nä=var=na|2 +någorlunda|nå=gor=lun=da|2 +nödsituation|nöd=si=tu=a=tion|2 +nödsystemet|nöd=sys=te=met|2 +nödventil|nöd=ven=til|2 +oaktsamhet|oakt=sam=het|2 +oavsett|oav=sett|2 +oavsiktliga|oav=sikt=li=ga|2 +obefintlig|obe=fint=lig|2 +obegränsade|obe=grän=sa=de|2 +obegränsat|obe=grän=sat|2 +obehindrat|obe=hind=rat|2 +obemannad|obe=man=nad|2 +oberoende|obe=ro=en=de|2 +Oberoende|Obe=ro=en=de|2 +obetydligt|obe=tyd=ligt|2 +Objektets|Ob=jek=tets|2 +Objektglaset|Ob=jekt=gla=set|2 +observation|ob=ser=va=tion|2 +oddsen|odd=sen|2 +odelade|ode=la=de|2 +odlade|od=la=de|2 +odlingen|od=ling=en|2 +Odlingstank|Od=lingstank|2 +odlingstanken|od=lingstan=ken|2 +ofarligt|ofar=ligt|2 +offentliggjorde|of=fent=lig=gjor=de|2 +officerare|of=fi=ce=ra=re|2 +offrade|off=ra=de|2 +oftare|of=ta=re|2 +oförstående|oför=stå=en=de|2 +oförstörbart|oför=stör=bart|2 +oförutsebart|oför=ut=se=bart|2 +oinskränkt|oin=skränkt|2 +oinstallerade|oin=stal=le=ra=de|2 +ojämna|ojäm=na|2 +okontrollerat|okon=trol=le=rat|2 +oktaedern|ok=ta=e=dern|2 +oktaver|ok=ta=ver|2 +okularen|oku=la=ren|2 +okända|okän=da|2 +omfång|om=fång|2 +omgivande|om=gi=van=de|2 +omloppsbanehastighet|om=lopps=ba=ne=has=tig=het|2 +omringades|om=ring=a=des|2 +Området|Om=rå=det|2 +omsorgsfull|om=sorgs=full|2 +omvandlas|om=vand=las|2 +omärkligt|omärk=ligt|2 +Omöjligt|Omöj=ligt|2 +oorganiska|oor=ga=nis=ka|2 +operation|ope=ra=tion|2 +operationer|ope=ra=tio=ner|2 +optimala|op=ti=ma=la|2 +opålitliga|opå=lit=li=ga|2 +orange|oran=ge|2 +ordalag|or=da=lag|2 +ordentlig|or=dent=lig|2 +oregelbundet|ore=gel=bun=det|2 +organeller|or=ga=nel=ler|2 +organism|or=ga=nism|2 +orienterar|ori=en=te=rar|2 +oroade|oro=a=de|2 +oroande|oro=an=de|2 +orolig|oro=lig|2 +orsakats|or=sa=kats|2 +orsaken|or=sa=ken|2 +orörlig|orör=lig|2 +oskarp|oskarp|2 +ostadig|osta=dig|2 +otaliga|ota=li=ga|2 +otydligt|otyd=ligt|2 +otäcka|otäcka|2 +ovanliga|ovan=li=ga|2 +ovetenskapligt|ove=ten=skap=ligt|2 +oväsendet|ovä=sen=det|2 +packningen|pack=ning=en|2 +pallen|pal=len|2 +Pappskalle|Papp=skal=le|2 +Paraguay|Pa=ra=gu=ay|2 +parallelltrapetsen|pa=ral=lell=tra=pet=sen|2 +parbildning|par=bild=ning|2 +parkeringsplatsen|par=ke=rings=plat=sen|2 +passagerarplan|pas=sa=ge=rar=plan|2 +passerade|pas=se=ra=de|2 +passning|pass=ning|2 +patenterade|pa=ten=te=ra=de|2 +patient|pa=ti=ent|2 +Patienten|Pa=ti=en=ten|2 +patienten|pa=ti=en=ten|2 +patienter|pa=ti=en=ter|2 +pendel|pen=del|2 +pendelrörelser|pen=del=rö=rel=ser|2 +pentagon|pen=ta=gon|2 +pentagonalt|pen=ta=go=nalt|2 +pentagoner|pen=ta=go=ner|2 +periapsis|pe=ri=apsis|2 +periodiska|pe=ri=o=dis=ka|2 +permanent|per=ma=nent|2 +personalen|per=so=na=len|2 +personen|per=so=nen|2 +personerna|per=so=ner=na|2 +personers|per=so=ners|2 +personlighet|per=son=lig=het|2 +petade|pe=ta=de|2 +Petrovakommittén|Pe=tro=va=kom=mit=tén|2 +Petrovalinjens|Pe=tro=va=lin=jens|2 +Petrovalinjer|Pe=tro=va=lin=jer|2 +Petrovaljus|Pe=tro=va=ljus|2 +Petrovaskopskärmen|Pe=tro=vas=kop=skär=men|2 +piggnar|pigg=nar|2 +pistol|pi=stol|2 +Planen|Pla=nen|2 +planer|pla=ner|2 +planerade|pla=ne=ra=de|2 +Planeter|Pla=ne=ter|2 +planets|pla=nets|2 +plastcirklar|plast=cirk=lar|2 +plasten|plas=ten|2 +plastlådor|plast=lå=dor|2 +plastlådorna|plast=lå=dor=na|2 +plastpåse|plast=på=se|2 +pluggade|plug=ga=de|2 +pluggar|plug=gar|2 +plågsamma|plåg=sam=ma|2 +plåten|plå=ten|2 +politiker|po=li=ti=ker|2 +population|po=pu=la=tion|2 +Populationen|Po=pu=la=tio=nen|2 +portal|por=tal|2 +portfölj|port=följ|2 +portföljen|port=föl=jen|2 +positiv|po=si=tiv|2 +potent|po=tent|2 +potentiell|po=ten=ti=ell|2 +potentiella|po=ten=ti=el=la|2 +poänglöst|po=äng=löst|2 +precision|pre=ci=sion|2 +Predatorn|Pre=da=torn|2 +presenter|pre=sen=ter|2 +pressas|pres=sas|2 +prickade|prick=a=de|2 +Prickarna|Prick=ar=na|2 +pricken|prick=en|2 +privata|pri=va=ta|2 +privilegier|pri=vi=le=gi=er|2 +procenten|pro=cen=ten|2 +produkter|pro=duk=ter|2 +progression|pro=gres=sion|2 +projektsymbolen|pro=jekt=sym=bo=len|2 +pronomen|pro=no=men|2 +proportionerna|pro=por=tio=ner=na|2 +proppfull|propp=full|2 +proton|pro=ton|2 +protonernas|pro=to=ner=nas|2 +Provet|Pro=vet|2 +Provtagaren|Prov=ta=ga=ren|2 +Provtagarens|Prov=ta=ga=rens|2 +prylen|pry=len|2 +psykologiska|psy=ko=lo=gis=ka|2 +publicerats|pu=bli=ce=rats|2 +pulver|pul=ver|2 +Pumpen|Pum=pen|2 +punkter|punk=ter|2 +pyttelilla|pyt=te=lil=la|2 +pyttesmå|pyt=te=små|2 +påfrestningar|på=frest=ning=ar|2 +radarpanelen|ra=dar=pa=ne=len|2 +radarsystem|ra=dar=sys=tem|2 +radiosändare|ra=dio=sän=da=re|2 +ramlade|ram=la=de|2 +Random|Ran=dom|2 +rapporter|rap=por=ter|2 +rapportera|rap=por=te=ra|2 +rationellt|ra=tio=nellt|2 +reagerade|re=a=ge=ra=de|2 +reaktionen|re=ak=tio=nen|2 +referensmaterial|re=fe=rens=ma=te=ri=al|2 +referenspunkt|re=fe=rens=punkt|2 +reflekterade|re=flek=te=ra=de|2 +reflekterat|re=flek=te=rat|2 +regelbundet|re=gel=bun=det|2 +regering|re=ge=ring|2 +regeringar|re=ge=ring=ar|2 +regimerna|re=gi=mer=na|2 +rekalibrering|re=ka=lib=re=ring|2 +rektangel|rek=tang=el|2 +rektangeln|rek=tang=eln|2 +rektanglar|rek=tang=lar|2 +RELATIV|RE=LA=TIV|2 +relativistisk|re=la=ti=vis=tisk|2 +relativiteten|re=la=ti=vi=te=ten|2 +relevant|re=le=vant|2 +relevanta|re=le=van=ta|2 +religion|re=li=gi=on|2 +remmar|rem=mar|2 +rensade|ren=sa=de|2 +reparerar|re=pa=re=rar|2 +repareras|re=pa=re=ras|2 +reparerat|re=pa=re=rat|2 +representerar|re=pre=sen=te=rar|2 +resenär|re=se=när|2 +Resultat|Re=sul=tat|2 +Resultatet|Re=sul=ta=tet|2 +revolutionen|re=vo=lu=tio=nen|2 +ribbor|rib=bor|2 +ringde|ring=de|2 +ringen|ring=en|2 +ritning|rit=ning|2 +Robert|Robert|2 +robotarmar|ro=bot=armar|2 +robotens|ro=bo=tens|2 +Rotationen|Ro=ta=tio=nen|2 +rotationscentrum|ro=ta=tions=cen=trum|2 +rotationshastighet|ro=ta=tions=has=tig=het|2 +Rotera|Ro=te=ra|2 +roterade|ro=te=ra=de|2 +rudimentärt|ru=di=men=tärt|2 +rullning|rull=ning|2 +rumskompis|rums=kom=pis|2 +rumstempererad|rums=tem=pe=re=rad|2 +ruttnande|rutt=nan=de|2 +ryggar|ryg=gar|2 +ryktbart|rykt=bart|2 +rymdalger|rym=dal=ger|2 +Rymddräkten|Rymd=dräk=ten|2 +rymddräktshandskar|rymd=dräkts=hands=kar|2 +rymdfarande|rymd=fa=ran=de|2 +rymdfart|rymd=fart|2 +rymdprogram|rymd=pro=gram|2 +rymdpromenaden|rymd=pro=me=na=den|2 +rymdstoft|rymd=stoft|2 +rymmer|rym=mer|2 +rynkar|ryn=kar|2 +ryssen|rys=sen|2 +rädsla|räds=la|2 +rädslan|räds=lan|2 +Räknar|Räk=nar|2 +räknare|räk=na=re|2 +ränder|rän=der|2 +rättar|rät=tar|2 +rättegången|rät=te=gång=en|2 +rättframt|rätt=framt|2 +rättssalen|rätts=sa=len|2 +rättvis|rätt=vis|2 +rättvisans|rätt=vi=sans|2 +rättvist|rätt=vist|2 +röntgenstrålar|rönt=gen=strå=lar|2 +rörande|rö=ran=de|2 +RÖRELSE|RÖ=REL=SE|2 +rörelsemängd|rö=rel=se=mängd|2 +röstläge|röst=lä=ge|2 +sakerna|sa=ker=na|2 +Saktar|Sak=tar|2 +samarbeta|sam=ar=be=ta|2 +samarbetade|sam=ar=be=ta=de|2 +samlingen|sam=ling=en|2 +sammanbundna|sam=man=bund=na|2 +sammanhang|sam=man=hang|2 +sammanhållet|sam=man=hål=let|2 +sammanlänkande|sam=man=län=kan=de|2 +Samtalet|Sam=ta=let|2 +Samtliga|Samt=li=ga|2 +sannolika|san=no=li=ka|2 +sarkasm|sar=kasm|2 +satsar|sat=sar|2 +sattes|sat=tes|2 +Saturnus|Sa=tur=nus|2 +scenariot|sce=na=ri=ot|2 +scheman|sche=man|2 +schemat|sche=mat|2 +schyst|schyst|2 +Schyst|Schyst|2 +sedimentära|se=di=men=tä=ra|2 +sektionen|sek=tio=nen|2 +sekunds|se=kunds|2 +sekundär|se=kun=där|2 +sekundära|se=kun=dä=ra|2 +sekvens|se=kvens|2 +semester|se=mes=ter|2 +Senare|Se=na=re|2 +separation|se=pa=ra=tion|2 +separerade|se=pa=re=ra=de|2 +seriös|se=ri=ös|2 +servering|ser=ve=ring|2 +servitrisen|ser=vi=tri=sen|2 +sexhundra|sex=hun=dra|2 +Sextiotusen|Sex=tio=tu=sen|2 +signalen|sig=na=len|2 +signalerar|sig=na=le=rar|2 +signatur|sig=na=tur|2 +silversked|sil=ver=sked|2 +simulera|si=mu=le=ra|2 +simulerar|si=mu=le=rar|2 +sinnade|sin=na=de|2 +sinnessjuk|sin=nes=sjuk|2 +Sirius|Si=ri=us|2 +Sitter|Sit=ter|2 +sjuhundra|sju=hun=dra|2 +Sjukdom|Sjuk=dom|2 +sjukdomen|sjuk=do=men|2 +sjunkande|sjun=kan=de|2 +sjuttiofem|sjut=tio=fem|2 +sjuttiotvå|sjut=tio=två|2 +Sjutton|Sjut=ton|2 +självbelåtet|själv=be=lå=tet|2 +självmord|själv=mord|2 +självmordsuppdraget|själv=mordsupp=dra=get|2 +självständigt|själv=stän=digt|2 +sjöfarare|sjö=fa=ra=re|2 +sjömännen|sjö=män=nen|2 +skaffade|skaf=fa=de|2 +skaffat|skaf=fat|2 +skalbaggen|skal=bag=gen|2 +skalbaggssonderna|skal=baggs=son=der=na|2 +skannar|skan=nar|2 +skapat|ska=pat|2 +skarpare|skar=pa=re|2 +skarpt|skarpt|2 +skelett|ske=lett|2 +skenbenet|sken=be=net|2 +skeppsingenjör|skepps=in=gen=jör|2 +skickligt|skick=ligt|2 +skildes|skil=des|2 +skjutas|skju=tas|2 +skjutit|skju=tit|2 +skolan|sko=lan|2 +skramlar|skram=lar|2 +skrapade|skra=pa=de|2 +skratt|skratt|2 +skriftspråk|skrift=språk|2 +skript|skript|2 +skrivet|skri=vet|2 +skrockade|skrock=a=de|2 +skrovliga|skrov=li=ga|2 +skrovmaterial|skrov=ma=te=ri=al|2 +skräck|skräck|2 +skräckslagen|skräck=sla=gen|2 +skurit|sku=rit|2 +skuttar|skut=tar|2 +skyddet|skyd=det|2 +skyddsdräkter|skydds=dräk=ter|2 +Skynda|Skyn=da|2 +Skälet|Skä=let|2 +skänkt|skänkt|2 +skärmens|skär=mens|2 +skärskådar|skär=skå=dar|2 +skörda|skör=da|2 +skötte|sköt=te|2 +slagit|sla=git|2 +slangen|slang=en|2 +slarvig|slar=vig|2 +slingra|sling=ra|2 +slipat|sli=pat|2 +slokar|slo=kar|2 +slumpartade|slump=ar=ta=de|2 +slumpen|slum=pen|2 +slunga|slunga|2 +slungas|slung=as|2 +sluppit|slup=pit|2 +slussar|slus=sar|2 +slussportarna|sluss=por=tar=na|2 +slutgiltiga|slut=gil=ti=ga|2 +slutna|slut=na|2 +slutresultatet|slut=re=sul=ta=tet|2 +släktet|släk=tet|2 +släktingar|släk=ting=ar|2 +slängt|slängt|2 +släpat|slä=pat|2 +släpps|släpps|2 +smattrar|smatt=rar|2 +smiter|smi=ter|2 +smockar|smock=ar|2 +smutsiga|smut=si=ga|2 +smuttade|smut=ta=de|2 +smällare|smäl=la=re|2 +smällde|smäll=de|2 +smältpunkt|smält=punkt|2 +smärtan|smär=tan|2 +smärtfritt|smärt=fritt|2 +smörjan|smör=jan|2 +Snabbare|Snab=ba=re|2 +snavade|sna=va=de|2 +sneglade|sneg=la=de|2 +sniffar|snif=far|2 +snillrik|snill=rik|2 +snillrika|snill=ri=ka|2 +Snurrar|Snur=rar|2 +snyggt|snyggt|2 +Snälla|Snäl=la|2 +sockerarter|sock=er=ar=ter|2 +soldater|sol=da=ter|2 +Soldaterna|Sol=da=ter=na|2 +solenergin|so=l=ener=gin|2 +solfläckarna|sol=fläck=ar=na|2 +Solfläckarna|Sol=fläck=ar=na|2 +solitt|so=litt|2 +solpartiklarna|sol=par=tik=lar=na|2 +solstrålningen|sol=strål=ning=en|2 +sonder|son=der|2 +sonderna|son=der=na|2 +sorgen|sor=gen|2 +sorter|sor=ter|2 +sortera|sor=te=ra|2 +Sovjet|Sov=jet|2 +spaghetti|spa=ghet=ti|2 +spalter|spal=ter|2 +Spanien|Spa=ni=en|2 +sparat|spa=rat|2 +Specificera|Spe=ci=fi=ce=ra|2 +speglar|speg=lar|2 +spekulativa|spe=ku=la=ti=va|2 +Spelar|Spe=lar|2 +spillprodukter|spill=pro=duk=ter|2 +spindriftmotorer|spindrift=mo=to=rer|2 +spindriftskonsolen|spindrifts=kon=so=len|2 +spindriftsmotorer|spindrift=smo=to=rer|2 +splitsning|splits=ning|2 +spotta|spot=ta|2 +sprida|spri=da|2 +spruckna|spruck=na|2 +sprängde|spräng=de|2 +sprängdes|spräng=des|2 +spänns|spänns|2 +spärrar|spär=rar|2 +stammade|stam=ma=de|2 +Stanna|Stan=na|2 +stapplande|stapp=lan=de|2 +starkaste|star=kas=te|2 +startade|star=ta=de|2 +Startar|Star=tar|2 +staten|sta=ten|2 +stater|sta=ter|2 +STATUS|STA=TUS|2 +stavar|sta=var|2 +stegade|ste=ga=de|2 +steget|ste=get|2 +stegpinnarna|steg=pin=nar=na|2 +stenarna|ste=nar=na|2 +steril|ste=ril|2 +stinker|stin=ker|2 +stjälk|stjälk|2 +Stjärnorna|Stjär=nor=na|2 +stolar|sto=lar|2 +stoppat|stop=pat|2 +strecket|streck=et|2 +strimma|strim=ma|2 +strukturerade|struk=tu=re=ra=de|2 +strukturskärm|struk=tur=skärm|2 +strunta|strun=ta|2 +Strupen|Stru=pen|2 +strupen|stru=pen|2 +sträng|sträng|2 +stråla|strå=la|2 +stråle|strå=le|2 +strålkastare|strål=kas=ta=re|2 +Strålning|Strål=ning|2 +Strålningen|Strål=ning=en|2 +strålningssjuka|strål=nings=sju=ka|2 +ströva|strö=va|2 +studerade|stu=de=ra=de|2 +stunds|stunds|2 +städat|stä=dat|2 +städer|stä=der|2 +ställen|stäl=len|2 +stämband|stäm=band|2 +stängd|stängd|2 +stängdes|stäng=des|2 +stållåda|stållå=da|2 +stöder|stö=der|2 +stödribbor|stöd=rib=bor|2 +störningar|stör=ning=ar|2 +Större|Stör=re|2 +störtar|stör=tar|2 +stötte|stöt=te|2 +substans|sub=stans|2 +sugrör|sug=rör|2 +supertaumöbor|su=per=t=au=mö=bor|2 +surrande|sur=ran=de|2 +svajande|sva=jan=de|2 +svalde|sval=de|2 +svalna|sval=na|2 +svällande|sväl=lan=de|2 +svälter|sväl=ter|2 +svängningar|sväng=ning=ar|2 +svängningstiden|sväng=nings=ti=den|2 +symmetrisk|sym=me=trisk|2 +synfältet|syn=fäl=tet|2 +synpunkter|syn=punk=ter|2 +sysslar|syss=lar|2 +säckar|säck=ar|2 +säkerhetsbältet|sä=ker=hets=bäl=tet|2 +säkrare|säk=ra=re|2 +sällsynt|säll=synt|2 +Sängen|Säng=en|2 +sängkläder|säng=klä=der|2 +sänker|sän=ker|2 +särdrag|sär=drag|2 +säregen|sär=egen|2 +säregna|sär=eg=na|2 +särskilda|sär=skil=da|2 +Sådant|Så=dant|2 +Såvida|Så=vi=da|2 +sökmönster|sök=möns=ter|2 +tabell|ta=bell|2 +tackar|tack=ar|2 +tafatt|ta=fatt|2 +takbubbla|tak=bubb=la|2 +Tankarna|Tan=kar=na|2 +taumöbalarm|tau=mö=ba=larm|2 +taumöbaodlingarna|tau=mö=ba=od=ling=ar=na|2 +taumöbapopulationen|tau=mö=bapo=pu=la=tio=nen|2 +taumöbastam|tau=mö=bastam|2 +taumöbatankarna|tau=mö=ba=tan=kar=na|2 +tecknad|teck=nad|2 +tecknar|teck=nar|2 +tejpat|tej=pat|2 +Teknikerna|Tek=ni=ker=na|2 +tekniska|tek=nis=ka|2 +Teleskopet|Te=le=sko=pet|2 +television|te=le=vi=sion|2 +Temperaturen|Tem=pe=ra=tu=ren|2 +temperaturkänsliga|tem=pe=ra=tur=käns=li=ga|2 +temporära|tem=po=rä=ra|2 +tenderar|ten=de=rar|2 +Thailand|Thai=land|2 +Theresa|The=re=sa|2 +tidsdilatation|tids=di=la=ta=tion|2 +tidsenhet|tids=en=het|2 +tillfällen|till=fäl=len|2 +tillföra|till=fö=ra|2 +tillgänglig|till=gäng=lig|2 +tillgången|till=gång=en|2 +tillhandahålla|till=han=da=hål=la|2 +tillhandahåller|till=han=da=hål=ler|2 +tillhörde|till=hör=de|2 +tillräckliga|till=räck=li=ga|2 +tillträde|till=trä=de|2 +tillverkas|till=ver=kas|2 +tillverkats|till=ver=kats|2 +timmars|tim=mars|2 +tingest|ting=est|2 +tingestarna|ting=es=tar=na|2 +tinningarna|tin=ning=ar=na|2 +tiondels|ti=on=dels|2 +tiotusen|tio=tu=sen|2 +tittat|tit=tat|2 +tjockare|tjock=a=re|2 +tjugondedel|tju=gon=de=del|2 +tjugonio|tju=go=nio|2 +tjugoniondedel|tju=go=ni=on=de=del|2 +tjugotredje|tju=go=tred=je|2 +tjugotvå|tju=go=två|2 +tjuter|tju=ter|2 +tjänst|tjänst|2 +tjänstgjorde|tjänst=gjor=de|2 +toalett|toa=lett|2 +tolkar|tol=kar|2 +tomrummet|tom=rum=met|2 +tonhöjd|ton=höjd|2 +torkade|tor=ka=de|2 +torkat|tor=kat|2 +transistorer|tran=sis=to=rer|2 +transparent|trans=pa=rent|2 +transportera|trans=por=te=ra|2 +trasiga|tra=si=ga|2 +trasigt|tra=sigt|2 +traska|tras=ka|2 +traskar|tras=kar|2 +trauma|trau=ma|2 +traven|tra=ven|2 +tredubbla|tre=dubb=la|2 +trehundra|tre=hun=dra|2 +trerövade|trer=ö=va=de|2 +trettioen|tret=tio=en|2 +trettiosex|tret=tio=sex|2 +trettiotvå|tret=tio=två|2 +trevar|tre=var|2 +trevligt|trev=ligt|2 +triangulär|tri=an=gu=lär|2 +triangulära|tri=an=gu=lä=ra|2 +trillar|tril=lar|2 +troligtvis|tro=ligt=vis|2 +tryckas|tryck=as|2 +tryckbehållare|tryck=be=hål=la=re|2 +tryckkammare|tryck=kam=ma=re|2 +tryckkraft|tryck=kraft|2 +tryckkärlet|tryck=kär=let|2 +tryckluft|tryck=luft|2 +träffades|träf=fa=des|2 +tränger|träng=er|2 +träpinnar|trä=pin=nar|2 +trådarna|trå=dar=na|2 +tröghetsreferensram|trög=hets=re=fe=rens=ram|2 +tummarna|tum=mar=na|2 +tungan|tung=an|2 +tunnare|tun=na=re|2 +tunneldel|tun=nel=del|2 +tunnelsystem|tun=nel=sys=tem|2 +tunnlarna|tunn=lar=na|2 +turbulens|tur=bu=lens|2 +tvekar|tve=kar|2 +tveklöst|tvek=löst|2 +tvingade|tving=a=de|2 +tvingades|tving=a=des|2 +tvingat|tving=at|2 +tvättar|tvät=tar|2 +tvättäkta|tvätt=äk=ta|2 +tvåhundratusen|två=hun=dra=tu=sen|2 +Tycker|Tyck=er|2 +tydlig|tyd=lig|2 +tyngden|tyng=den|2 +typiska|ty=pis=ka|2 +tystnade|tyst=na=de|2 +tätare|tä=ta=re|2 +tålamod|tå=la=mod|2 +underhåll|un=der=håll|2 +underhållning|un=der=håll=ning|2 +underifrån|un=der=i=från|2 +underliggande|un=der=lig=gan=de|2 +undermedvetna|un=der=med=vet=na|2 +undervisa|un=der=vi=sa|2 +undrat|und=rat|2 +ungarnas|ung=ar=nas|2 +ungefärliga|un=ge=fär=li=ga|2 +universitetet|uni=ver=si=te=tet|2 +UPPDRAG|UPP=DRAG|2 +uppdragets|upp=dra=gets|2 +uppdragssymbol|upp=drags=sym=bol|2 +uppenbarelse|uppen=ba=rel=se|2 +uppfattades|upp=fat=ta=des|2 +uppfattar|upp=fat=tar|2 +uppfostrad|upp=fost=rad|2 +upphetsning|upp=hets=ning|2 +upphov|upp=hov|2 +uppkopplade|upp=kopp=la=de|2 +upplevde|upp=lev=de|2 +upplevelse|upp=le=vel=se|2 +upplysande|upp=ly=san=de|2 +UPPMÄTT|UPP=MÄTT|2 +uppmätt|upp=mätt|2 +uppnådd|upp=nådd|2 +uppnått|upp=nått|2 +upprörda|upp=rör=da|2 +uppsats|upp=sats|2 +uppskattad|upp=skat=tad|2 +uppskattning|upp=skatt=ning|2 +uppskjutning|upp=skjut=ning|2 +uppskjutningen|upp=skjut=ning=en|2 +uppspelt|upp=spelt|2 +uppstått|upp=stått|2 +uppsättningar|upp=sätt=ning=ar|2 +uppsättningen|upp=sätt=ning=en|2 +upptar|upp=tar|2 +uppvisar|upp=vi=sar|2 +urkopplad|ur=kopp=lad|2 +Ursprungligen|Ur=sprung=li=gen|2 +urtavlan|ur=tav=lan|2 +urusla|ur=usla|2 +Utanför|Ut=an=för|2 +utbilda|ut=bil=da|2 +utbrister|ut=bris=ter|2 +utbytbara|ut=byt=ba=ra|2 +uteslutande|ute=slu=tan=de|2 +utformade|ut=for=ma=de|2 +utformades|ut=for=ma=des|2 +utforskat|ut=fors=kat|2 +utgick|ut=gick|2 +utgjorde|ut=gjor=de|2 +utgångar|ut=gång=ar|2 +utgångspunkt|ut=gångs=punkt|2 +utgörs|ut=görs|2 +Utifrån|Ut=i=från|2 +utjämnat|ut=jäm=nat|2 +utmattad|ut=mat=tad|2 +utnyttja|ut=nytt=ja|2 +Utomjordingen|Ut=om=jor=ding=en|2 +Utomjordingens|Ut=om=jor=ding=ens|2 +utrota|ut=ro=ta|2 +uträkningar|ut=räk=ning=ar|2 +utsidan|ut=si=dan|2 +utstråla|ut=strå=la|2 +utstrålas|ut=strå=las|2 +utstrålning|ut=strål=ning|2 +utsökt|ut=sökt|2 +uttalade|ut=ta=la=de|2 +uttalas|ut=ta=las|2 +uttryck|ut=tryck|2 +utvidgas|ut=vid=gas|2 +utvilad|ut=vi=lad|2 +vakuumbehållare|va=ku=um=be=hål=la=re|2 +vakuumbehållaren|va=ku=um=be=hål=la=ren|2 +valvbåge|valv=bå=ge|2 +vanligast|van=li=gast|2 +vanligtvis|van=ligt=vis|2 +vansinne|van=sin=ne|2 +vansinnet|van=sin=net|2 +varandras|var=and=ras|2 +variationer|va=ri=a=tio=ner|2 +variera|va=ri=e=ra|2 +varierande|va=ri=e=ran=de|2 +varnande|var=nan=de|2 +varnar|var=nar|2 +varningen|var=ning=en|2 +varsitt|var=sitt|2 +Varsågod|Var=så=god|2 +Vartenda|Vart=en=da|2 +vattenbaserade|vat=ten=ba=se=ra=de|2 +vattnas|vatt=nas|2 +vektorerna|vek=to=rer=na|2 +Verktyg|Verk=tyg|2 +verktygslådan|verk=tygs=lå=dan|2 +Vetenskap|Ve=ten=skap|2 +vetenskapsexpert|ve=ten=skap=s=ex=pert|2 +vetenskapsman|ve=ten=skaps=man|2 +vetenskapsmänniska|ve=ten=skaps=män=ni=ska|2 +vetenskapsutrustning|ve=ten=skaps=ut=rust=ning|2 +vetskapen|vet=ska=pen|2 +vetter|vet=ter|2 +vettet|vet=tet|2 +vettig|vet=tig|2 +vibrationer|vib=ra=tio=ner|2 +Vidare|Vi=da=re|2 +videon|vi=de=on|2 +villkor|vill=kor|2 +vinddraget|vind=dra=get|2 +vindlande|vind=lan=de|2 +Vinkeln|Vin=keln|2 +vinner|vin=ner|2 +vippströmbrytaren|vipp=ström=bry=ta=ren|2 +visades|vi=sa=des|2 +visserligen|vis=ser=li=gen|2 +Visstja|Vis=s=tja|2 +vittnar|vitt=nar|2 +vodkaglas|vod=kag=las|2 +vodkan|vod=kan|2 +vokabulären|vo=ka=bu=lä=ren|2 +votering|vo=te=ring|2 +vridning|vrid=ning|2 +vridningen|vrid=ning=en|2 +vrålar|vrå=lar|2 +väderförhållanden|vä=der=för=hål=lan=den|2 +vädermönstren|vä=der=mönst=ren|2 +vädret|väd=ret|2 +väggar|väg=gar|2 +väggfästen|vägg=fäs=ten|2 +välbekant|väl=be=kant|2 +väldig|väl=dig|2 +väldiga|väl=di=ga|2 +Väldigt|Väl=digt|2 +välförsett|väl=för=sett|2 +välklädda|väl=kläd=da|2 +VÄLKOMMEN|VÄL=KOM=MEN|2 +vänligt|vän=ligt|2 +väntan|vän=tan|2 +väntande|vän=tan=de|2 +Väntat|Vän=tat|2 +värdena|vär=de=na|2 +värker|vär=ker|2 +Världen|Värl=den|2 +världsbefolkningen|världs=be=folk=ning=en|2 +världsledare|världs=le=da=re|2 +värmeelementet|vär=me=ele=men=tet|2 +värmeenergin|vär=me=ener=gin|2 +värmesystemet|vär=me=sys=te=met|2 +väsandet|vä=san=det|2 +västra|väst=ra|2 +vätebomb|vä=te=bomb|2 +vätejoner|vä=te=jo=ner|2 +vätskan|väts=kan|2 +växthusgas|växt=hus=gas|2 +växthusgaser|växt=hus=ga=ser|2 +våglängderna|våg=läng=der=na|2 +vårdkoj|vård=koj|2 +whiskyglas|whis=kyg=las|2 +Wikipedia|Wiki=pe=dia|2 +xenonitbubbla|xe=no=nit=bubb=la|2 +Xenoniten|Xe=no=ni=ten|2 +xenonitväggen|xe=no=nit=väg=gen|2 +yrkanden|yr=kan=den|2 +ytterdörr|yt=ter=dörr|2 +ytterhöljet|yt=ter=höl=jet|2 +ytterst|yt=terst|2 +ädelgas|ädel=gas|2 +äldsta|älds=ta|2 +ätliga|ät=li=ga|2 +äventyr|även=tyr|2 +århundradets|år=hun=dra=dets|2 +åstadkommit|åstad=kom=mit|2 +återgått|åter=gått|2 +återkommer|åter=kom=mer|2 +återstoden|åter=sto=den|2 +ÅTGÄRD|ÅT=GÄRD|2 +åtskilliga|åt=skil=li=ga|2 +Ögonlocken|Ögon=lock=en|2 +ökningen|ök=ning=en|2 +öppnat|öpp=nat|2 +öppningarna|öpp=ning=ar=na|2 +överdos|över=dos|2 +överdrivna|över=driv=na|2 +överenskommelsen|över=ens=kom=mel=sen|2 +övergick|över=gick|2 +överlevnad|över=lev=nad|2 +överlägset|över=läg=set|2 +överlämnar|över=läm=nar|2 +överraskning|över=rask=ning|2 +överraskningar|över=rask=ning=ar|2 +översätta|över=sät=ta|2 +översållat|över=sål=lat|2 +övertryck|över=tryck|2 +övertyga|över=ty=ga|2 +övriga|öv=ri=ga|2 +övrigt|öv=rigt|2 +илюхина|илюхина|2 +abnorma|ab=nor=ma|1 +accelerationerna|ac=ce=le=ra=tio=ner=na|1 +accelerationskraften|ac=ce=le=ra=tions=kraf=ten|1 +accelerationsläge|ac=ce=le=ra=tions=lä=ge|1 +accelerationslängd|ac=ce=le=ra=tions=längd|1 +accelerationsäventyr|ac=ce=le=ra=tions=även=tyr|1 +Accelerera|Ac=ce=le=re=ra|1 +accelereration|ac=ce=le=re=ra=tion|1 +accelerometer|ac=ce=le=ro=me=ter|1 +accent|ac=cent|1 +acceptabel|ac=cep=ta=bel|1 +acceptabelt|ac=cep=ta=belt|1 +accepteras|ac=cep=te=ras|1 +acklimatiseras|ac=kli=ma=ti=se=ras|1 +ackorden|ac=kor=den|1 +ackordet|ac=kor=det|1 +adderar|ad=de=rar|1 +administratörer|ad=mi=ni=stra=tö=rer|1 +adrenalinrusningen|ad=re=nal=in=rus=ning=en|1 +Adrianliv|Ad=ri=an=liv|1 +Adriansken|Ad=ri=ans=ken|1 +Adrianäventyr|Ad=ri=a=nä=ven=tyr|1 +advokat|ad=vo=kat|1 +advokaten|ad=vo=ka=ten|1 +aerodynamik|aero=dy=na=mik|1 +aerodynamikens|aero=dy=na=mi=kens|1 +afrikansk|af=ri=kansk|1 +afrikanske|af=ri=kans=ke|1 +Afrikas|Af=ri=kas|1 +agapaddorna|aga=pad=dor=na|1 +agenterna|ag=en=ter=na|1 +aggregaten|ag=gre=ga=ten|1 +aggregationstillståndsföränderligt|ag=gre=ga=tions=till=stånds=för=än=der=ligt|1 +aggressiva|ag=gres=si=va|1 +aggressivt|ag=gres=sivt|1 +akronym|akro=nym|1 +aktuellt|ak=tu=ellt|1 +Albert|Al=bert|1 +alfabeten|al=fa=be=ten|1 +Algerna|Al=ger=na|1 +ALLDELES|ALL=DE=LES|1 +allierade|al=li=e=ra=de|1 +allmän|all=män|1 +allmänhet|all=män=het|1 +allmänheten|all=män=he=ten|1 +alstrar|alst=rar|1 +alstras|alst=ras|1 +alstringen|alst=ring=en|1 +alternativa|al=ter=na=ti=va|1 +alternativknapparna|al=ter=na=tiv=knap=par=na|1 +Aluminium|Alu=mi=ni=um|1 +aluminiumblock|alu=mi=ni=um=block|1 +aluminiumfolie|alu=mi=ni=um=fo=lie|1 +Aluminiumplåt|Alu=mi=ni=ump=låt|1 +aluminiumplåten|alu=mi=ni=ump=lå=ten|1 +aluminiumskrov|alu=mi=ni=um=skrov|1 +aluminiumspånen|alu=mi=ni=um=spå=nen|1 +Amaterasu|Ama=te=ra=su|1 +amatör|ama=tör|1 +amatörastronomer|ama=tö=rast=ro=no=mer|1 +amatörerna|ama=tö=rer=na|1 +amatörgenererade|ama=tör=ge=ne=re=ra=de|1 +amatörmässiga|ama=tör=mäs=si=ga|1 +Ambulerande|Am=bu=le=ran=de|1 +Amerika|Ame=ri=ka|1 +amerikanen|ame=ri=ka=nen|1 +amerikaner|ame=ri=ka=ner|1 +amerikanske|ame=ri=kans=ke|1 +ammoniakatmosfär|am=mo=ni=a=kat=mo=sfär|1 +ammoniaken|am=mo=ni=a=ken|1 +ammoniakhalsbanden|am=mo=ni=ak=hals=ban=den|1 +ammoniaklukt|am=mo=nia=klukt|1 +ammoniaklukten|am=mo=nia=kluk=ten|1 +Ammoniaklukten|Am=mo=nia=kluk=ten|1 +ammunitionen|am=mu=ni=tio=nen|1 +amputerat|am=pu=te=rat|1 +Amundsen|Amund=sen|1 +amöbaliknande|amö=ba=lik=nan=de|1 +amöbalivsform|amö=ba=livs=form|1 +anakronism|ana=kro=nism|1 +analogi|ana=lo=gi|1 +analyser|ana=ly=ser|1 +Analyserade|Ana=ly=se=ra=de|1 +analyserades|ana=ly=se=ra=des|1 +andades|an=da=des|1 +andedräkt|an=de=dräkt|1 +andhämtning|and=hämt=ning|1 +andning|and=ning|1 +andningshål|and=nings=hål|1 +Andningssvårigheter|And=nings=svå=rig=he=ter|1 +andras|andras|1 +Andrea|Andrea|1 +Andrew|And=rew|1 +anfäder|an=fä=der|1 +Angeles|Ang=e=les|1 +angelägna|an=ge=läg=na|1 +angivit|an=gi=vit|1 +angivningen|an=giv=ning=en|1 +angripa|an=gri=pa|1 +anhalt|an=halt|1 +ankarpunkter|an=kar=punk=ter|1 +anklagas|an=kla=gas|1 +ankrad|ank=rad|1 +ankrade|ank=ra=de|1 +ankrar|ank=rar|1 +anlita|an=li=ta|1 +Anodisera|Ano=di=se=ra|1 +anodisera|ano=di=se=ra|1 +anomal|ano=mal|1 +anomalier|ano=ma=li=er|1 +anomalin|ano=ma=lin|1 +anonym|ano=nym|1 +anonymt|ano=nymt|1 +anpassade|an=pas=sa=de|1 +anpassat|an=pas=sat|1 +ansamlades|an=sam=la=des|1 +Ansiktet|An=sik=tet|1 +ansiktsuttryck|an=sikts=ut=tryck|1 +ansiktsvinylrutan|an=sikts=vi=nyl=ru=tan|1 +ansiktsöppning|an=sikts=öpp=ning|1 +anslående|an=slå=en=de|1 +anspråk|an=språk|1 +ansträngde|an=sträng=de|1 +anställda|an=ställ=da|1 +ansvar|an=svar|1 +ansvarar|an=sva=rar|1 +antagande|an=ta=gan=de|1 +antagandet|an=ta=gan=det|1 +anteckna|an=teck=na|1 +antecknat|an=teck=nat|1 +antenn|an=tenn|1 +Antennen|An=ten=nen|1 +antiamerikanska|an=ti=a=me=ri=kans=ka|1 +antibiotikan|an=ti=bi=o=ti=kan|1 +antiklimax|an=tik=li=max|1 +antikrigsaktivist|an=ti=krigs=ak=ti=vist|1 +antikverad|an=tik=ve=rad|1 +antipartikel|an=ti=par=ti=kel|1 +antogs|an=togs|1 +antropomorfierar|an=tro=po=mor=fi=e=rar|1 +antydde|an=tyd=de|1 +antydningar|an=tyd=ning=ar|1 +anvisade|an=vi=sa=de|1 +anvisningar|an=vis=ning=ar|1 +använd|an=vänd|1 +användaren|an=vän=da=ren|1 +användargränssnitt|an=vän=dar=gräns=snitt|1 +användargränssnittsknapparna|an=vän=dar=gräns=snittsknap=par=na|1 +användbara|an=vänd=ba=ra|1 +användning|an=vänd=ning|1 +användningsmöjligheter|an=vänd=nings=möj=lig=he=ter|1 +apelsin|apel=sin|1 +apokalyptisk|apo=ka=lyp=tisk|1 +Apollorymdskeppet|Apol=lorymd=skep=pet|1 +Apparaten|Ap=pa=ra=ten|1 +apparaturen|ap=pa=ra=tu=ren|1 From 32c76a4f608735e8f822388132cd8c3b08bc50d1 Mon Sep 17 00:00:00 2001 From: marcinoktawian Date: Sat, 16 May 2026 04:28:52 +0200 Subject: [PATCH 80/93] fix: use power button held time for shutdown logic (#1890) ## Summary * **What is the goal of this PR?** Fix incorrect power button long-press detection during shutdown/wake verification by introducing dedicated power button timing logic. * **What changes are included?** * Added getPowerButtonHeldTime() to HalGPIO as a wrapper over input manager logic * Replaced generic getHeldTime() usage with power-button-specific timing in verifyPowerButtonWakeup() * Ensures shutdown/wake decision is based only on actual power button hold duration, not any-button timing * Minor header update for new API exposure in HalGPIO.h ## Additional Context This fixes a bug where holding another button while briefly pressing the power button could incorrectly trigger shutdown behavior due to shared timing state (getHeldTime()). The change isolates power button timing to prevent cross-button interference and makes shutdown logic reliable during multi-button interactions. No behavioral changes are expected outside of power-button handling logic. **Dependencies** - SDK PR: https://github.com/crosspoint-reader/community-sdk/pull/3 This PR requires the `community-sdk` submodule to be updated after the SDK change is merged. - Fixes: #1881 --- ### AI Usage Did you use AI tools to help write this code? _**PARTIALLY**_ --- lib/hal/HalGPIO.cpp | 6 ++++-- lib/hal/HalGPIO.h | 1 + src/main.cpp | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp index 59b6eb72b1..b150dc8c75 100644 --- a/lib/hal/HalGPIO.cpp +++ b/lib/hal/HalGPIO.cpp @@ -223,6 +223,8 @@ bool HalGPIO::wasAnyReleased() const { return inputMgr.wasAnyReleased(); } unsigned long HalGPIO::getHeldTime() const { return inputMgr.getHeldTime(); } +unsigned long HalGPIO::getPowerButtonHeldTime() const { return inputMgr.getPowerButtonHeldTime(); } + void HalGPIO::startDeepSleep() { // Ensure that the power button has been released to avoid immediately turning back on if you're holding it while (inputMgr.isPressed(BTN_POWER)) { @@ -258,8 +260,8 @@ void HalGPIO::verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPre do { delay(10); inputMgr.update(); - } while (inputMgr.isPressed(BTN_POWER) && inputMgr.getHeldTime() < calibratedDuration); - if (inputMgr.getHeldTime() < calibratedDuration) { + } while (inputMgr.isPressed(BTN_POWER) && inputMgr.getPowerButtonHeldTime() < calibratedDuration); + if (inputMgr.getPowerButtonHeldTime() < calibratedDuration) { startDeepSleep(); } } else { diff --git a/lib/hal/HalGPIO.h b/lib/hal/HalGPIO.h index 6337cf9e80..94e9bcd711 100644 --- a/lib/hal/HalGPIO.h +++ b/lib/hal/HalGPIO.h @@ -70,6 +70,7 @@ class HalGPIO { bool wasReleased(uint8_t buttonIndex) const; bool wasAnyReleased() const; unsigned long getHeldTime() const; + unsigned long getPowerButtonHeldTime() const; // Setup wake up GPIO and enter deep sleep void startDeepSleep(); diff --git a/src/main.cpp b/src/main.cpp index 4a220843a2..189ea64c76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -185,8 +185,8 @@ void verifyPowerButtonDuration() { do { delay(10); gpio.update(); - } while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() < calibratedPressDuration); - abort = gpio.getHeldTime() < calibratedPressDuration; + } while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getPowerButtonHeldTime() < calibratedPressDuration); + abort = gpio.getPowerButtonHeldTime() < calibratedPressDuration; } else { abort = true; } @@ -472,7 +472,7 @@ void loop() { return; } - if (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() > SETTINGS.getPowerButtonDuration()) { + if (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getPowerButtonHeldTime() > SETTINGS.getPowerButtonDuration()) { // If the screenshot combination is potentially being pressed, don't sleep if (gpio.isPressed(HalGPIO::BTN_DOWN)) { return; From 9ceeaaaf53f855667ccd7a872eef3376cc85775b Mon Sep 17 00:00:00 2001 From: Kira <104837532+Kirillka8996@users.noreply.github.com> Date: Sat, 16 May 2026 07:29:38 +0500 Subject: [PATCH 81/93] fix: prevent card overflow on screens (#1943) ## Summary prevent card overflow ## Additional Context bug Bug fix fix2 --- ### AI Usage Did you use AI tools to help write this code? _**NO**_ --- src/network/html/FilesPage.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index 96014ee922..8c8880ac65 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -261,6 +261,7 @@ } .folder-link, .file-link { + word-break: break-word; color: var(--accent-color); text-decoration: none; cursor: pointer; @@ -1216,6 +1217,7 @@ font-size: 14px; } .card { + line-break: anywhere; padding: 12px; margin: 10px 0; } From 66fd083df8f74130e0bd3530b4a480b27aa13492 Mon Sep 17 00:00:00 2001 From: Blue Date: Sat, 16 May 2026 04:30:49 +0200 Subject: [PATCH 82/93] fix: update URL-encoded image during EPUB optimization (#1985) ## Summary * **What is the goal of this PR?** Fix EPUB optimization when XHTML image references are URL-encoded. * **What changes are included?** The optimizer already converts image files to `.jpg`, but XHTML files could still reference the original URL-encoded image path, for example: ```html ```` The optimized EPUB then contained the converted file: ```text images/wensday 1 full 2.jpg ``` but the XHTML still pointed to the old `.png`, so CrossPoint failed to extract/render the image. The issue was that the previous replacement logic matched only the plain filename form, such as: ```text wensday 1 full 2.png ``` but not the URL-encoded form: ```text wensday%201%20full%202.png ``` This PR updates XHTML image `src` attributes through the existing DOMParser pass by decoding and resolving the image path before matching it against renamed images. After this fix, the optimized EPUB correctly rewrites the XHTML image reference to the generated `.jpg`, and the image renders correctly. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ --- Please let me know if you have questions, Thank you! --- src/network/html/FilesPage.html | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index 8c8880ac65..eb21ae3782 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -4316,10 +4316,6 @@

📂 Move File

const r2 = fixSvgWrappedImages(t); if (r2.fixed) { t = r2.c; logFix(`SVG images (${r2.count})`, xhtmlPath.split('/').pop()); } - for (const [o, n] of Object.entries(renamed)) { - t = t.split(o.split('/').pop()).join(n.split('/').pop()); - } - // Use DOMParser for all img modifications: remove width/height and handle split images try { const parser = new DOMParser(); @@ -4335,6 +4331,20 @@

📂 Move File

for (const img of allImgElements) { if (img.hasAttribute('width')) { img.removeAttribute('width'); modified = true; } if (img.hasAttribute('height')) { img.removeAttribute('height'); modified = true; } + + const src = img.getAttribute('src'); + if (src) { + const decodedSrc = decodeHref(src); + const resolvedSrc = resolvePath(xhtmlPath, decodedSrc); + + const match = Object.entries(renamed).find(([oldPath]) => resolvedSrc === oldPath); + + if (match) { + const [oldPath, newPath] = match; + img.setAttribute('src', decodedSrc.replace(oldPath.split('/').pop(), newPath.split('/').pop())); + modified = true; + } + } } // Handle split images with path collision prevention From 4d883ed247ccea8f3f0b450409a247cc347f5ff5 Mon Sep 17 00:00:00 2001 From: mvidelatraduc <113857985+mvidelatraduc@users.noreply.github.com> Date: Sat, 16 May 2026 15:55:27 -0300 Subject: [PATCH 83/93] chore: Update spanish.yaml (#2011) --- lib/I18n/translations/spanish.yaml | 89 +++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 26f9f1b3a4..cb7723a2c3 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -26,7 +26,7 @@ STR_LOADING: "Cargando..." STR_LOADING_POPUP: "Cargando" STR_WIFI_NETWORKS: "Redes Wi-Fi" STR_NO_NETWORKS: "No hay redes disponibles" -STR_NETWORKS_FOUND: "%zu redes encontradas" +STR_NETWORKS_FOUND: "%zu red(es) encontrada(s)" STR_SCANNING: "Buscando..." STR_CONNECTING: "Conectando..." STR_CONNECTED: "¡Conectado!" @@ -45,9 +45,9 @@ STR_OPEN_URL_HINT: "Abra esta dirección en su navegador" STR_OR_HTTP_PREFIX: "o http://" STR_SCAN_QR_HINT: "o escanee el código QR con su móvil:" STR_CALIBRE_WIRELESS: "Calibre inalámbrico" -STR_CALIBRE_WEB_URL: "OPDS URL" -STR_NETWORK_LEGEND: "* (Cifrado) | + (Guardado)" -STR_MAC_ADDRESS: "MAC Address:" +STR_CALIBRE_WEB_URL: "URL OPDS" +STR_NETWORK_LEGEND: "* (Con contraseña) | + (Guardada)" +STR_MAC_ADDRESS: "Dirección MAC:" STR_CHECKING_WIFI: "Verificando Wi-Fi..." STR_ENTER_WIFI_PASSWORD: "Introduzca la contraseña del Wi-Fi" STR_TO_PREFIX: "a " @@ -61,7 +61,7 @@ STR_CAT_DISPLAY: "Pantalla" STR_CAT_READER: "Lector" STR_CAT_CONTROLS: "Controles" STR_CAT_SYSTEM: "Sistema" -STR_SLEEP_SCREEN: "Pantalla de suspensión" +STR_SLEEP_SCREEN: "Fondo de pantalla de suspensión" STR_SLEEP_COVER_MODE: "Modo de pantalla de suspensión" STR_HIDE_BATTERY: "Ocultar % de batería" STR_EXTRA_SPACING: "Espaciado entre párrafos" @@ -70,13 +70,13 @@ STR_IMAGES: "Imágenes" STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Reemplazar" STR_IMAGES_SUPPRESS: "Ocultar" -STR_SHORT_PWR_BTN: "Toque corto botón encendido" +STR_SHORT_PWR_BTN: "Toque corto del encendido" STR_ORIENTATION: "Orientación" STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)" -STR_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón" -STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado" +STR_LONG_PRESS_BEHAVIOR: "Al mantener pulsado un botón" +STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo" -STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambio de orientación" +STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambiar orient." STR_FONT_FAMILY: "Tipografía" STR_FONT_SIZE: "Tamaño" STR_LINE_SPACING: "Interlineado" @@ -131,6 +131,7 @@ STR_ALWAYS: "Siempre" STR_IGNORE: "Ignorar" STR_SLEEP: "Suspender" STR_PAGE_TURN: "Pasar página" +STR_FORCE_REFRESH: "Refrescar pant." STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horizontal (horario)" STR_INVERTED: "Invertido" @@ -171,7 +172,8 @@ STR_UPDATING: "Actualizando..." STR_NO_UPDATE: "No hay actualizaciones disponibles" STR_UPDATE_FAILED: "Fallo de actualización" STR_UPDATE_COMPLETE: "Actualización completada" -STR_POWER_ON_HINT: "Reinicie manteniendo pulsado botón de encendido" +STR_POWER_ON_HINT: "Reinicie manteniendo pulsado el encendido" +STR_RESTARTING_HINT: "Reiniciando... Si no se reinicia, mantenga pulsado el encendido unos segundos." STR_NO_ENTRIES: "No se encontraron elementos" STR_DOWNLOADING: "Descargando..." STR_DOWNLOAD_FAILED: "Fallo de descarga" @@ -180,12 +182,14 @@ STR_UNNAMED: "Sin nombre" STR_NO_SERVER_URL: "No se configuró URL de servidor" STR_FETCH_FEED_FAILED: "Fallo al obtener el feed" STR_PARSE_FEED_FAILED: "Fallo al procesar el feed" +STR_NEXT_PAGE: "Pág. siguiente »" +STR_PREV_PAGE: "« Pág. anterior" STR_NETWORK_PREFIX: "Red: " STR_IP_ADDRESS_PREFIX: "IP: " STR_ERROR_GENERAL_FAILURE: "Error: Fallo general" STR_ERROR_NETWORK_NOT_FOUND: "Error: Red no encontrada" STR_ERROR_CONNECTION_TIMEOUT: "Error: Tiempo de conexión agotado" -STR_SD_CARD: "Tarjeta microSD" +STR_SD_CARD: "Tarjeta SD" STR_BACK: "« Atrás" STR_EXIT: "« Salir" STR_HOME: "« Inicio" @@ -227,14 +231,18 @@ STR_EXAMPLE_BOOK: "Título del libro" STR_PREVIEW: "Previsualización" STR_TITLE: "Título" STR_BATTERY: "Batería" +STR_XTC_STATUS_BAR: "Barra de estado de XTC" +STR_BOTTOM: "Abajo" +STR_TOP: "Arriba" STR_UI_THEME: "Interfaz" STR_THEME_CLASSIC: "Clásico" STR_THEME_LYRA: "Lyra" -STR_THEME_LYRA_EXTENDED: "Lyra Extendido" STR_THEME_ROUNDEDRAFF: "RoundedRaff" +STR_THEME_LYRA_EXTENDED: "Lyra Extendido" STR_SUNLIGHT_FADING_FIX: "Corrección de desvanecimiento" STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales" STR_OPDS_BROWSER: "Navegador OPDS" +STR_SEARCH: "Buscar" STR_COVER_CUSTOM: "Portada + Pers." STR_MENU_RECENT_BOOKS: "Libros recientes" STR_NO_RECENT_BOOKS: "No hay libros recientes" @@ -292,10 +300,65 @@ STR_BOOK_S_STYLE: "Estilo del libro" STR_EMBEDDED_STYLE: "Estilo integrado" STR_FOCUS_READING: "Lectura enfocada" STR_OPDS_SERVER_URL: "URL del servidor OPDS" +STR_SET_SLEEP_COVER: "Pant. sus." STR_FOOTNOTES: "Pie de página" STR_NO_FOOTNOTES: "No hay notas al pie de esta página" STR_LINK: "[enlace]" STR_SCREENSHOT_BUTTON: "Tomar captura de pantalla" -STR_TILT_PAGE_TURN: "Pasar página inclinando" +STR_ADD_SERVER: "Agregar servidor" +STR_SERVER_NAME: "Nombre de servidor" +STR_NO_SERVERS: "No se configuraron servidores OPDS" +STR_DELETE_SERVER: "Borrar servidor" +STR_DELETE_CONFIRM: "¿Borrar este servidor?" +STR_OPDS_SERVERS: "Servidores OPDS" STR_AUTO_TURN_ENABLED: "Avance activado: " STR_AUTO_TURN_PAGES_PER_MIN: "Avance auto. (pág./min)" +STR_MANAGE_FONTS: "Gestionar tipografías" +STR_FONT_BROWSER: "Navegador de tipografías" +STR_LOADING_FONT_LIST: "Cargando lista de tipografías..." +STR_NO_FONTS_AVAILABLE: "No hay tipografías disponibles" +STR_FONT_INSTALLED: "¡Se instaló la tipografía!" +STR_FONT_INSTALL_FAILED: "Falló la instalación de tipografía" +STR_INSTALLED: "Instalada" +STR_CONFIRM_DOWNLOAD_PROMPT: "¿Descargar?" +STR_SD_CARD_FULL: "No hay espacio en la tarjeta SD" +STR_FILES_LABEL: "Archivos: " +STR_SIZE_LABEL: "Tamaño: " +STR_REDOWNLOAD: "Redescargar" +STR_DOWNLOAD_ALL: "Descargar todas" +STR_UPDATE_ALL: "Actualizar todas" +STR_ALL_FONTS_INSTALLED: "¡Se instalaron todas las tipografías!" +STR_UPDATE_AVAILABLE: "Actualizar" +STR_CRASH_TITLE: "Fallo del sistema" +STR_CRASH_DESCRIPTION: "Se guardó un informe detallado en crash_report.txt. Inclúyalo al avisar sobre el error." +STR_CRASH_REASON: "Razón del fallo:" +STR_CRASH_NO_REASON: "(No se registró una razón)." +STR_TILT_PAGE_TURN: "Pasar página inclinando" +STR_KB_HINT_MOVE_CURSOR: "Pulse IZQ. o DCHA. para mover el cursor" +STR_KB_HINT_RETURN_CURSOR: "Pulse IZQ. para volver a la posición del cursor" +STR_KB_HINT_HIDE_PASSWORD: "Pulse DCHA., luego [***] para ocultar la contraseña" +STR_KB_HINT_SHOW_PASSWORD: "Pulse DCHA., luego [abc] para mostrar la contraseña" +STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Pulse [***] para ocultar la contraseña" +STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Pulse [abc] para mostrar la contraseña" +STR_KB_HINT_EDIT_ENTRY: "Mantenga SUBIR para editar la entrada" +STR_KB_TIPS: "Consejos: " +STR_KB_HINT_RETURN_KEYBOARD: "Pulse BAJAR para volver al teclado" +STR_KB_HINT_EXIT_URL_MODE: "Pulse ABC para salir del modo URL" +STR_KB_HINT_CLEAR_TEXT: "Mantenga RETROCESO para borrar todo" +STR_KB_HINT_SECONDARY_CHAR: "Mantenga SELECC. para otros caracteres" +STR_KB_HINT_UPPER_SECONDARY: "Mantenga SELECC. para MAYÚS./otros caracteres" +STR_KB_HINT_LOWER_SECONDARY: "Mantenga SELECC. para minús./otros caracteres" +STR_KB_HINT_URL_SNIPPETS: "Pulse URL para ingresar fragmentos" +STR_SD_FIRMWARE_UPDATE: "Actualizar firmware por tarj. SD" +STR_SELECT_FIRMWARE_FILE: "Elija el archivo de firmware (.bin)" +STR_NO_BIN_FILES: "No se encontraron archivos .bin" +STR_VALIDATING_FIRMWARE: "Validando el firmware..." +STR_INVALID_FIRMWARE: "Archivo de firmware no válido" +STR_FIRMWARE_TOO_LARGE: "Firmware demasiado grande p/la partición" +STR_FIRMWARE_TOO_SMALL: "Firmware demasiado pequeño" +STR_FIRMWARE_UPDATE_PROMPT: "¿Actualizar el firmware?" +STR_FIRMWARE_FILE_OPEN_FAILED: "No se puede abrir el archivo" +STR_FIRMWARE_WRITE_FAILED: "Falló la escritura del firmware" +STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "¡No apague el dispositivo!" +STR_RECOVERY_MODE: "Modo de recuperación" +STR_RECOVERY_MODE_HINT: "Ponga firmware.bin en la raíz de la tarj. SD y selecciónelo" From 2a76281d3366aee60fe7855be703fcad6fe9fa55 Mon Sep 17 00:00:00 2001 From: Matteo Scopel Date: Sun, 17 May 2026 11:08:16 +0200 Subject: [PATCH 84/93] feat: add the Domitian font family (#2016) --- lib/EpdFont/scripts/sd-fonts.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/EpdFont/scripts/sd-fonts.yaml b/lib/EpdFont/scripts/sd-fonts.yaml index 6ef1247c8a..6ff7aaf1e5 100644 --- a/lib/EpdFont/scripts/sd-fonts.yaml +++ b/lib/EpdFont/scripts/sd-fonts.yaml @@ -113,6 +113,17 @@ families: italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter-Italic%5Bwght%5D.ttf", variable: {wght: 500}} bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter-Italic%5Bwght%5D.ttf", variable: {wght: 700}} + - name: Domitian + description: "A humanist serif for literary reading (Latin, Greek, Cyrillic)" + intervals: latin-ext,greek,cyrillic + sizes: [12, 14, 16, 18] + styles: + regular: {url: "https://mirrors.ctan.org/fonts/domitian/opentype/Domitian-Roman.otf"} + bold: {url: "https://mirrors.ctan.org/fonts/domitian/opentype/Domitian-Bold.otf"} + italic: {url: "https://mirrors.ctan.org/fonts/domitian/opentype/Domitian-Italic.otf"} + bolditalic: {url: "https://mirrors.ctan.org/fonts/domitian/opentype/Domitian-BoldItalic.otf"} + + # ── Sans-serif ───────────────────────────────────────────────────────── - name: NotoSansExtended From c0e6f78c2a74763e0dc32b0c87b95cdaf39fb722 Mon Sep 17 00:00:00 2001 From: zgredex <112968378+zgredex@users.noreply.github.com> Date: Sun, 17 May 2026 11:12:56 +0200 Subject: [PATCH 85/93] fix: harden EPUB optimiser UI gating, size reporting, and picker teardown (#1947) Co-authored-by: Justin Mitchell --- src/network/CrossPointWebServer.cpp | 2 + src/network/html/FilesPage.html | 164 +++++++++++++++++++++++----- 2 files changed, 137 insertions(+), 29 deletions(-) diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 33a1348ddc..aeaad11e1d 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -374,6 +375,7 @@ void CrossPointWebServer::handleStatus() const { doc["rssi"] = apMode ? 0 : WiFi.RSSI(); doc["freeHeap"] = ESP.getFreeHeap(); doc["uptime"] = millis() / 1000; + doc["device"] = gpio.deviceIsX3() ? "X3" : "X4"; String json; serializeJson(doc, json); diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index eb21ae3782..1220f08415 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -1539,7 +1539,7 @@

📤 Upload file

⚫ True-Grayscale - 📏 Max 480×800px + 📏 Max 480×800px 📦 85% JPEG 🔧 Fix SVG
@@ -1574,6 +1574,19 @@

📤 Upload file

--> + +
+
📱 Target Device
+
+
+ + + +
+
+
↻ Rotation Direction
@@ -2374,13 +2387,13 @@

📂 Move File

const isTiny = (dims.width < 200 && dims.height < 200); // Images that fit screen can only rotate, not split - const fitsScreen = (dims.width <= 480 && dims.height <= 800); - + const fitsScreen = (dims.width <= MAX_WIDTH && dims.height <= MAX_HEIGHT); + // Split capability - no upscaling allowed - // H-Split scales width to 800, so needs width >= 800 - // V-Split scales height to 800, so needs height >= 800 - const canHSplit = dims.width >= 800; - const canVSplit = dims.height >= 800; + // H-Split scales width to MAX_HEIGHT (long edge), so needs width >= MAX_HEIGHT + // V-Split scales height to MAX_HEIGHT, so needs height >= MAX_HEIGHT + const canHSplit = dims.width >= MAX_HEIGHT; + const canVSplit = dims.height >= MAX_HEIGHT; images.push({ path: path, @@ -2618,16 +2631,16 @@

📂 Move File

if (showSplitLines) { let finalWidth; if (state === 1) { - // H-Split: scale width to 800, rotate, then check width - const scaledH = Math.round(img.height * (800 / img.width)); + // H-Split: scale width to MAX_HEIGHT, rotate, then check width + const scaledH = Math.round(img.height * (MAX_HEIGHT / img.width)); finalWidth = scaledH; // After rotation, height becomes width } else { - // V-Split: scale height to 800, then check width - finalWidth = Math.round(img.width * (800 / img.height)); + // V-Split: scale height to MAX_HEIGHT, then check width + finalWidth = Math.round(img.width * (MAX_HEIGHT / img.height)); } - if (finalWidth > 480) { - const minOverlapPx = Math.round(480 * (OVERLAP_PERCENT / 100)); - const maxStep = 480 - minOverlapPx; + if (finalWidth > MAX_WIDTH) { + const minOverlapPx = Math.round(MAX_WIDTH * (OVERLAP_PERCENT / 100)); + const maxStep = MAX_WIDTH - minOverlapPx; numParts = Math.ceil((finalWidth - minOverlapPx) / maxStep); if (numParts < 2) numParts = 2; } @@ -2933,11 +2946,20 @@

📂 Move File

const convertOptions = document.getElementById('convertOptions'); fileInput.classList.toggle('has-files', files.length > 0); - // Show/hide convert options based on file selection - if (files.length > 0) { + // Show convert options only when at least one selected file is an EPUB. + const hasEpub = Array.from(files).some(f => f.name.toLowerCase().endsWith('.epub')); + if (files.length > 0 && hasEpub) { convertOptions.style.display = 'block'; } else { - clearImagePicker(); + convertOptions.style.display = 'none'; + // Clear stale checkbox state so the "Optimize & Upload" button doesn't linger + // when the user re-picks a non-EPUB after having ticked Optimize for an EPUB. + const cb = document.getElementById('convertBeforeUpload'); + if (cb && cb.checked) { + cb.checked = false; + toggleConvertOptions(); + } + if (files.length === 0) clearImagePicker(); } if (files.length > 0) { @@ -2946,6 +2968,11 @@

📂 Move File

const convertEnabled = document.getElementById('convertBeforeUpload').checked; if (advancedContent.classList.contains('visible') && files.length === 1 && convertEnabled && files[0].name.toLowerCase().endsWith('.epub')) { showImagePicker(files[0]).catch(err => console.error('Image picker error:', err)); + } else { + // New selection no longer matches single-EPUB-with-advanced-expanded — + // tear down any picker from a previous file so the grid and picker-mode + // layout don't linger. clearImagePicker is idempotent. + clearImagePicker(); } // If multiple files with conversion, inform user about batch mode @@ -2978,14 +3005,24 @@

📂 Move File

// EPUB Image Conversion Functions (from Baseline JPEG Converter) // ============================================================================ +// Device profiles (short edge × long edge in portrait orientation) +const DEVICE_PROFILES = { + X4: { width: 480, height: 800, label: 'X4' }, + X3: { width: 528, height: 792, label: 'X3' }, +}; + // Default conversion settings -const DEFAULT_MAX_WIDTH = 480; -const DEFAULT_MAX_HEIGHT = 800; +const DEFAULT_DEVICE = 'X4'; +const DEFAULT_MAX_WIDTH = DEVICE_PROFILES[DEFAULT_DEVICE].width; +const DEFAULT_MAX_HEIGHT = DEVICE_PROFILES[DEFAULT_DEVICE].height; const DEFAULT_JPEG_QUALITY = 85; const DEFAULT_ENABLE_GRAYSCALE = true; // Note: Overlap is now always centered distribution (min 5%) // Dynamic conversion settings (updated by UI) +let DEVICE_TARGET = 'auto'; // 'auto' | 'X4' | 'X3' +let DETECTED_DEVICE = null; // populated from /api/status +let ACTIVE_DEVICE = DEFAULT_DEVICE; let MAX_WIDTH = DEFAULT_MAX_WIDTH; let MAX_HEIGHT = DEFAULT_MAX_HEIGHT; let JPEG_QUALITY = DEFAULT_JPEG_QUALITY; @@ -3021,12 +3058,55 @@

📂 Move File

if (response.ok) { const data = await response.json(); crosspointVersion = data.version || 'Unknown'; + if (data.device === 'X3' || data.device === 'X4') { + DETECTED_DEVICE = data.device; + applyDeviceTarget(); + } } } catch (e) { console.error('Failed to fetch version:', e); } } +// Resolve DEVICE_TARGET ('auto' | 'X4' | 'X3') to a concrete profile and update UI. +function applyDeviceTarget() { + const resolved = DEVICE_TARGET === 'auto' ? (DETECTED_DEVICE || DEFAULT_DEVICE) : DEVICE_TARGET; + const profile = DEVICE_PROFILES[resolved] || DEVICE_PROFILES[DEFAULT_DEVICE]; + ACTIVE_DEVICE = resolved; + MAX_WIDTH = profile.width; + MAX_HEIGHT = profile.height; + + const summary = document.getElementById('convertSizeSummary'); + if (summary) { + summary.textContent = `📏 Max ${profile.width}×${profile.height}px`; + } + document.querySelectorAll('.device-btn').forEach(btn => { + btn.classList.toggle('active', btn.dataset.value === DEVICE_TARGET); + }); + const autoLabel = document.getElementById('deviceAutoLabel'); + if (autoLabel) { + autoLabel.textContent = DETECTED_DEVICE ? `Auto (${DETECTED_DEVICE})` : 'Auto'; + } + + // Recompute picker classification with new dimensions, then refresh grid. + if (Array.isArray(epubImagesCache) && epubImagesCache.length > 0) { + for (const img of epubImagesCache) { + img.fitsScreen = (img.width <= MAX_WIDTH && img.height <= MAX_HEIGHT); + img.canHSplit = img.width >= MAX_HEIGHT; + img.canVSplit = img.height >= MAX_HEIGHT; + } + const pickerSection = document.getElementById('imagePickerSection'); + if (pickerSection && pickerSection.style.display !== 'none' && typeof renderImageGrid === 'function') { + renderImageGrid(); + } + } +} + +function setDeviceTarget(value) { + DEVICE_TARGET = value; + applyDeviceTarget(); +} + // Batch logging system for multiple files let batchLogEntries = []; let batchStats = { filesProcessed: 0, filesSucceeded: 0, filesFailed: 0, totalImages: 0, totalSplits: 0, totalFixes: 0, totalErrors: 0, totalOriginalSize: 0, totalNewSize: 0 }; @@ -3183,7 +3263,7 @@

📂 Move File

} // Save current file's log to batch entries -function saveToFileBatchLog(fileName, succeeded) { +function saveToFileBatchLog(fileName, succeeded, originalSize = 0, newSize = 0) { if (!isBatchMode) return; const entries = Array.from(logContainer.querySelectorAll('.log-entry')); @@ -3205,6 +3285,10 @@

📂 Move File

batchStats.totalSplits += conversionStats.splits; batchStats.totalFixes += conversionStats.fixes; batchStats.totalErrors += conversionStats.errors; + // Defaults of 0 keep failure-path callers safe — files that never + // produced a converted blob contribute nothing to the totals. + batchStats.totalOriginalSize += originalSize; + batchStats.totalNewSize += newSize; // Clear for next file logContainer.innerHTML = ''; @@ -3237,6 +3321,22 @@

📂 Move File

}); }); + // Aggregate size totals: only emit rows when at least one file was successfully + // converted (totalOriginalSize stays 0 for batches where conversion was off or + // every file fell back to original upload). + const totalSaved = batchStats.totalOriginalSize - batchStats.totalNewSize; + const totalSavedPct = batchStats.totalOriginalSize > 0 + ? ((totalSaved / batchStats.totalOriginalSize) * 100).toFixed(1) + : '0.0'; + const sizeRowsHtml = batchStats.totalOriginalSize > 0 ? ` + Total original${formatBytes(batchStats.totalOriginalSize)} + Total optimised${formatBytes(batchStats.totalNewSize)} + Total saved${ + totalSaved > 0 + ? `${formatBytes(totalSaved)} (${totalSavedPct}%)` + : `+${formatBytes(-totalSaved)}` + }` : ''; + // Add batch summary const batchSummaryHtml = `
@@ -3248,7 +3348,7 @@

📂 Move File

Total images processed${batchStats.totalImages} Total splits${batchStats.totalSplits} Total fixes applied${batchStats.totalFixes} - ${batchStats.totalErrors > 0 ? `Total errors${batchStats.totalErrors}` : ''} + ${batchStats.totalErrors > 0 ? `Total errors${batchStats.totalErrors}` : ''}${sizeRowsHtml} Total time${batchTime.toFixed(1)}s
@@ -4180,8 +4280,6 @@

📂 Move File

async function convertEpubFile(file, progressCallback) { const startTime = Date.now(); const originalSize = file.size; - let totalImageSize = 0; - let totalNewSize = 0; // Initialize logging clearLog(); @@ -4265,9 +4363,6 @@

📂 Move File

const origFormat = path.split('.').pop(); logImage(imgName, meta.origW, meta.origH, origFormat, meta.origSize, meta.finalW, meta.finalH, meta.finalSize, meta.wasSplit, meta.splitCount || 0, parts, meta.imageState || 0); - totalImageSize += meta.origSize; - totalNewSize += meta.finalSize; - if (parts.length === 1 && parts[0].suffix === '') { const newPath = renamed[path] || path.replace(/\.[^.]+$/, newExt); out.file(newPath, parts[0].data, { compression: 'STORE', createFolders: false }); @@ -4518,7 +4613,7 @@

📂 Move File

// Log completion log('Conversion complete!', 'success', 'DONE'); - logSummary(totalImageSize > 0 ? totalImageSize : originalSize, totalNewSize > 0 ? totalNewSize : newSize, timeElapsed); + logSummary(originalSize, newSize, timeElapsed); // Auto-export only if NOT in batch mode (batch mode exports at the end) if (!isBatchMode && exportLogCheckbox && exportLogCheckbox.checked) { @@ -4787,6 +4882,8 @@

📂 Move File

const needsConversion = isEpub && convertEnabled; let conversionSucceeded = false; let conversionFailed = false; // Track if conversion actually failed + let convOriginalSize = 0; // Picked-file size; 0 unless conversion succeeded + let convNewSize = 0; // Generated blob size; 0 unless conversion succeeded const methodText = useWebSocket ? ' [WS]' : ' [HTTP]'; const stageText = needsConversion ? 'Converting & uploading' : 'Uploading'; @@ -4806,7 +4903,8 @@

📂 Move File

// Save file log to batch if in batch mode and this file was converted // Consider it successful only if conversion didn't fail if (useBatchLog && needsConversion) { - saveToFileBatchLog(file.name, !conversionFailed && conversionSucceeded); + const ok = !conversionFailed && conversionSucceeded; + saveToFileBatchLog(file.name, ok, ok ? convOriginalSize : 0, ok ? convNewSize : 0); } currentIndex++; @@ -4817,7 +4915,9 @@

📂 Move File

// Save failed file log to batch if in batch mode if (useBatchLog && needsConversion) { logError(`Upload failed: ${error}`); - saveToFileBatchLog(file.name, false); + // Preserve conversion size totals when the convert step succeeded but + // the upload failed; otherwise nothing was produced and 0/0 is correct. + saveToFileBatchLog(file.name, false, convOriginalSize, convNewSize); } failedFiles.push({ name: file.name, error: error, file: originalFile }); @@ -4862,6 +4962,10 @@

📂 Move File

showLog(); } + // Snapshot before the `file =` reassignment below, which swaps in the + // converted blob and makes file.size point at the optimised size. + const origFileSize = file.size; + try { const convertedBlob = await convertEpubFile(file, (percent) => { // Pass current quality setting to converter @@ -4872,6 +4976,8 @@

📂 Move File

file = new File([convertedBlob], file.name, { type: 'application/epub+zip' }); progressFill.style.backgroundColor = '#27ae60'; // Back to green for upload conversionSucceeded = true; + convOriginalSize = origFileSize; + convNewSize = convertedBlob.size; } catch (convError) { if (operationCancelled) { if (uploadGeneration === myGeneration) restoreAfterCancel(); return; } console.error('Conversion error:', convError); From 9037b8f7b7d75bfe115e3dc40b0d1f578fabaf76 Mon Sep 17 00:00:00 2001 From: muhas Date: Sun, 17 May 2026 15:17:56 +0300 Subject: [PATCH 86/93] feat: update Russian translation (#2017) Co-authored-by: muhas --- lib/I18n/translations/russian.yaml | 71 +++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 7815092afc..eadbc60b00 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -131,6 +131,7 @@ STR_ALWAYS: "Всегда" STR_IGNORE: "Игнорировать" STR_SLEEP: "Сон" STR_PAGE_TURN: "Перелистывание" +STR_FORCE_REFRESH: "Обновление экрана" STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Инверсия" @@ -172,6 +173,7 @@ STR_NO_UPDATE: "Обновлений нет" STR_UPDATE_FAILED: "Ошибка обновления" STR_UPDATE_COMPLETE: "Обновление завершено" STR_POWER_ON_HINT: "Удерживайте кнопку питания для включения" +STR_RESTARTING_HINT: "Перезапуск... Если устройство не перезагружается, удерживайте кнопку питания несколько секунд" STR_NO_ENTRIES: "Записи не найдены" STR_DOWNLOADING: "Загрузка..." STR_DOWNLOAD_FAILED: "Ошибка загрузки" @@ -180,6 +182,8 @@ STR_UNNAMED: "Без имени" STR_NO_SERVER_URL: "URL сервера не настроен" STR_FETCH_FEED_FAILED: "Не удалось получить ленту" STR_PARSE_FEED_FAILED: "Не удалось обработать ленту" +STR_NEXT_PAGE: "Следующая страница »" +STR_PREV_PAGE: "« Предыдущая страница" STR_NETWORK_PREFIX: "Сеть: " STR_IP_ADDRESS_PREFIX: "IP-адрес: " STR_ERROR_GENERAL_FAILURE: "Ошибка: Общая ошибка" @@ -227,6 +231,9 @@ STR_EXAMPLE_BOOK: "Название книги" STR_PREVIEW: "Предпросмотр" STR_TITLE: "Заглавие" STR_BATTERY: "Батарея" +STR_XTC_STATUS_BAR: "Строка состояния XTC" +STR_BOTTOM: "Низ" +STR_TOP: "Верх" STR_UI_THEME: "Тема интерфейса" STR_THEME_CLASSIC: "Классическая" STR_THEME_LYRA: "Lyra" @@ -235,9 +242,7 @@ STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания" STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки" STR_OPDS_BROWSER: "OPDS браузер" -STR_FOOTNOTES: "Примечания" -STR_NO_FOOTNOTES: "На данной странице нет примечаний" -STR_LINK: "[ссылка]" +STR_SEARCH: "Поиск" STR_COVER_CUSTOM: "Обложка + Свой" STR_MENU_RECENT_BOOKS: "Недавние книги" STR_NO_RECENT_BOOKS: "Нет недавних книг" @@ -263,8 +268,8 @@ STR_GO_HOME_BUTTON: "На главную" STR_SYNC_PROGRESS: "Синхронизировать прогресс" STR_DELETE_CACHE: "Удалить кэш книги" STR_DELETE: "Удалить" -STR_CHAPTER_PREFIX: "Глава: " STR_DISPLAY_QR: "Показать страницу в виде QR-кода" +STR_CHAPTER_PREFIX: "Глава: " STR_PAGES_SEPARATOR: " стр. | " STR_BOOK_PREFIX: "Книга: " STR_CALIBRE_URL_HINT: "Для Calibre добавьте /opds к URL" @@ -295,13 +300,65 @@ STR_BOOK_S_STYLE: "Стиль книги" STR_EMBEDDED_STYLE: "Встроенный стиль" STR_FOCUS_READING: "Фокусное чтение" STR_OPDS_SERVER_URL: "URL OPDS сервера" +STR_SET_SLEEP_COVER: "Обложкой" +STR_FOOTNOTES: "Примечания" +STR_NO_FOOTNOTES: "На данной странице нет примечаний" +STR_LINK: "[ссылка]" STR_SCREENSHOT_BUTTON: "Сделать снимок экрана" -STR_AUTO_TURN_ENABLED: "Автоперелистывание: " -STR_AUTO_TURN_PAGES_PER_MIN: "Автоперелистывание (стр./мин)" -STR_TILT_PAGE_TURN: "Перелистывание наклоном" STR_ADD_SERVER: "Добавить сервер" STR_SERVER_NAME: "Имя сервера" STR_NO_SERVERS: "Нет настроенных серверов OPDS" STR_DELETE_SERVER: "Удалить сервер" STR_DELETE_CONFIRM: "Удалить этот сервер?" STR_OPDS_SERVERS: "Серверы OPDS" +STR_AUTO_TURN_ENABLED: "Автоперелистывание: " +STR_AUTO_TURN_PAGES_PER_MIN: "Автоперелистывание (стр./мин)" +STR_MANAGE_FONTS: "Управление шрифтами" +STR_FONT_BROWSER: "Обзор шрифтов" +STR_LOADING_FONT_LIST: "Загрузка списка шрифтов..." +STR_NO_FONTS_AVAILABLE: "Шрифты недоступны" +STR_FONT_INSTALLED: "Шрифт установлен!" +STR_FONT_INSTALL_FAILED: "Не удалоь установить шрифт" +STR_INSTALLED: "Установлен" +STR_CONFIRM_DOWNLOAD_PROMPT: "Загрузить?" +STR_SD_CARD_FULL: "Недостаточно места на SD-карте" +STR_FILES_LABEL: "Файлы: " +STR_SIZE_LABEL: "Размер: " +STR_REDOWNLOAD: "Скачать снова" +STR_DOWNLOAD_ALL: "Загрузить все" +STR_UPDATE_ALL: "Обновить всё" +STR_ALL_FONTS_INSTALLED: "Все шрифты установлены!" +STR_UPDATE_AVAILABLE: "Обновить" +STR_CRASH_TITLE: "Сбой системы" +STR_CRASH_DESCRIPTION: "Подробный отчёт сохранён в crash_report.txt. Пожалуйста, приложите этот файл к вашему отчёту об ошибке." +STR_CRASH_REASON: "Причина сбоя:" +STR_CRASH_NO_REASON: "(Причина не указана)" +STR_TILT_PAGE_TURN: "Перелистывание наклоном" +STR_KB_HINT_MOVE_CURSOR: "Нажмите ВЛЕВО или ВПРАВО для перемещения курсора" +STR_KB_HINT_RETURN_CURSOR: "Нажмите ВЛЕВО, чтобы вернуться к позиции курсора" +STR_KB_HINT_HIDE_PASSWORD: "Удерживайте ВПРАВО, затем нажмите [***], чтобы скрыть пароль" +STR_KB_HINT_SHOW_PASSWORD: "Удерживайте ВПРАВО, затем нажмите [abc], чтобы показать пароль" +STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Нажмите [***], чтобы скрыть пароль" +STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Нажмите [abc], чтобы показать пароль" +STR_KB_HINT_EDIT_ENTRY: "Удерживайте ВВЕРХ для редактирования записи" +STR_KB_TIPS: "Подсказки:" +STR_KB_HINT_RETURN_KEYBOARD: "Нажмите ВНИЗ, чтобы вернуться к клавиатуре" +STR_KB_HINT_EXIT_URL_MODE: "Нажмите ABC для выхода из режима URL" +STR_KB_HINT_CLEAR_TEXT: "Удерживайте <— чтобы очитисть весь текст" +STR_KB_HINT_SECONDARY_CHAR: "Удерживайте ВЫБРАТЬ для дополнительного символа" +STR_KB_HINT_UPPER_SECONDARY: "Удерживайте ВЫБРАТЬ для ВЕРХНЕГО РЕГИСТРА или дополнительного символа" +STR_KB_HINT_LOWER_SECONDARY: "Удерживайте ВЫБРАТЬ для нижнего регистра или дополнительного символа" +STR_KB_HINT_URL_SNIPPETS: "Нажмите URL для выбора шаблонов" +STR_SD_FIRMWARE_UPDATE: "Обновление системы с SD-карты" +STR_SELECT_FIRMWARE_FILE: "Выберите файл прошивки (.bin)" +STR_NO_BIN_FILES: ".bin файлы не найдены" +STR_VALIDATING_FIRMWARE: "Проверка файла прошивки..." +STR_INVALID_FIRMWARE: "Не подходящий файл прошивки" +STR_FIRMWARE_TOO_LARGE: "Прошивка слишком велика для раздела" +STR_FIRMWARE_TOO_SMALL: "Файл прошивки слишком мал" +STR_FIRMWARE_UPDATE_PROMPT: "Обновить прошивку?" +STR_FIRMWARE_FILE_OPEN_FAILED: "Не могу открыть файл" +STR_FIRMWARE_WRITE_FAILED: "Ошибка записи прошивки" +STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не выключайте питание!" +STR_RECOVERY_MODE: "Режим восстановления" +STR_RECOVERY_MODE_HINT: "Поместите firmware.bin в корень SD-карты и выберите его" From ea54af46bbcec7fc84efcb334726fe13bd600da7 Mon Sep 17 00:00:00 2001 From: pablohc Date: Sun, 17 May 2026 21:59:44 +0200 Subject: [PATCH 87/93] feat: apply custom cross logo to boot and sleep screens --- src/activities/boot_sleep/BootActivity.cpp | 6 +- src/activities/boot_sleep/SleepActivity.cpp | 6 +- src/images/CrossLarge.h | 133 ++++++++++++++++++++ 3 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 src/images/CrossLarge.h diff --git a/src/activities/boot_sleep/BootActivity.cpp b/src/activities/boot_sleep/BootActivity.cpp index 9e59ed592b..864c181666 100644 --- a/src/activities/boot_sleep/BootActivity.cpp +++ b/src/activities/boot_sleep/BootActivity.cpp @@ -4,7 +4,7 @@ #include #include "fontIds.h" -#include "images/Logo120.h" +#include "images/CrossLarge.h" void BootActivity::onEnter() { Activity::onEnter(); @@ -13,9 +13,9 @@ void BootActivity::onEnter() { const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); - renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); + renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_BOOTING)); + renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_BOOTING), true, EpdFontFamily::BOLD); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION); renderer.displayBuffer(); } diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 877eaf1e5c..1fc0d21997 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -14,7 +14,7 @@ #include "Epub/converters/DirectPixelWriter.h" #include "activities/reader/ReaderUtils.h" #include "fontIds.h" -#include "images/Logo120.h" +#include "images/CrossLarge.h" namespace { constexpr uint8_t SLEEP_FACTORY_INTERNAL_PREFLASH_PASSES = 0; @@ -207,9 +207,9 @@ void SleepActivity::renderDefaultSleepScreen() const { const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); - renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); + renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD); - renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING)); + renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING), true, EpdFontFamily::BOLD); // Make sleep screen dark unless light is selected in settings if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) { diff --git a/src/images/CrossLarge.h b/src/images/CrossLarge.h new file mode 100644 index 0000000000..9ce6777f06 --- /dev/null +++ b/src/images/CrossLarge.h @@ -0,0 +1,133 @@ +#pragma once +#include + +const unsigned char CrossLarge [] PROGMEM = { +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf8, 0xfe, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf1, 0xf0, 0xfc, 0x7f, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf0, 0xfc, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x99, 0xcf, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x0f, 0x87, 0xc7, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7e, 0x1e, 0x07, 0x03, 0xc3, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7c, 0x1e, 0x07, 0x03, 0xc3, 0xf7, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x1e, 0x07, 0x03, 0xc7, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x30, 0x00, 0x00, 0x3e, 0x3f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xc1, 0xe0, 0xf0, 0x70, 0x3c, 0x1f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc1, 0xe0, 0x60, 0x30, 0x38, 0x1f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc1, 0xe0, 0xf0, 0x30, 0x3c, 0x1f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xa0, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x36, 0x18, 0x00, 0x00, 0x01, 0xe1, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf8, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3e, 0x1e, 0x0d, 0x00, 0x00, 0x01, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x63, 0x01, 0x00, 0x00, 0x00, 0x03, 0x3f, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, 0x00, 0x1e, 0x1f, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0xf0, 0x30, 0x38, 0x1e, 0x1f, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, 0x38, 0x1e, 0x1f, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xe3, 0xf0, 0x90, 0x00, 0x00, 0x01, 0x3f, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7c, 0x3e, 0x1f, 0x0f, 0x02, 0x80, 0x00, 0xfb, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, 0x1c, 0x1e, 0x07, 0x03, 0x81, 0x80, 0xf1, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, 0x1c, 0x1e, 0x03, 0x03, 0x81, 0x80, 0xf1, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x3e, 0x1e, 0x01, 0x04, 0x80, 0x00, 0x3f, 0xff, +0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0x30, 0x00, 0x80, 0x00, 0x00, 0x1f, 0xff, +0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xdf, 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x14, 0x0f, 0xff, +0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, +0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xcf, 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, +0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xef, 0xf3, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, +0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xfc, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x83, 0x40, 0xf1, 0xff, +0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x03, 0x81, 0xc0, 0xe0, 0xff, +0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x81, 0xc0, 0xe1, 0xff, +0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xfc, 0xfe, 0x3e, 0x00, 0x01, 0x86, 0x82, 0x41, 0x31, 0xff, +0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x1f, 0xff, +0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, 0xff, +0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, +0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, 0xff, +0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xcc, 0x40, 0x00, 0x1f, 0xff, +0xff, 0xff, 0x80, 0x00, 0x0f, 0xff, 0xfe, 0xfe, 0x00, 0x00, 0x03, 0x87, 0x83, 0xc1, 0xf1, 0xff, +0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xfc, 0x7c, 0x00, 0x00, 0x03, 0x07, 0x81, 0xc0, 0xe1, 0xff, +0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xfc, 0x7c, 0x00, 0x00, 0x07, 0x07, 0x83, 0xc0, 0xf1, 0xff, +0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0x87, 0xc3, 0x41, 0xfb, 0xff, +0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x18, 0xc8, 0x64, 0x02, 0x1f, 0xff, +0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xdf, 0xc0, 0x00, 0x00, 0x30, 0x78, 0x38, 0x1c, 0x1f, 0xff, +0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xcf, 0xc0, 0x00, 0x00, 0x70, 0x70, 0x38, 0x1c, 0x1f, 0xff, +0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0xf0, 0x78, 0x3c, 0x1e, 0x1f, 0xff, +0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x01, 0x9d, 0xcc, 0xc6, 0x23, 0x3f, 0xff, +0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x03, 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, +0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x07, 0x0f, 0x07, 0x83, 0xc1, 0xfb, 0xff, +0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x0f, 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, +0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x1f, 0x9f, 0x8f, 0xc3, 0xe1, 0xff, 0xff, +0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x31, 0xf8, 0xf8, 0x7c, 0x3f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, +0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, +0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf1, 0xf8, 0xf8, 0x7c, 0x3f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xce, 0xc6, 0x7f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3f, 0x0f, 0x87, 0xc3, 0xf3, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1f, 0x0f, 0x87, 0x83, 0xe3, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x0f, 0x3f, 0x0f, 0x87, 0xc3, 0xe3, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x9f, 0xcf, 0xc7, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xfb, 0xf8, 0xfc, 0x7f, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x73, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0xf7, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0xff, 0x9f, 0xcf, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x3f, 0x9f, 0xcf, 0xe7, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x9f, 0xcf, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; From b32e8187332b25ac73f0f14f56eda4af51e767f5 Mon Sep 17 00:00:00 2001 From: pablohc Date: Sun, 17 May 2026 22:24:59 +0200 Subject: [PATCH 88/93] style: reformat CrossLarge.h byte array layout --- src/images/CrossLarge.h | 239 ++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 130 deletions(-) diff --git a/src/images/CrossLarge.h b/src/images/CrossLarge.h index 9ce6777f06..86574c27e5 100644 --- a/src/images/CrossLarge.h +++ b/src/images/CrossLarge.h @@ -1,133 +1,112 @@ #pragma once #include -const unsigned char CrossLarge [] PROGMEM = { -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf8, 0xfe, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf1, 0xf0, 0xfc, 0x7f, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf0, 0xfc, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x99, 0xcf, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x0f, 0x87, 0xc7, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7e, 0x1e, 0x07, 0x03, 0xc3, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7c, 0x1e, 0x07, 0x03, 0xc3, 0xf7, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x1e, 0x07, 0x03, 0xc7, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x30, 0x00, 0x00, 0x3e, 0x3f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xc1, 0xe0, 0xf0, 0x70, 0x3c, 0x1f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc1, 0xe0, 0x60, 0x30, 0x38, 0x1f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc1, 0xe0, 0xf0, 0x30, 0x3c, 0x1f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xa0, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x36, 0x18, 0x00, 0x00, 0x01, 0xe1, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf8, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3e, 0x1e, 0x0d, 0x00, 0x00, 0x01, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x63, 0x01, 0x00, 0x00, 0x00, 0x03, 0x3f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, 0x00, 0x1e, 0x1f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0xf0, 0x30, 0x38, 0x1e, 0x1f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, 0x38, 0x1e, 0x1f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xe3, 0xf0, 0x90, 0x00, 0x00, 0x01, 0x3f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7c, 0x3e, 0x1f, 0x0f, 0x02, 0x80, 0x00, 0xfb, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, 0x1c, 0x1e, 0x07, 0x03, 0x81, 0x80, 0xf1, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, 0x1c, 0x1e, 0x03, 0x03, 0x81, 0x80, 0xf1, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x3e, 0x1e, 0x01, 0x04, 0x80, 0x00, 0x3f, 0xff, -0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0x30, 0x00, 0x80, 0x00, 0x00, 0x1f, 0xff, -0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xdf, 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x14, 0x0f, 0xff, -0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, -0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xcf, 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, -0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xef, 0xf3, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, -0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xfc, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x83, 0x40, 0xf1, 0xff, -0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x03, 0x81, 0xc0, 0xe0, 0xff, -0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x81, 0xc0, 0xe1, 0xff, -0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xfc, 0xfe, 0x3e, 0x00, 0x01, 0x86, 0x82, 0x41, 0x31, 0xff, -0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x1f, 0xff, -0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, 0xff, -0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, -0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, 0xff, -0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xcc, 0x40, 0x00, 0x1f, 0xff, -0xff, 0xff, 0x80, 0x00, 0x0f, 0xff, 0xfe, 0xfe, 0x00, 0x00, 0x03, 0x87, 0x83, 0xc1, 0xf1, 0xff, -0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xfc, 0x7c, 0x00, 0x00, 0x03, 0x07, 0x81, 0xc0, 0xe1, 0xff, -0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xfc, 0x7c, 0x00, 0x00, 0x07, 0x07, 0x83, 0xc0, 0xf1, 0xff, -0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0x87, 0xc3, 0x41, 0xfb, 0xff, -0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x18, 0xc8, 0x64, 0x02, 0x1f, 0xff, -0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xdf, 0xc0, 0x00, 0x00, 0x30, 0x78, 0x38, 0x1c, 0x1f, 0xff, -0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xcf, 0xc0, 0x00, 0x00, 0x70, 0x70, 0x38, 0x1c, 0x1f, 0xff, -0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0xf0, 0x78, 0x3c, 0x1e, 0x1f, 0xff, -0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x01, 0x9d, 0xcc, 0xc6, 0x23, 0x3f, 0xff, -0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x03, 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, -0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x07, 0x0f, 0x07, 0x83, 0xc1, 0xfb, 0xff, -0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x0f, 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, -0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x1f, 0x9f, 0x8f, 0xc3, 0xe1, 0xff, 0xff, -0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x31, 0xf8, 0xf8, 0x7c, 0x3f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, -0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, -0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf1, 0xf8, 0xf8, 0x7c, 0x3f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xce, 0xc6, 0x7f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3f, 0x0f, 0x87, 0xc3, 0xf3, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1f, 0x0f, 0x87, 0x83, 0xe3, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x0f, 0x3f, 0x0f, 0x87, 0xc3, 0xe3, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x9f, 0xcf, 0xc7, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xfb, 0xf8, 0xfc, 0x7f, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x73, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0xf7, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0xff, 0x9f, 0xcf, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x3f, 0x9f, 0xcf, 0xe7, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x9f, 0xcf, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; +const unsigned char CrossLarge[] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf8, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf1, 0xf0, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xf0, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfb, 0x99, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1f, 0x0f, 0x87, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7e, 0x1e, 0x07, + 0x03, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7c, 0x1e, 0x07, 0x03, 0xc3, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x1e, 0x07, 0x03, 0xc7, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x30, 0x00, 0x00, 0x3e, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xef, 0xc1, 0xe0, 0xf0, 0x70, 0x3c, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc7, 0xc1, 0xe0, 0x60, 0x30, 0x38, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc1, 0xe0, + 0xf0, 0x30, 0x3c, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xa0, 0x80, 0x00, 0x00, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x36, 0x18, 0x00, 0x00, 0x01, 0xe1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xf8, 0x1c, 0x0e, 0x07, 0x03, 0x01, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0x3e, 0x1e, 0x0d, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x63, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, + 0x00, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0xf0, 0x30, 0x38, 0x1e, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc1, 0xe0, 0xf0, 0x70, 0x38, 0x1e, 0x1f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xe3, 0xf0, 0x90, 0x00, 0x00, 0x01, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0x7c, 0x3e, 0x1f, 0x0f, 0x02, 0x80, 0x00, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, + 0x1c, 0x1e, 0x07, 0x03, 0x81, 0x80, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7c, 0x1c, 0x1e, 0x03, + 0x03, 0x81, 0x80, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0x3e, 0x1e, 0x01, 0x04, 0x80, 0x00, + 0x3f, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0x30, 0x00, 0x80, 0x00, 0x00, 0x1f, 0xff, 0xff, + 0xff, 0xc1, 0xff, 0xff, 0xff, 0xdf, 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x14, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0xff, + 0xff, 0xff, 0xcf, 0xc3, 0xc1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xcf, + 0xc3, 0xe1, 0xe0, 0x00, 0x70, 0x38, 0x1c, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xef, 0xf3, 0xe0, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xfc, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x83, + 0x40, 0xf1, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x03, 0x81, 0xc0, 0xe0, 0xff, + 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xf8, 0x7c, 0x3e, 0x00, 0x01, 0x07, 0x81, 0xc0, 0xe1, 0xff, 0xff, 0xff, 0x80, + 0x03, 0xff, 0xff, 0xfc, 0xfe, 0x3e, 0x00, 0x01, 0x86, 0x82, 0x41, 0x31, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xcf, 0xc7, 0xe0, + 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x70, + 0x38, 0x1c, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xcf, 0xc7, 0xe0, 0x00, 0x00, 0x78, 0x38, 0x1c, 0x0f, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xcc, 0x40, 0x00, 0x1f, 0xff, 0xff, 0xff, + 0x80, 0x00, 0x0f, 0xff, 0xfe, 0xfe, 0x00, 0x00, 0x03, 0x87, 0x83, 0xc1, 0xf1, 0xff, 0xff, 0xff, 0x80, 0x00, 0x07, + 0xff, 0xfc, 0x7c, 0x00, 0x00, 0x03, 0x07, 0x81, 0xc0, 0xe1, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xfc, 0x7c, + 0x00, 0x00, 0x07, 0x07, 0x83, 0xc0, 0xf1, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, + 0x87, 0xc3, 0x41, 0xfb, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x18, 0xc8, 0x64, 0x02, + 0x1f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xdf, 0xc0, 0x00, 0x00, 0x30, 0x78, 0x38, 0x1c, 0x1f, 0xff, 0xff, + 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xcf, 0xc0, 0x00, 0x00, 0x70, 0x70, 0x38, 0x1c, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x00, + 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0xf0, 0x78, 0x3c, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, + 0xc0, 0x00, 0x01, 0x9d, 0xcc, 0xc6, 0x23, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x03, + 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x07, 0x0f, 0x07, 0x83, + 0xc1, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x0f, 0x0f, 0x87, 0x83, 0xc1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x1f, 0x9f, 0x8f, 0xc3, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x31, 0xf8, 0xf8, 0x7c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x61, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf1, 0xf0, 0x78, 0x38, 0x1f, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf1, 0xf8, 0xf8, + 0x7c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xce, 0xc6, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3f, 0x0f, 0x87, 0xc3, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1f, 0x0f, 0x87, 0x83, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + 0x00, 0x00, 0x00, 0x0f, 0x3f, 0x0f, 0x87, 0xc3, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x1f, 0xff, 0x9f, 0xcf, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xfb, 0xf8, + 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x73, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0xf7, 0xf1, 0xf0, 0xf8, 0x7e, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, + 0x07, 0xff, 0xff, 0x9f, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x3f, + 0x9f, 0xcf, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x9f, 0xcf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, + 0x00, 0x3f, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xf8, + 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, + 0x00, 0x07, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, + 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, + 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x7f, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; From 8ade6499ebde65da89c38326422e06d2a3c2831b Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 15 May 2026 11:27:53 +0200 Subject: [PATCH 89/93] feat: add power button action menu Adds a 5th short press power button option 'Action Menu' that opens a navigable popup with Sleep, Refresh Screen, and Screenshot actions. Includes theme-aware popup rendering (Classic/Lyra/RoundedRaff) using drawDialogBackground and drawPopupSelection virtual methods. --- lib/I18n/translations/english.yaml | 1 + src/CrossPointSettings.h | 2 +- src/CrossPointState.h | 2 + src/SettingsList.h | 3 +- src/activities/reader/ReaderUtils.h | 9 +- .../util/PowerButtonMenuActivity.cpp | 116 ++++++++++++++++++ src/activities/util/PowerButtonMenuActivity.h | 39 ++++++ src/components/themes/BaseTheme.cpp | 15 +++ src/components/themes/BaseTheme.h | 2 + src/components/themes/lyra/LyraTheme.cpp | 16 +++ src/components/themes/lyra/LyraTheme.h | 2 + .../themes/roundedraff/RoundedRaffTheme.cpp | 16 +++ .../themes/roundedraff/RoundedRaffTheme.h | 4 +- src/main.cpp | 33 +++++ 14 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 src/activities/util/PowerButtonMenuActivity.cpp create mode 100644 src/activities/util/PowerButtonMenuActivity.h diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 4ee88b2dfc..1ec8388a0e 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -132,6 +132,7 @@ STR_IGNORE: "Ignore" STR_SLEEP: "Sleep" STR_PAGE_TURN: "Page Turn" STR_FORCE_REFRESH: "Refresh Screen" +STR_ACTION_MENU: "Action Menu" STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Landscape CW" STR_INVERTED: "Inverted" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 9c67463a81..b7080aa29c 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -135,7 +135,7 @@ class CrossPointSettings { }; // Short power button press actions - enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, FORCE_REFRESH = 3, SHORT_PWRBTN_COUNT }; + enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, FORCE_REFRESH = 3, MENU = 4, SHORT_PWRBTN_COUNT }; // Hide battery percentage enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT }; diff --git a/src/CrossPointState.h b/src/CrossPointState.h index 4b18f3b1cd..d4fd343f8d 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -15,6 +15,8 @@ class CrossPointState { uint8_t recentSleepFill = 0; // valid entries (0..SLEEP_RECENT_COUNT) uint8_t readerActivityLoadCount = 0; bool lastSleepFromReader = false; + uint8_t pendingPwrBtnAction = 0xFF; + bool pendingPageTurnFromMenu = false; // Returns true if idx was shown within the last checkCount picks. // Walks backwards from the most recently written slot. diff --git a/src/SettingsList.h b/src/SettingsList.h index 4ad42206bf..5089a4ce7e 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -169,7 +169,8 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist StrId::STR_LONG_PRESS_BEHAVIOR_ORIENTATION}, "longPressButtonBehavior", StrId::STR_CAT_CONTROLS), SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn, - {StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN, StrId::STR_FORCE_REFRESH}, + {StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN, StrId::STR_FORCE_REFRESH, + StrId::STR_ACTION_MENU}, "shortPwrBtn", StrId::STR_CAT_CONTROLS), // --- System --- diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index b483358383..a6ce2f1606 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -45,8 +46,12 @@ inline PageTurnResult detectPageTurn(const MappedInputManager& input) { input.wasPressed(MappedInputManager::Button::Left)) : (input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(MappedInputManager::Button::Left))); - const bool powerTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && - input.wasReleased(MappedInputManager::Button::Power); + const bool powerTurn = (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && + input.wasReleased(MappedInputManager::Button::Power)) || + APP_STATE.pendingPageTurnFromMenu; + if (APP_STATE.pendingPageTurnFromMenu) { + APP_STATE.pendingPageTurnFromMenu = false; + } const bool next = tiltNext || (usePress ? (input.wasPressed(MappedInputManager::Button::PageForward) || powerTurn || input.wasPressed(MappedInputManager::Button::Right)) : (input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn || diff --git a/src/activities/util/PowerButtonMenuActivity.cpp b/src/activities/util/PowerButtonMenuActivity.cpp new file mode 100644 index 0000000000..12e429060d --- /dev/null +++ b/src/activities/util/PowerButtonMenuActivity.cpp @@ -0,0 +1,116 @@ +#include "PowerButtonMenuActivity.h" + +#include +#include +#include +#include + +#include "CrossPointState.h" +#include "MappedInputManager.h" +#include "components/UITheme.h" +#include "fontIds.h" + +void PowerButtonMenuActivity::onEnter() { + Activity::onEnter(); + s_isActive = true; + selectedIndex = 0; + requestUpdate(); +} + +void PowerButtonMenuActivity::onExit() { + Activity::onExit(); + s_isActive = false; +} + +void PowerButtonMenuActivity::loop() { + buttonNavigator.onNext([this] { + selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast(items.size())); + requestUpdate(); + }); + + buttonNavigator.onPrevious([this] { + selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast(items.size())); + requestUpdate(); + }); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + APP_STATE.pendingPwrBtnAction = static_cast(items[selectedIndex].action); + LOG_DBG("PWRMENU", "Selected action: %d", APP_STATE.pendingPwrBtnAction); + finish(); + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + finish(); + return; + } +} + +void PowerButtonMenuActivity::render(RenderLock&&) { + const int screenWidth = renderer.getScreenWidth(); + const int screenHeight = renderer.getScreenHeight(); + + const int itemCount = static_cast(items.size()); + constexpr int dialogPadding = 20; + constexpr int titleHeight = 40; + constexpr int itemHeight = 35; + constexpr int hintsHeight = 40; + constexpr int dialogMarginTop = 15; + constexpr int dialogMarginBottom = 10; + + const int contentHeight = titleHeight + (itemCount * itemHeight) + hintsHeight; + const int dialogHeight = dialogMarginTop + contentHeight + dialogMarginBottom; + + int maxTextWidth = renderer.getTextWidth(UI_12_FONT_ID, tr(STR_ACTION_MENU), EpdFontFamily::BOLD); + for (const auto& item : items) { + const int w = renderer.getTextWidth(UI_10_FONT_ID, I18N.get(item.labelId)); + if (w > maxTextWidth) { + maxTextWidth = w; + } + } + const int dialogWidth = maxTextWidth + dialogPadding * 2 + 20; + + const int dialogX = (screenWidth - dialogWidth) / 2; + const int dialogY = (screenHeight - dialogHeight) / 2; + + GUI.drawDialogBackground(renderer, Rect{dialogX, dialogY, dialogWidth, dialogHeight}); + + const int titleWidth = renderer.getTextWidth(UI_12_FONT_ID, tr(STR_ACTION_MENU), EpdFontFamily::BOLD); + const int titleX = dialogX + (dialogWidth - titleWidth) / 2; + renderer.drawText(UI_12_FONT_ID, titleX, dialogY + dialogMarginTop + 5, tr(STR_ACTION_MENU), true, + EpdFontFamily::BOLD); + + const int separator1Y = dialogY + dialogMarginTop + titleHeight; + renderer.drawLine(dialogX + dialogPadding, separator1Y, dialogX + dialogWidth - dialogPadding, separator1Y); + + const int itemStartY = separator1Y + 5; + for (int i = 0; i < itemCount; i++) { + const int itemY = itemStartY + (i * itemHeight); + if (i == selectedIndex) { + GUI.drawPopupSelection(renderer, + Rect{dialogX + dialogPadding, itemY, dialogWidth - dialogPadding * 2, itemHeight - 2}, + I18N.get(items[i].labelId)); + } else { + renderer.drawText(UI_10_FONT_ID, dialogX + dialogPadding + 12, itemY + 8, I18N.get(items[i].labelId)); + } + } + + const int separator2Y = itemStartY + (itemCount * itemHeight); + renderer.drawLine(dialogX + dialogPadding, separator2Y, dialogX + dialogWidth - dialogPadding, separator2Y); + + const int hintsY = separator2Y + 8; + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); + const int hintAreaWidth = dialogWidth - dialogPadding * 2; + const int singleHintWidth = hintAreaWidth / 4; + const char* hintLabels[] = {labels.btn1, labels.btn2, labels.btn3, labels.btn4}; + + for (int i = 0; i < 4; i++) { + if (hintLabels[i] != nullptr && hintLabels[i][0] != '\0') { + const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, hintLabels[i]); + const int hintCenterX = dialogX + dialogPadding + (i * singleHintWidth) + singleHintWidth / 2; + renderer.drawText(UI_10_FONT_ID, hintCenterX - textWidth / 2, hintsY, hintLabels[i]); + } + } + + renderer.displayBuffer(HalDisplay::FAST_REFRESH); +} diff --git a/src/activities/util/PowerButtonMenuActivity.h b/src/activities/util/PowerButtonMenuActivity.h new file mode 100644 index 0000000000..7a1085ee48 --- /dev/null +++ b/src/activities/util/PowerButtonMenuActivity.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +#include "../Activity.h" +#include "CrossPointSettings.h" +#include "util/ButtonNavigator.h" + +class PowerButtonMenuActivity final : public Activity { + public: + explicit PowerButtonMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) + : Activity("PowerButtonMenu", renderer, mappedInput) {} + + static bool isActive() { return s_isActive; } + + void onEnter() override; + void onExit() override; + void loop() override; + void render(RenderLock&& lock) override; + + private: + static inline bool s_isActive = false; + + struct MenuItem { + CrossPointSettings::SHORT_PWRBTN action; + StrId labelId; + }; + + const std::vector items = { + {CrossPointSettings::SHORT_PWRBTN::SLEEP, StrId::STR_SLEEP}, + {CrossPointSettings::SHORT_PWRBTN::PAGE_TURN, StrId::STR_PAGE_TURN}, + {CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH, StrId::STR_FORCE_REFRESH}, + }; + + int selectedIndex = 0; + ButtonNavigator buttonNavigator; +}; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 64619c36e8..6c31adb01d 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -591,6 +591,21 @@ Rect BaseTheme::drawPopup(const GfxRenderer& renderer, const char* message) cons return Rect{x, y, w, h}; } +void BaseTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) const { + constexpr int outline = 2; + renderer.fillRect(rect.x - outline, rect.y - outline, rect.width + outline * 2, rect.height + outline * 2, true); + renderer.fillRect(rect.x, rect.y, rect.width, rect.height, false); +} + +void BaseTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { + renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true); + const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, text); + const int textX = rect.x + (rect.width - textWidth) / 2; + const int textLineHeight = renderer.getLineHeight(UI_10_FONT_ID); + const int textY = rect.y + (rect.height - textLineHeight) / 2; + renderer.drawText(UI_10_FONT_ID, textX, textY, text, false); +} + void BaseTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const { constexpr int barHeight = 4; const int barWidth = layout.width - 30; // twice the margin in drawPopup to match text width diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index ec4167061d..45dba2ea14 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -158,6 +158,8 @@ class BaseTheme { const std::function& buttonLabel, const std::function& rowIcon) const; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; + virtual void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const; + virtual void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, std::string title, const int paddingBottom = 0, const int textYOffset = 0) const; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index e5cb06afc0..784a95b01c 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -564,6 +564,22 @@ Rect LyraTheme::drawPopup(const GfxRenderer& renderer, const char* message) cons return Rect{x, y, w, h}; } +void LyraTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) const { + constexpr int outline = 2; + renderer.fillRoundedRect(rect.x - outline, rect.y - outline, rect.width + outline * 2, rect.height + outline * 2, + cornerRadius + outline, Color::White); + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black); +} + +void LyraTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray); + const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, text); + const int textX = rect.x + (rect.width - textWidth) / 2; + const int textLineHeight = renderer.getLineHeight(UI_10_FONT_ID); + const int textY = rect.y + (rect.height - textLineHeight) / 2; + renderer.drawText(UI_10_FONT_ID, textX, textY, text, true); +} + void LyraTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const { constexpr int barHeight = 4; diff --git a/src/components/themes/lyra/LyraTheme.h b/src/components/themes/lyra/LyraTheme.h index d984019e17..d8137acafb 100644 --- a/src/components/themes/lyra/LyraTheme.h +++ b/src/components/themes/lyra/LyraTheme.h @@ -71,6 +71,8 @@ class LyraTheme : public BaseTheme { std::function storeCoverBuffer) const override; void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const; Rect drawPopup(const GfxRenderer& renderer, const char* message) const override; + void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const override; + void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const override; void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override; bool showsFileIcons() const override { return true; } }; diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.cpp b/src/components/themes/roundedraff/RoundedRaffTheme.cpp index 3d5ea90ca2..3d4b0866d7 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.cpp +++ b/src/components/themes/roundedraff/RoundedRaffTheme.cpp @@ -431,3 +431,19 @@ void RoundedRaffTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, renderer.setOrientation(origOrientation); } + +void RoundedRaffTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) const { + constexpr int outline = 2; + renderer.fillRoundedRect(rect.x - outline, rect.y - outline, rect.width + outline * 2, rect.height + outline * 2, + kMenuRadius + outline, Color::White); + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, kMenuRadius, Color::Black); +} + +void RoundedRaffTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, kMenuRadius, Color::Black); + const int textWidth = renderer.getTextWidth(kTitleFontId, text, EpdFontFamily::BOLD); + const int textX = rect.x + (rect.width - textWidth) / 2; + const int textLineHeight = renderer.getLineHeight(kTitleFontId); + const int textY = rect.y + (rect.height - textLineHeight) / 2; + renderer.drawText(kTitleFontId, textX, textY, text, false, EpdFontFamily::BOLD); +} diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.h b/src/components/themes/roundedraff/RoundedRaffTheme.h index b73a5b5555..c4abb36547 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.h +++ b/src/components/themes/roundedraff/RoundedRaffTheme.h @@ -70,6 +70,8 @@ class RoundedRaffTheme : public BaseTheme { const std::function& rowValue = nullptr, bool highlightValue = false, const std::function& rowDimmed = nullptr) const override; void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, - const char* btn4) const override; + const char* btn4) const override; + void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const override; + void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const override; bool homeMenuShowsContinueReading() const { return true; } }; diff --git a/src/main.cpp b/src/main.cpp index 189ea64c76..0590a8cb73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ #include "activities/Activity.h" #include "activities/ActivityManager.h" #include "activities/settings/SdFirmwareUpdateActivity.h" +#include "activities/util/PowerButtonMenuActivity.h" #include "components/UITheme.h" #include "fontIds.h" #include "util/ButtonNavigator.h" @@ -490,6 +491,38 @@ void loop() { renderer.displayBuffer(HalDisplay::HALF_REFRESH); } + // Dispatch pending action selected from power button action menu. + if (APP_STATE.pendingPwrBtnAction != 0xFF) { + const uint8_t action = APP_STATE.pendingPwrBtnAction; + APP_STATE.pendingPwrBtnAction = 0xFF; + switch (action) { + case CrossPointSettings::SHORT_PWRBTN::SLEEP: + LOG_DBG("MAIN", "Power button menu: sleep"); + enterDeepSleep(); + return; + case CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH: + LOG_DBG("MAIN", "Power button menu: refresh screen"); + { + RenderLock lock; + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } + break; + case CrossPointSettings::SHORT_PWRBTN::PAGE_TURN: + LOG_DBG("MAIN", "Power button menu: page turn"); + APP_STATE.pendingPageTurnFromMenu = true; + break; + default: + break; + } + } + + // Show power button action menu on short press when set to MENU mode. + if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::MENU && + mappedInputManager.wasReleased(MappedInputManager::Button::Power) && + !PowerButtonMenuActivity::isActive()) { + activityManager.pushActivity(std::make_unique(renderer, mappedInputManager)); + } + // Refresh the battery icon when USB is plugged or unplugged. // Placed after sleep guards so we never queue a render that won't be processed. if (gpio.wasUsbStateChanged()) { From d4f36f4b0bb1f4d86298731f1eef44fd422bb949 Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 15 May 2026 13:12:11 +0200 Subject: [PATCH 90/93] fix: wrap action menu screenshot in RenderLock to prevent persistent border artifact --- src/CrossPointState.h | 1 - src/activities/reader/ReaderUtils.h | 9 +-- .../util/PowerButtonMenuActivity.cpp | 67 +++++++------------ src/activities/util/PowerButtonMenuActivity.h | 10 +-- src/components/themes/BaseTheme.cpp | 13 ++-- src/components/themes/BaseTheme.h | 3 +- src/components/themes/lyra/LyraTheme.cpp | 21 +++--- src/components/themes/lyra/LyraTheme.h | 3 +- .../themes/roundedraff/RoundedRaffTheme.cpp | 21 +++--- .../themes/roundedraff/RoundedRaffTheme.h | 5 +- src/main.cpp | 52 +++++++++----- 11 files changed, 104 insertions(+), 101 deletions(-) diff --git a/src/CrossPointState.h b/src/CrossPointState.h index d4fd343f8d..588395a6b2 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -16,7 +16,6 @@ class CrossPointState { uint8_t readerActivityLoadCount = 0; bool lastSleepFromReader = false; uint8_t pendingPwrBtnAction = 0xFF; - bool pendingPageTurnFromMenu = false; // Returns true if idx was shown within the last checkCount picks. // Walks backwards from the most recently written slot. diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index a6ce2f1606..b483358383 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -46,12 +45,8 @@ inline PageTurnResult detectPageTurn(const MappedInputManager& input) { input.wasPressed(MappedInputManager::Button::Left)) : (input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(MappedInputManager::Button::Left))); - const bool powerTurn = (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && - input.wasReleased(MappedInputManager::Button::Power)) || - APP_STATE.pendingPageTurnFromMenu; - if (APP_STATE.pendingPageTurnFromMenu) { - APP_STATE.pendingPageTurnFromMenu = false; - } + const bool powerTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && + input.wasReleased(MappedInputManager::Button::Power); const bool next = tiltNext || (usePress ? (input.wasPressed(MappedInputManager::Button::PageForward) || powerTurn || input.wasPressed(MappedInputManager::Button::Right)) : (input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn || diff --git a/src/activities/util/PowerButtonMenuActivity.cpp b/src/activities/util/PowerButtonMenuActivity.cpp index 12e429060d..34ee99e472 100644 --- a/src/activities/util/PowerButtonMenuActivity.cpp +++ b/src/activities/util/PowerButtonMenuActivity.cpp @@ -34,7 +34,7 @@ void PowerButtonMenuActivity::loop() { }); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - APP_STATE.pendingPwrBtnAction = static_cast(items[selectedIndex].action); + APP_STATE.pendingPwrBtnAction = 10 + static_cast(items[selectedIndex].action); LOG_DBG("PWRMENU", "Selected action: %d", APP_STATE.pendingPwrBtnAction); finish(); return; @@ -51,65 +51,44 @@ void PowerButtonMenuActivity::render(RenderLock&&) { const int screenHeight = renderer.getScreenHeight(); const int itemCount = static_cast(items.size()); - constexpr int dialogPadding = 20; - constexpr int titleHeight = 40; - constexpr int itemHeight = 35; - constexpr int hintsHeight = 40; - constexpr int dialogMarginTop = 15; - constexpr int dialogMarginBottom = 10; + constexpr int innerPadding = 16; + constexpr int itemSpacing = 6; + constexpr int selectionHPadding = 8; + constexpr int selectionVPadding = 4; - const int contentHeight = titleHeight + (itemCount * itemHeight) + hintsHeight; - const int dialogHeight = dialogMarginTop + contentHeight + dialogMarginBottom; + const int titleHeight = renderer.getLineHeight(UI_12_FONT_ID); + const int itemHeight = renderer.getLineHeight(UI_10_FONT_ID); + const int listHeight = itemHeight * itemCount + itemSpacing * (itemCount - 1); + const int contentHeight = titleHeight + 10 + listHeight; + const int dialogHeight = contentHeight + innerPadding * 2; int maxTextWidth = renderer.getTextWidth(UI_12_FONT_ID, tr(STR_ACTION_MENU), EpdFontFamily::BOLD); for (const auto& item : items) { - const int w = renderer.getTextWidth(UI_10_FONT_ID, I18N.get(item.labelId)); + const int w = renderer.getTextWidth(UI_10_FONT_ID, I18N.get(item.labelId), EpdFontFamily::BOLD); if (w > maxTextWidth) { maxTextWidth = w; } } - const int dialogWidth = maxTextWidth + dialogPadding * 2 + 20; + const int dialogWidth = std::min((maxTextWidth + innerPadding * 2) * 12 / 10, screenWidth - 20); const int dialogX = (screenWidth - dialogWidth) / 2; const int dialogY = (screenHeight - dialogHeight) / 2; GUI.drawDialogBackground(renderer, Rect{dialogX, dialogY, dialogWidth, dialogHeight}); - const int titleWidth = renderer.getTextWidth(UI_12_FONT_ID, tr(STR_ACTION_MENU), EpdFontFamily::BOLD); - const int titleX = dialogX + (dialogWidth - titleWidth) / 2; - renderer.drawText(UI_12_FONT_ID, titleX, dialogY + dialogMarginTop + 5, tr(STR_ACTION_MENU), true, - EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, dialogY + innerPadding, tr(STR_ACTION_MENU), true, EpdFontFamily::BOLD); - const int separator1Y = dialogY + dialogMarginTop + titleHeight; - renderer.drawLine(dialogX + dialogPadding, separator1Y, dialogX + dialogWidth - dialogPadding, separator1Y); - - const int itemStartY = separator1Y + 5; + int y = dialogY + innerPadding + titleHeight + 10; for (int i = 0; i < itemCount; i++) { - const int itemY = itemStartY + (i * itemHeight); - if (i == selectedIndex) { - GUI.drawPopupSelection(renderer, - Rect{dialogX + dialogPadding, itemY, dialogWidth - dialogPadding * 2, itemHeight - 2}, - I18N.get(items[i].labelId)); - } else { - renderer.drawText(UI_10_FONT_ID, dialogX + dialogPadding + 12, itemY + 8, I18N.get(items[i].labelId)); - } - } - - const int separator2Y = itemStartY + (itemCount * itemHeight); - renderer.drawLine(dialogX + dialogPadding, separator2Y, dialogX + dialogWidth - dialogPadding, separator2Y); - - const int hintsY = separator2Y + 8; - const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); - const int hintAreaWidth = dialogWidth - dialogPadding * 2; - const int singleHintWidth = hintAreaWidth / 4; - const char* hintLabels[] = {labels.btn1, labels.btn2, labels.btn3, labels.btn4}; - - for (int i = 0; i < 4; i++) { - if (hintLabels[i] != nullptr && hintLabels[i][0] != '\0') { - const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, hintLabels[i]); - const int hintCenterX = dialogX + dialogPadding + (i * singleHintWidth) + singleHintWidth / 2; - renderer.drawText(UI_10_FONT_ID, hintCenterX - textWidth / 2, hintsY, hintLabels[i]); - } + const int itemY = y + i * (itemHeight + itemSpacing); + const bool selected = (i == selectedIndex); + const char* labelText = I18N.get(items[i].labelId); + const int labelWidth = renderer.getTextWidth(UI_10_FONT_ID, labelText, EpdFontFamily::BOLD); + const int labelX = dialogX + (dialogWidth - labelWidth) / 2; + + Rect itemRect(labelX - selectionHPadding, itemY - selectionVPadding, labelWidth + selectionHPadding * 2, + itemHeight + selectionVPadding * 2); + GUI.drawPopupSelection(renderer, UI_10_FONT_ID, itemRect, labelText, selected); } renderer.displayBuffer(HalDisplay::FAST_REFRESH); diff --git a/src/activities/util/PowerButtonMenuActivity.h b/src/activities/util/PowerButtonMenuActivity.h index 7a1085ee48..840e0b9a83 100644 --- a/src/activities/util/PowerButtonMenuActivity.h +++ b/src/activities/util/PowerButtonMenuActivity.h @@ -10,6 +10,8 @@ class PowerButtonMenuActivity final : public Activity { public: + enum class MenuAction { SLEEP, REFRESH_SCREEN, SCREENSHOT }; + explicit PowerButtonMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("PowerButtonMenu", renderer, mappedInput) {} @@ -24,14 +26,14 @@ class PowerButtonMenuActivity final : public Activity { static inline bool s_isActive = false; struct MenuItem { - CrossPointSettings::SHORT_PWRBTN action; + MenuAction action; StrId labelId; }; const std::vector items = { - {CrossPointSettings::SHORT_PWRBTN::SLEEP, StrId::STR_SLEEP}, - {CrossPointSettings::SHORT_PWRBTN::PAGE_TURN, StrId::STR_PAGE_TURN}, - {CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH, StrId::STR_FORCE_REFRESH}, + {MenuAction::SLEEP, StrId::STR_SLEEP}, + {MenuAction::REFRESH_SCREEN, StrId::STR_FORCE_REFRESH}, + {MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON}, }; int selectedIndex = 0; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 6c31adb01d..d667ba69fc 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -597,13 +597,16 @@ void BaseTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) con renderer.fillRect(rect.x, rect.y, rect.width, rect.height, false); } -void BaseTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { - renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true); - const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, text); +void BaseTheme::drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const { + if (selected) { + renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true); + } + const int textWidth = renderer.getTextWidth(fontId, text); const int textX = rect.x + (rect.width - textWidth) / 2; - const int textLineHeight = renderer.getLineHeight(UI_10_FONT_ID); + const int textLineHeight = renderer.getLineHeight(fontId); const int textY = rect.y + (rect.height - textLineHeight) / 2; - renderer.drawText(UI_10_FONT_ID, textX, textY, text, false); + renderer.drawText(fontId, textX, textY, text, !selected); } void BaseTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const { diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index 45dba2ea14..8cafbc4fe1 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -159,7 +159,8 @@ class BaseTheme { const std::function& rowIcon) const; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; virtual void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const; - virtual void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const; + virtual void drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, std::string title, const int paddingBottom = 0, const int textYOffset = 0) const; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 784a95b01c..cad0fc7c12 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -567,17 +567,20 @@ Rect LyraTheme::drawPopup(const GfxRenderer& renderer, const char* message) cons void LyraTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) const { constexpr int outline = 2; renderer.fillRoundedRect(rect.x - outline, rect.y - outline, rect.width + outline * 2, rect.height + outline * 2, - cornerRadius + outline, Color::White); - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black); + cornerRadius + outline, Color::Black); + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::White); } -void LyraTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray); - const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, text); - const int textX = rect.x + (rect.width - textWidth) / 2; - const int textLineHeight = renderer.getLineHeight(UI_10_FONT_ID); - const int textY = rect.y + (rect.height - textLineHeight) / 2; - renderer.drawText(UI_10_FONT_ID, textX, textY, text, true); +void LyraTheme::drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const { + const int textH = renderer.getLineHeight(fontId); + const int textW = renderer.getTextWidth(fontId, text); + const int textY = rect.y + (rect.height - textH) / 2; + const int textX = rect.x + (rect.width - textW) / 2; + if (selected) { + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray); + } + renderer.drawText(fontId, textX, textY, text, true); } void LyraTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const { diff --git a/src/components/themes/lyra/LyraTheme.h b/src/components/themes/lyra/LyraTheme.h index d8137acafb..068878b6e4 100644 --- a/src/components/themes/lyra/LyraTheme.h +++ b/src/components/themes/lyra/LyraTheme.h @@ -72,7 +72,8 @@ class LyraTheme : public BaseTheme { void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const; Rect drawPopup(const GfxRenderer& renderer, const char* message) const override; void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const override; - void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const override; + void drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const override; void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override; bool showsFileIcons() const override { return true; } }; diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.cpp b/src/components/themes/roundedraff/RoundedRaffTheme.cpp index 3d4b0866d7..7ce0650780 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.cpp +++ b/src/components/themes/roundedraff/RoundedRaffTheme.cpp @@ -435,15 +435,18 @@ void RoundedRaffTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, void RoundedRaffTheme::drawDialogBackground(const GfxRenderer& renderer, Rect rect) const { constexpr int outline = 2; renderer.fillRoundedRect(rect.x - outline, rect.y - outline, rect.width + outline * 2, rect.height + outline * 2, - kMenuRadius + outline, Color::White); - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, kMenuRadius, Color::Black); + kRowRadius + outline, Color::Black); + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, kRowRadius, Color::White); } -void RoundedRaffTheme::drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const { - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, kMenuRadius, Color::Black); - const int textWidth = renderer.getTextWidth(kTitleFontId, text, EpdFontFamily::BOLD); - const int textX = rect.x + (rect.width - textWidth) / 2; - const int textLineHeight = renderer.getLineHeight(kTitleFontId); - const int textY = rect.y + (rect.height - textLineHeight) / 2; - renderer.drawText(kTitleFontId, textX, textY, text, false, EpdFontFamily::BOLD); +void RoundedRaffTheme::drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const { + const int textH = renderer.getLineHeight(fontId); + const int textW = renderer.getTextWidth(fontId, text); + const int textY = rect.y + (rect.height - textH) / 2; + const int textX = rect.x + (rect.width - textW) / 2; + if (selected) { + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, 6, Color::Black); + } + renderer.drawText(fontId, textX, textY, text, !selected); } diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.h b/src/components/themes/roundedraff/RoundedRaffTheme.h index c4abb36547..f64a41490e 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.h +++ b/src/components/themes/roundedraff/RoundedRaffTheme.h @@ -70,8 +70,9 @@ class RoundedRaffTheme : public BaseTheme { const std::function& rowValue = nullptr, bool highlightValue = false, const std::function& rowDimmed = nullptr) const override; void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, - const char* btn4) const override; + const char* btn4) const override; void drawDialogBackground(const GfxRenderer& renderer, Rect rect) const override; - void drawPopupSelection(const GfxRenderer& renderer, Rect rect, const char* text) const override; + void drawPopupSelection(const GfxRenderer& renderer, int fontId, Rect rect, const char* text, + bool selected) const override; bool homeMenuShowsContinueReading() const { return true; } }; diff --git a/src/main.cpp b/src/main.cpp index 0590a8cb73..5c8e54a3ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -495,31 +495,47 @@ void loop() { if (APP_STATE.pendingPwrBtnAction != 0xFF) { const uint8_t action = APP_STATE.pendingPwrBtnAction; APP_STATE.pendingPwrBtnAction = 0xFF; - switch (action) { - case CrossPointSettings::SHORT_PWRBTN::SLEEP: - LOG_DBG("MAIN", "Power button menu: sleep"); - enterDeepSleep(); - return; - case CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH: - LOG_DBG("MAIN", "Power button menu: refresh screen"); - { + if (action >= 10) { + // Actions from the power button menu (offset by 10) + switch (action - 10) { + case 0: // SLEEP + LOG_DBG("MAIN", "Power button menu: sleep"); + enterDeepSleep(); + return; + case 1: // REFRESH_SCREEN + LOG_DBG("MAIN", "Power button menu: refresh screen"); + { + RenderLock lock; + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } + break; + case 2: // SCREENSHOT + LOG_DBG("MAIN", "Power button menu: screenshot"); + { + RenderLock lock; + ScreenshotUtil::takeScreenshot(renderer); + } + break; + } + } else { + // Direct SHORT_PWRBTN actions (not from menu) + switch (action) { + case CrossPointSettings::SHORT_PWRBTN::SLEEP: + enterDeepSleep(); + return; + case CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH: { RenderLock lock; renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } - break; - case CrossPointSettings::SHORT_PWRBTN::PAGE_TURN: - LOG_DBG("MAIN", "Power button menu: page turn"); - APP_STATE.pendingPageTurnFromMenu = true; - break; - default: - break; + } break; + default: + break; + } } } // Show power button action menu on short press when set to MENU mode. if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::MENU && - mappedInputManager.wasReleased(MappedInputManager::Button::Power) && - !PowerButtonMenuActivity::isActive()) { + mappedInputManager.wasReleased(MappedInputManager::Button::Power) && !PowerButtonMenuActivity::isActive()) { activityManager.pushActivity(std::make_unique(renderer, mappedInputManager)); } From 13c456d095501eea7f37800b799c01b3de19c0bc Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 15 May 2026 15:04:42 +0200 Subject: [PATCH 91/93] fix: resolve cppcheck knownConditionTrueFalse false positive --- src/activities/util/PowerButtonMenuActivity.cpp | 2 ++ src/activities/util/PowerButtonMenuActivity.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/activities/util/PowerButtonMenuActivity.cpp b/src/activities/util/PowerButtonMenuActivity.cpp index 34ee99e472..31ba3fcad7 100644 --- a/src/activities/util/PowerButtonMenuActivity.cpp +++ b/src/activities/util/PowerButtonMenuActivity.cpp @@ -10,6 +10,8 @@ #include "components/UITheme.h" #include "fontIds.h" +bool PowerButtonMenuActivity::isActive() { return s_isActive; } + void PowerButtonMenuActivity::onEnter() { Activity::onEnter(); s_isActive = true; diff --git a/src/activities/util/PowerButtonMenuActivity.h b/src/activities/util/PowerButtonMenuActivity.h index 840e0b9a83..161be59d96 100644 --- a/src/activities/util/PowerButtonMenuActivity.h +++ b/src/activities/util/PowerButtonMenuActivity.h @@ -15,7 +15,7 @@ class PowerButtonMenuActivity final : public Activity { explicit PowerButtonMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("PowerButtonMenu", renderer, mappedInput) {} - static bool isActive() { return s_isActive; } + static bool isActive(); void onEnter() override; void onExit() override; From 130612c4b8ce09ee520f298bfbebb0415568133b Mon Sep 17 00:00:00 2001 From: pablohc Date: Fri, 15 May 2026 16:24:35 +0200 Subject: [PATCH 92/93] refactor: separate menu action state and position popup at 40% height --- src/CrossPointState.h | 2 +- .../util/PowerButtonMenuActivity.cpp | 6 +- src/activities/util/PowerButtonMenuActivity.h | 2 +- src/main.cpp | 58 +++++++------------ 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/CrossPointState.h b/src/CrossPointState.h index 588395a6b2..8ca22ef6b5 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -15,7 +15,7 @@ class CrossPointState { uint8_t recentSleepFill = 0; // valid entries (0..SLEEP_RECENT_COUNT) uint8_t readerActivityLoadCount = 0; bool lastSleepFromReader = false; - uint8_t pendingPwrBtnAction = 0xFF; + uint8_t pendingMenuAction = 0xFF; // Returns true if idx was shown within the last checkCount picks. // Walks backwards from the most recently written slot. diff --git a/src/activities/util/PowerButtonMenuActivity.cpp b/src/activities/util/PowerButtonMenuActivity.cpp index 31ba3fcad7..5333ab410b 100644 --- a/src/activities/util/PowerButtonMenuActivity.cpp +++ b/src/activities/util/PowerButtonMenuActivity.cpp @@ -36,8 +36,8 @@ void PowerButtonMenuActivity::loop() { }); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - APP_STATE.pendingPwrBtnAction = 10 + static_cast(items[selectedIndex].action); - LOG_DBG("PWRMENU", "Selected action: %d", APP_STATE.pendingPwrBtnAction); + APP_STATE.pendingMenuAction = static_cast(items[selectedIndex].action); + LOG_DBG("PWRMENU", "Selected action: %d", APP_STATE.pendingMenuAction); finish(); return; } @@ -74,7 +74,7 @@ void PowerButtonMenuActivity::render(RenderLock&&) { const int dialogWidth = std::min((maxTextWidth + innerPadding * 2) * 12 / 10, screenWidth - 20); const int dialogX = (screenWidth - dialogWidth) / 2; - const int dialogY = (screenHeight - dialogHeight) / 2; + const int dialogY = screenHeight * 2 / 5 - dialogHeight / 2; GUI.drawDialogBackground(renderer, Rect{dialogX, dialogY, dialogWidth, dialogHeight}); diff --git a/src/activities/util/PowerButtonMenuActivity.h b/src/activities/util/PowerButtonMenuActivity.h index 161be59d96..7ededc672c 100644 --- a/src/activities/util/PowerButtonMenuActivity.h +++ b/src/activities/util/PowerButtonMenuActivity.h @@ -10,7 +10,7 @@ class PowerButtonMenuActivity final : public Activity { public: - enum class MenuAction { SLEEP, REFRESH_SCREEN, SCREENSHOT }; + enum class MenuAction : uint8_t { SLEEP, REFRESH_SCREEN, SCREENSHOT, NONE = 0xFF }; explicit PowerButtonMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("PowerButtonMenu", renderer, mappedInput) {} diff --git a/src/main.cpp b/src/main.cpp index 5c8e54a3ca..b0081d1e7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -492,44 +492,30 @@ void loop() { } // Dispatch pending action selected from power button action menu. - if (APP_STATE.pendingPwrBtnAction != 0xFF) { - const uint8_t action = APP_STATE.pendingPwrBtnAction; - APP_STATE.pendingPwrBtnAction = 0xFF; - if (action >= 10) { - // Actions from the power button menu (offset by 10) - switch (action - 10) { - case 0: // SLEEP - LOG_DBG("MAIN", "Power button menu: sleep"); - enterDeepSleep(); - return; - case 1: // REFRESH_SCREEN - LOG_DBG("MAIN", "Power button menu: refresh screen"); - { - RenderLock lock; - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } - break; - case 2: // SCREENSHOT - LOG_DBG("MAIN", "Power button menu: screenshot"); - { - RenderLock lock; - ScreenshotUtil::takeScreenshot(renderer); - } - break; - } - } else { - // Direct SHORT_PWRBTN actions (not from menu) - switch (action) { - case CrossPointSettings::SHORT_PWRBTN::SLEEP: - enterDeepSleep(); - return; - case CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH: { + if (APP_STATE.pendingMenuAction != static_cast(PowerButtonMenuActivity::MenuAction::NONE)) { + const auto action = static_cast(APP_STATE.pendingMenuAction); + APP_STATE.pendingMenuAction = static_cast(PowerButtonMenuActivity::MenuAction::NONE); + switch (action) { + case PowerButtonMenuActivity::MenuAction::SLEEP: + LOG_DBG("MAIN", "Power button menu: sleep"); + enterDeepSleep(); + return; + case PowerButtonMenuActivity::MenuAction::REFRESH_SCREEN: + LOG_DBG("MAIN", "Power button menu: refresh screen"); + { RenderLock lock; renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } break; - default: - break; - } + } + break; + case PowerButtonMenuActivity::MenuAction::SCREENSHOT: + LOG_DBG("MAIN", "Power button menu: screenshot"); + { + RenderLock lock; + ScreenshotUtil::takeScreenshot(renderer); + } + break; + default: + break; } } From ed1f342445ba4ccf30b796540cb8ff69b588263f Mon Sep 17 00:00:00 2001 From: pablohc Date: Mon, 30 Mar 2026 23:25:18 +0200 Subject: [PATCH 93/93] refactor: remove border drawing from bottom and side button hints --- src/components/themes/BaseTheme.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index d667ba69fc..95acba9d19 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -151,8 +151,6 @@ void BaseTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c // Only draw if the label is non-empty if (labels[i] != nullptr && labels[i][0] != '\0') { const int x = buttonPositions[i]; - renderer.fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false); - renderer.drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight); const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, labels[i]); const int textX = x + (buttonWidth - 1 - textWidth) / 2; renderer.drawText(UI_10_FONT_ID, textX, pageHeight - buttonY + textYOffset, labels[i]); @@ -169,12 +167,10 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top constexpr int buttonMargin = 4; if (gpio.deviceIsX3()) { - // X3 layout: Up on left side, Down on right side, positioned higher constexpr int x3ButtonY = 155; if (topBtn != nullptr && topBtn[0] != '\0') { const int leftX = buttonMargin; - renderer.drawRect(leftX, x3ButtonY, buttonWidth, buttonHeight); const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, topBtn); const int textHeight = renderer.getTextHeight(SMALL_FONT_ID); const int textX = leftX + (buttonWidth - textHeight) / 2; @@ -184,7 +180,6 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top if (bottomBtn != nullptr && bottomBtn[0] != '\0') { const int rightX = screenWidth - buttonMargin - buttonWidth; - renderer.drawRect(rightX, x3ButtonY, buttonWidth, buttonHeight); const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, bottomBtn); const int textHeight = renderer.getTextHeight(SMALL_FONT_ID); const int textX = rightX + (buttonWidth - textHeight) / 2; @@ -192,28 +187,10 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top renderer.drawTextRotated90CW(SMALL_FONT_ID, textX, textY, bottomBtn); } } else { - // X4 layout: Both buttons stacked on right side constexpr int topButtonY = 345; const char* labels[] = {topBtn, bottomBtn}; const int x = screenWidth - buttonMargin - buttonWidth; - if (topBtn != nullptr && topBtn[0] != '\0') { - renderer.drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); - renderer.drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); - renderer.drawLine(x + buttonWidth - 1, topButtonY, x + buttonWidth - 1, topButtonY + buttonHeight - 1); - } - - if ((topBtn != nullptr && topBtn[0] != '\0') || (bottomBtn != nullptr && bottomBtn[0] != '\0')) { - renderer.drawLine(x, topButtonY + buttonHeight, x + buttonWidth - 1, topButtonY + buttonHeight); - } - - if (bottomBtn != nullptr && bottomBtn[0] != '\0') { - renderer.drawLine(x, topButtonY + buttonHeight, x, topButtonY + 2 * buttonHeight - 1); - renderer.drawLine(x + buttonWidth - 1, topButtonY + buttonHeight, x + buttonWidth - 1, - topButtonY + 2 * buttonHeight - 1); - renderer.drawLine(x, topButtonY + 2 * buttonHeight - 1, x + buttonWidth - 1, topButtonY + 2 * buttonHeight - 1); - } - for (int i = 0; i < 2; i++) { if (labels[i] != nullptr && labels[i][0] != '\0') { const int y = topButtonY + i * buttonHeight;