From c1b8e531386b6bb6eeba26e74e8c02bbbd98df91 Mon Sep 17 00:00:00 2001 From: cottongin Date: Thu, 19 Feb 2026 13:20:30 -0500 Subject: [PATCH] fix: Port upstream 1.1.0-rc fixes (glyph null-safety, PNGdec wide image buffer) Cherry-pick two bug fixes from upstream PR #992: - fix(GfxRenderer): Null-safety in getSpaceWidth/getTextAdvanceX to prevent Load access fault when bold/italic font variants lack certain glyphs (upstream 3e2c518) - fix(PNGdec): Increase PNG_MAX_BUFFERED_PIXELS to 16416 for 2048px wide images and add pre-decode buffer overflow guard (upstream b8e743e) Co-authored-by: Cursor --- .../converters/PngToFramebufferConverter.cpp | 38 +++++++++++++++++++ lib/GfxRenderer/GfxRenderer.cpp | 7 +++- platformio.ini | 6 +-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp index f54e5e66..7925fa62 100644 --- a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp @@ -90,6 +90,32 @@ int32_t pngSeekWithHandle(PNGFILE* pFile, int32_t pos) { constexpr size_t PNG_DECODER_APPROX_SIZE = 44 * 1024; // ~42 KB + overhead constexpr size_t MIN_FREE_HEAP_FOR_PNG = PNG_DECODER_APPROX_SIZE + 16 * 1024; // decoder + 16 KB headroom +// PNGdec keeps TWO scanlines in its internal ucPixels buffer (current + previous) +// and each scanline includes a leading filter byte. +// Required storage is therefore approximately: 2 * (pitch + 1) + alignment slack. +// If PNG_MAX_BUFFERED_PIXELS is smaller than this requirement for a given image, +// PNGdec can overrun its internal buffer before our draw callback executes. +int bytesPerPixelFromType(int pixelType) { + switch (pixelType) { + case PNG_PIXEL_TRUECOLOR: + return 3; + case PNG_PIXEL_GRAY_ALPHA: + return 2; + case PNG_PIXEL_TRUECOLOR_ALPHA: + return 4; + case PNG_PIXEL_GRAYSCALE: + case PNG_PIXEL_INDEXED: + default: + return 1; + } +} + +int requiredPngInternalBufferBytes(int srcWidth, int pixelType) { + // +1 filter byte per scanline, *2 for current+previous lines, +32 for alignment margin. + int pitch = srcWidth * bytesPerPixelFromType(pixelType); + return ((pitch + 1) * 2) + 32; +} + // Convert entire source line to grayscale with alpha blending to white background. // For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards. // Processing the whole line at once improves cache locality and reduces per-pixel overhead. @@ -304,6 +330,18 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth, ctx.dstHeight, ctx.scale, png->getBpp()); + const int pixelType = png->getPixelType(); + const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType); + if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) { + LOG_ERR("PNG", + "PNG row buffer too small: need %d bytes for width=%d type=%d, configured PNG_MAX_BUFFERED_PIXELS=%d", + requiredInternal, ctx.srcWidth, pixelType, PNG_MAX_BUFFERED_PIXELS); + LOG_ERR("PNG", "Aborting decode to avoid PNGdec internal buffer overflow"); + png->close(); + delete png; + return false; + } + if (png->getBpp() != 8) { warnUnsupportedFeature("bit depth (" + std::to_string(png->getBpp()) + "bpp)", imagePath); } diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index ffc3b482..52a5a7a7 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -824,7 +824,8 @@ int GfxRenderer::getSpaceWidth(const int fontId, const EpdFontFamily::Style styl return 0; } - return fontIt->second.getGlyph(' ', style)->advanceX; + const EpdGlyph* spaceGlyph = fontIt->second.getGlyph(' ', style); + return spaceGlyph ? spaceGlyph->advanceX : 0; } int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, const EpdFontFamily::Style style) const { @@ -838,7 +839,9 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, const EpdFo int width = 0; const auto& font = fontIt->second; while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { - width += font.getGlyph(cp, style)->advanceX; + const EpdGlyph* glyph = font.getGlyph(cp, style); + if (!glyph) glyph = font.getGlyph(REPLACEMENT_GLYPH, style); + if (glyph) width += glyph->advanceX; } return width; } diff --git a/platformio.ini b/platformio.ini index 042f434b..98cbab0f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ default_envs = default [crosspoint] -version = 1.0.0 +version = 1.1.1-rc [base] platform = espressif32 @ 6.12.0 @@ -31,9 +31,9 @@ build_flags = -std=gnu++2a # Enable UTF-8 long file names in SdFat -DUSE_UTF8_LONG_NAMES=1 -# Increase PNG scanline buffer to support up to 800px wide images +# Increase PNG scanline buffer to support up to 2048px wide images # Default is (320*4+1)*2=2562, we need more for larger images - -DPNG_MAX_BUFFERED_PIXELS=6402 + -DPNG_MAX_BUFFERED_PIXELS=16416 build_unflags = -std=gnu++11