From 5816ab2a47fc686d14945b9f2aa3f12beca8bd72 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Sat, 14 Feb 2026 16:49:39 +0100 Subject: [PATCH] feat: use pre-compressed HTML pages (#861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Pre-compress the HTML file to save flash space. I'm using `gzip` because it's supported everywhere (indeed, we are using the same optimization on [llama.cpp server](https://github.com/ggml-org/llama.cpp), our HTML page is huge 😅 ). This free up ~40KB flash space. Some users suggested using `brotli` which is known to further reduce 20% in size, but it doesn't supported by firefox (only supports if served via HTTPS), and some reverse proxy like nginx doesn't support it out of the box (unrelated in this context, but just mention for completeness) ``` PR: RAM: [=== ] 31.0% (used 101700 bytes from 327680 bytes) Flash: [==========] 95.5% (used 6259244 bytes from 6553600 bytes) master: RAM: [=== ] 31.0% (used 101700 bytes from 327680 bytes) Flash: [==========] 96.2% (used 6302416 bytes from 6553600 bytes) ``` --- ### 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**, only the python part --- scripts/build_html.py | 25 ++++++++++++++++++++++++- src/network/CrossPointWebServer.cpp | 13 ++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/scripts/build_html.py b/scripts/build_html.py index 248aba84..b144d5dc 100644 --- a/scripts/build_html.py +++ b/scripts/build_html.py @@ -1,5 +1,6 @@ import os import re +import gzip SRC_DIR = "src" @@ -40,12 +41,34 @@ for root, _, files in os.walk(SRC_DIR): # minified = regex.sub("\g<1>", html_content) minified = minify_html(html_content) + + # Compress with gzip (compresslevel 9 is maximum compression) + # IMPORTANT: we don't use brotli because Firefox doesn't support brotli with insecured context (only supported on HTTPS) + compressed = gzip.compress(minified.encode('utf-8'), compresslevel=9) + base_name = f"{os.path.splitext(file)[0]}Html" header_path = os.path.join(root, f"{base_name}.generated.h") with open(header_path, "w", encoding="utf-8") as h: h.write(f"// THIS FILE IS AUTOGENERATED, DO NOT EDIT MANUALLY\n\n") h.write(f"#pragma once\n") - h.write(f'constexpr char {base_name}[] PROGMEM = R"rawliteral({minified})rawliteral";\n') + h.write(f"#include \n\n") + + # Write the compressed data as a byte array + h.write(f"constexpr char {base_name}[] PROGMEM = {{\n") + + # Write bytes in rows of 16 + for i in range(0, len(compressed), 16): + chunk = compressed[i:i+16] + hex_values = ', '.join(f'0x{b:02x}' for b in chunk) + h.write(f" {hex_values},\n") + + h.write(f"}};\n\n") + h.write(f"constexpr size_t {base_name}CompressedSize = {len(compressed)};\n") + h.write(f"constexpr size_t {base_name}OriginalSize = {len(minified)};\n") print(f"Generated: {header_path}") + print(f" Original: {len(html_content)} bytes") + print(f" Minified: {len(minified)} bytes ({100*len(minified)/len(html_content):.1f}%)") + print(f" Compressed: {len(compressed)} bytes ({100*len(compressed)/len(html_content):.1f}%)") + diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 3fd3ff87..91f1fd51 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -293,8 +293,13 @@ CrossPointWebServer::WsUploadStatus CrossPointWebServer::getWsUploadStatus() con return status; } +static void sendHtmlContent(WebServer* server, const char* data, size_t len) { + server->sendHeader("Content-Encoding", "gzip"); + server->send_P(200, "text/html", data, len); +} + void CrossPointWebServer::handleRoot() const { - server->send(200, "text/html", HomePageHtml); + sendHtmlContent(server.get(), HomePageHtml, sizeof(HomePageHtml)); LOG_DBG("WEB", "Served root page"); } @@ -385,7 +390,9 @@ bool CrossPointWebServer::isEpubFile(const String& filename) const { return lower.endsWith(".epub"); } -void CrossPointWebServer::handleFileList() const { server->send(200, "text/html", FilesPageHtml); } +void CrossPointWebServer::handleFileList() const { + sendHtmlContent(server.get(), FilesPageHtml, sizeof(FilesPageHtml)); +} void CrossPointWebServer::handleFileListData() const { // Get current path from query string (default to root) @@ -989,7 +996,7 @@ void CrossPointWebServer::handleDelete() const { } void CrossPointWebServer::handleSettingsPage() const { - server->send(200, "text/html", SettingsPageHtml); + sendHtmlContent(server.get(), SettingsPageHtml, sizeof(SettingsPageHtml)); LOG_DBG("WEB", "Served settings page"); }