Skip to content

Extend scrolling text FX with custom fonts and international UTF-8 character support#5372

Open
DedeHai wants to merge 24 commits intowled:mainfrom
DedeHai:cusomFonts
Open

Extend scrolling text FX with custom fonts and international UTF-8 character support#5372
DedeHai wants to merge 24 commits intowled:mainfrom
DedeHai:cusomFonts

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Feb 15, 2026

  • change font format to a tightly bit packed format with a header
  • added UTF-8 to unicode conversion functions (2, 3 and 4 byte codes supported)
  • added support to read font files from file system (font.wbs, font1.wbs ... font4.wbs)
  • added checkmark to use custom fonts - fallback to internal fonts if no fonts are found (backwads compatible)
  • replaced 3 of the built-in fonts with nicer looking ones (almost the same for best backwards compatibility)

in the current state, this only uses 1k of extra flash (thanks to optimized font bit packing)

This is accompanied with a web based tool to generate wbs fonts from true type fonts as well as from bitmap based bdf fonts. It's a very versatile and easy to use tool: load a font, move sliders until it looks like you want it to, choose extended char range or set it to any unicode offset you like. Choose the range of glyphs to be exportet, remove unwanted glyphs and even edit glyph pixels before export. I also made a "showcase" tool that loads a wbf font file and exports the glyphs as a PNG so users can share their fonts with a nice preview on discourse/reddit etc.

Currently the tool is not yet downloadable in the PixelForge but available here: https://github.com/DedeHai/WLED-Tools/tree/main/WLED-FontFactory

Fixes #5101

Summary by CodeRabbit

Release Notes

  • New Features
    • Added custom font selection for the 2D scrolling text effect.
    • Implemented international character (UTF-8) support for improved text rendering compatibility.
    • Introduced new built-in fonts (Tom Thumb, TinyUnicode, and additional sizes) for enhanced visual quality.
    • Enabled dynamic font loading from both embedded and external storage sources.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 15, 2026

Walkthrough

This PR introduces a FontManager-based font rendering system for 2D scrolling text, replacing hard-coded per-letter calculations with dynamic font metrics. It adds UTF-8 support, font loading from storage, a new WLED Bitmap Font (WBF) format for font data, and token support for dates/times.

Changes

Cohort / File(s) Summary
Font Management System
wled00/FX.h, wled00/FX_2Dfcn.cpp
Introduces FontManager class with methods for font loading, glyph caching, font metrics retrieval (height, width, spacing), and character rendering. Implements filesystem-backed font handling with fallback logic, glyph bitmap access, and cache management.
2D Scrolling Text Mode
wled00/FX.cpp
Refactors mode_2Dscrollingtext to use FontManager for text rendering instead of hard-coded per-letter width/height calculations. Adds support for text tokens (#DATE, #TIME, #SS), dynamic glyph positioning, text centering when within bounds, and rotation handling via font-driven measurement.
UTF-8 Utilities
wled00/util.cpp, wled00/fcn_declare.h
Adds UTF-8 decoding infrastructure: UTF8_LEN macro, utf8_decode() function for codepoint extraction, and utf8_strlen() for character counting in UTF-8 strings.
Font Data (New WBF Format)
wled00/src/font/font_TinyUnicode_8px.h, wled00/src/font/font_tom_thumb_6px.h, wled00/src/font/font_5x12.h, wled00/src/font/console_font_4x6.h
Introduces new WLED Bitmap Font (WBF) packed fixed-width format with 12-byte header (magic, height, width, spacing, flags, char range, Unicode offset) followed by width table and per-glyph bitmap data. Replaces previous font format.
Font Data (Legacy Archive)
wled00/src/font/old fonts/*
Archives of previous CP437-based fonts (console_font_4x6.h, console_font_5x8.h, console_font_5x12.h, console_font_6x8.h, console_font_7x9.h) converted to WBF format for backward compatibility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #4742: Both PRs modify mode_2Dscrollingtext's text handling and token assembly; this PR adds font-driven rendering while refactoring token caching and support infrastructure.

Suggested reviewers

  • willmmiles
  • softhack007
  • blazoncek
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary changes: extending the scrolling text effect with custom font support and UTF-8 character handling.
Linked Issues check ✅ Passed The PR implements Cyrillic/special character support through UTF-8 functions, font format redesign, and custom font loading from filesystem, directly addressing issue #5101.
Out of Scope Changes check ✅ Passed All changes directly support custom font implementation and UTF-8 support. Font data files, FontManager implementation, UTF-8 utilities, and FX updates are all in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@wled00/FX_2Dfcn.cpp`:
- Around line 969-970: Check that fileHdr.last >= fileHdr.first before computing
numGlyphs to avoid underflow; if not, handle the error (return/fail) instead of
proceeding. Change the numGlyphs calculation to use a wider type (e.g., size_t
or uint16_t) and allocate the width table safely (use std::vector<uint8_t> or
heap allocation) rather than a VLA: compute numGlyphs = (size_t)fileHdr.last -
(size_t)fileHdr.first + 1 after the validation, resize the vector to numGlyphs,
then call file.read(widthTable.data(), numGlyphs). Ensure early exit or error
logging if validation fails to prevent reading an incorrect/huge amount of data.
- Around line 1010-1015: The loop copies widths from widthTable into registry
but widthTable may be partially uninitialized if file.read(widthTable,
numGlyphs) returned fewer bytes; check the return value of file.read and ensure
it equals numGlyphs before using widthTable, or otherwise handle the short read
by adjusting numGlyphs/neededCount, zero-filling the remaining widthTable
entries, or aborting loading; also validate that each `code` from neededCodes is
within the bounds of widthTable before doing `registry[k].width =
widthTable[code]` (and continue to set `registry[k].height = fileHdr.height`),
so update the loading logic that populates widthTable and the loop that uses
`neededCount`, `neededCodes`, `registry`, and `widthTable` to guard against
truncated files.
- Around line 783-795: The cache-clear branch in FX_2Dfcn.cpp (when fontToUse !=
meta->cachedFontNum) reallocates SegmentFontMetadata via _segment->allocateData
but does not reset meta->glyphCount, leaving stale non-zero values that prevent
prepare() from detecting an empty cache; after reassigning meta = getMetadata()
and before setting meta->availableFonts/meta->cachedFontNum/meta->fontsScanned,
explicitly set meta->glyphCount = 0 so prepare()'s if (meta->glyphCount == 0)
correctly triggers a rebuild of the glyph cache.

In `@wled00/FX.cpp`:
- Around line 6455-6485: The centering math is wrong: SEGENV.aux0 was computed
as the left-origin centered offset but drawX is using cols - SEGENV.aux0 which
places text at the far right; change the drawX calculation in FX.cpp so it uses
SEGENV.aux0 as the left-origin base (e.g., baseX = SEGENV.aux0) and compute
drawX = baseX + currentXOffset (still using advance = (rotate == 1 || rotate ==
-1) ? letterHeight : glyphWidth), ensuring this branch (when totalTextWidth <=
cols / non-scrolling case) uses the left-origin base rather than cols - aux0 so
glyphs are centered correctly.

In `@wled00/FX.h`:
- Around line 1145-1149: Update the memory layout comment to match the constant
FONT_HEADER_SIZE by changing "[11-byte font header]" to "[12-byte font header]";
locate the comment near the cached fonts section in FX.h and ensure it exactly
reflects the value of FONT_HEADER_SIZE (defined earlier) so the documentation
and the constant remain consistent.
- Around line 1095-1100: The readUInt32LE function can invoke signed-integer
overflow because readByte() (uint8_t) promotes to signed int before left shifts;
cast each readByte(...) to uint32_t (e.g., (uint32_t)readByte(offset + N))
before shifting and ORing so all shifts operate on unsigned 32-bit values,
ensuring defined behavior in readUInt32LE.
- Around line 814-815: Update the two non-2D drawCharacter stub signatures to
use the same Unicode type as the 2D variant and FontManager: change the first
parameter from "unsigned char chr" to "uint32_t unicode" in both inline stubs
(the two empty drawCharacter overloads currently declared), so their signatures
match the 2D drawCharacter (uint32_t unicode) and FontManager::drawCharacter in
FX_2Dfcn.cpp; keep the rest of the parameter list the same and do not modify the
empty bodies.

In `@wled00/util.cpp`:
- Around line 165-196: Fix the typos in the UTF-8 comment block above
utf8_decode: change "onversion" to "conversion" and remove the stray trailing
'c' so "U+10FFFFc" becomes "U+10FFFF"; these edits live in the comment
immediately preceding the UTF8_LEN macro/utf8_decode function and do not require
code changes.
- Around line 184-188: Replace the expensive strlen(s) call and the lax
continuation handling by iterating up to n bytes from s and validating each
continuation byte prefix instead of scanning the whole string; specifically, in
the branch that currently does "if (strlen(s) < n) { *len = 1; return '?'; }"
check for early NUL by verifying s[i] != '\0' for each i in [0,n-1] and also
validate each continuation byte with (s[i] & 0xC0) == 0x80, and if any check
fails set *len = 1 and return '?' (preserving behavior of n, *len and the
returned default char).
🧹 Nitpick comments (4)
wled00/util.cpp (1)

174-196: Indentation uses 4 spaces; the rest of util.cpp uses 2 spaces.

The function body uses 4-space base indentation, which is inconsistent with the file's established 2-space convention. As per coding guidelines, wled00/**/*.cpp should use 2-space indentation.

wled00/FX.cpp (1)

6394-6397: Avoid reloading/preparing fonts every frame.

loadFont() + prepare() are called on every frame (Line 6395-6396). If these hit filesystem or re-parse glyphs, this will tank FPS and I/O. Consider gating on SEGENV.call == 0 or when fontNum/useCustomFont/text changes (the unused init flag could drive this).

wled00/FX_2Dfcn.cpp (2)

1024-1040: Repeated O(code) offset recomputation for each glyph.

For every needed glyph, lines 1031–1034 re-scan widthTable from index 0 to code-1 to compute the file offset. With up to 64 needed glyphs and codes up to ~255, this is ~16K iterations — tolerable for a one-time cache build but easy to optimize with a single prefix-sum pass.

Sketch: pre-compute cumulative offsets
+  // Pre-compute cumulative byte offsets for each glyph
+  uint32_t cumOffset[numGlyphs + 1];
+  cumOffset[0] = 0;
+  for (uint8_t j = 0; j < numGlyphs; j++) {
+    uint16_t b = widthTable[j] * fileHdr.height;
+    cumOffset[j + 1] = cumOffset[j] + (b + 7) / 8;
+  }
+
   // Write bitmap data in registry order
   for (uint8_t k = 0; k < neededCount; k++) {
     uint8_t code = neededCodes[k];
     uint16_t bits = widthTable[code] * fileHdr.height;
     uint16_t bytes = (bits + 7) / 8;
     
     // Calculate file offset
-    uint32_t offset = fileDataStart;
-    for (uint8_t j = 0; j < code; j++) {
-      uint16_t b = widthTable[j] * fileHdr.height;
-      offset += (b + 7) / 8;
-    }
+    uint32_t offset = fileDataStart + cumOffset[code];
     
     // Read from file
     file.seek(offset);

647-701: Pointer returned from getGlyphBitmap is only used as an offset base — document this contract.

The returned const uint8_t* is never dereferenced directly; drawCharacter computes (bitmap - _fontBase) + bytePos and reads through _reader. This works correctly but is a non-obvious contract — a future caller might dereference the pointer directly, which would fail for PROGMEM data. A brief comment would prevent misuse.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 15, 2026

The Font Factory tool:
image


Font pixel editor mode:

image

Built-in international chars support:

image

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 16, 2026

@coderabbitai please review the fontmanager and make suggesions for code consolidation and maintainability.

@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

@DedeHai DedeHai marked this pull request as ready for review February 26, 2026 13:07
@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 26, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
wled00/FX.cpp (1)

6445-6446: ⚠️ Potential issue | 🟠 Major

Use left-origin base X for centered non-scrolling text.

Line 6445 stores a centered left-origin offset in SEGENV.aux0, but Line 6474 treats it as scrolling offset (cols - aux0). This shifts short text to the right and clips it.

🔧 Proposed fix
   // Draw characters
   idx = 0;
   int currentXOffset = 0; // offset of current glyph from text start
+  const int baseX = (totalTextWidth > cols)
+    ? int(cols) - int(SEGENV.aux0)   // scrolling mode
+    : int(SEGENV.aux0);              // centered non-scrolling mode
 
   for (int c = 0; c < numberOfChars; c++) {
     uint8_t charLen;
     uint32_t unicode = utf8_decode(&text[idx], &charLen);
     idx += charLen;
     uint8_t glyphWidth = fontManager.getGlyphWidth(unicode);
-    int drawX = int(cols) - int(SEGENV.aux0) + currentXOffset; // AUX0 is scrolling offset
+    int drawX = baseX + currentXOffset;
     int advance = isRotated ? glyphHeight + 1 : glyphWidth + letterSpacing; // when rotated use spacing of 1

Also applies to: 6474-6475

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6445 - 6446, SEGENV.aux0 is being set as a
centered left-origin X offset but later treated as a scrolling offset (using
cols - aux0) which pushes/ clips short non-scrolling text; change the code paths
that use SEGENV.aux0 for non-scrolling/centered text (the logic around where
cols - SEGENV.aux0 is used) to use SEGENV.aux0 directly as the left-origin base
X for drawing centered text, and stop inverting it (remove the cols - ... usage)
so short centered text is placed and not clipped; look for uses of SEGENV.aux0
around the centering block and at the usage site currently doing cols -
SEGENV.aux0 and replace that behavior to consume aux0 as the left-origin X.
🧹 Nitpick comments (4)
wled00/src/font/font_6x12.h (2)

40-49: Remove duplicate comment block.

Lines 40-44 and 45-49 contain identical content—this appears to be a copy-paste error.

🧹 Proposed fix to remove duplicate
 // Font: 6x12
 // Height: 12, Max Width: 5, Spacing: 1
 // Characters: 32-127 (96 glyphs)
 // Unicode Offset: 0x00000000
 // Variable Width: Yes
-// Font: 6x12
-// Height: 12, Max Width: 5, Spacing: 1
-// Characters: 32-127 (96 glyphs)
-// Unicode Offset: 0x00000000
-// Variable Width: Yes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/font_6x12.h` around lines 40 - 49, Remove the duplicated
comment block describing the font (the repeated lines starting with "Font:
6x12", "Height: 12, Max Width: 5, Spacing: 1", "Characters: 32-127 (96 glyphs)",
"Unicode Offset: 0x00000000", "Variable Width: Yes") so only a single header
comment remains; locate the duplicate by searching for the "Font: 6x12" comment
block in font_6x12.h and delete the second occurrence.

14-14: Minor documentation inconsistency: Last Char value.

The header layout comment on line 14 says [6] Last Char: 126, but the actual header byte at line 52 is 0x7F (127). The width table correctly includes 96 entries for characters 32-127, with character 127 having width 0. Consider updating line 14 to match the actual value.

📝 Proposed fix
- * [6]   Last Char: 126
+ * [6]   Last Char: 127
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/font_6x12.h` at line 14, The header comment's "Last Char"
value is inconsistent with the actual font data: update the header layout
comment (the "[6]   Last Char" entry in font_6x12.h) to 127 to match the header
byte 0x7F in the file and the width table covering characters 32–127 (96
entries, with char 127 width 0); ensure the comment and the stored last-char
byte (0x7F) are consistent.
wled00/src/font/old fonts/console_font_6x8.h (1)

41-170: Consider using 2-space indentation for consistency with project guidelines.

The array contents use 4-space indentation, but the coding guidelines specify 2-space indentation for C++ header files matching wled00/**/!(html_*)*.h. While this is data rather than logic, aligning with the project standard improves consistency.

As per coding guidelines: "Use 2-space indentation for non-generated C++ header files (.h)".

Example adjustment (first few lines)
 static const unsigned char console_font_6x8[] PROGMEM = {
-    0x57, 0x08, 0x06, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
-
-    // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     // /* code=0, hex=0x00, ascii="^@" */
+  0x57, 0x08, 0x06, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
+
+  // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     // /* code=0, hex=0x00, ascii="^@" */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/old` fonts/console_font_6x8.h around lines 41 - 170, The
console_font_6x8 array (static const unsigned char console_font_6x8[] PROGMEM)
uses 4-space indentation; update the file to use 2-space indentation throughout
(including the array header line, each byte row, and commented code/metadata
lines) to match project C++ header style; ensure alignment is preserved so each
comment like /* code=32, ... */ and each hex row keeps the same relative spacing
but with two spaces per indent level.
wled00/FX.h (1)

1144-1146: Consider defensive null checks for font dimension getters.

The inline getters getFontHeight(), getFontWidth(), and getFontSpacing() directly dereference _fontBase without null checks. If called before loadFont() succeeds or after a failed load, this would cause undefined behavior.

While the current usage pattern in the scrolling text effect appears to guard these calls properly, adding defensive checks would improve robustness.

Proposed fix
   // Get dimensions (use cached header)
-  inline uint8_t getFontHeight() { return reinterpret_cast<FontHeader*>(_fontBase)->height; }
-  inline uint8_t getFontWidth()  { return reinterpret_cast<FontHeader*>(_fontBase)->width; }
-  inline uint8_t getFontSpacing() { return reinterpret_cast<FontHeader*>(_fontBase)->spacing; }
+  inline uint8_t getFontHeight() { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->height : 0; }
+  inline uint8_t getFontWidth()  { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->width : 0; }
+  inline uint8_t getFontSpacing() { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->spacing : 0; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.h` around lines 1144 - 1146, The getters getFontHeight(),
getFontWidth(), and getFontSpacing() dereference _fontBase via
reinterpret_cast<FontHeader*> without null checks; update each to defensively
check whether _fontBase is non-null (and optionally valid) and return a safe
default (e.g., 0) when it is null instead of dereferencing, so calls to
getFontHeight/getFontWidth/getFontSpacing do not cause undefined behavior if
loadFont() failed or wasn't called; locate these methods in FX.h and guard
access to reinterpret_cast<FontHeader*>(_fontBase)->height/width/spacing
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/FX_2Dfcn.cpp`:
- Around line 812-820: The bitmap read in the font-loading loop doesn't verify
file.read returned the expected byte count, so update the loop where
file.read(dataptr, bytes) is called to capture its return value (e.g., size_t n
= file.read(dataptr, bytes)) and check that n == bytes; on mismatch, handle the
truncated file by zeroing the target buffer (e.g., memset(dataptr, 0, bytes))
and either break/return an error code or mark the font load as failed as
appropriate for the surrounding flow; ensure the same dataptr/offset bookkeeping
is preserved and keep the existing flash path using memcpy_P(flashFont + offset,
...) unchanged.

In `@wled00/FX.cpp`:
- Around line 6407-6419: totalTextWidth is being computed inconsistently with
the actual glyph advance causing double-spacing and mismatches for rotated text;
update the loop that decodes glyphs (utf8_decode) to accumulate per-glyph
advance instead of mixing getGlyphWidth + letterSpacing: for each unicode glyph
compute advance = isRotated ? (glyphHeight + 1) :
fontManager.getGlyphAdvance(unicode) (or if no getGlyphAdvance exists, use
fontManager.getGlyphWidth(unicode) and add letterSpacing only once per-glyph),
add advance to totalTextWidth without adding an extra letterSpacing after the
last character (remove the redundant totalTextWidth -= letterSpacing when you
handle spacing per-glyph), and make the later draw-advance calculation (the code
around the draw advance at lines ~6475) use the same per-glyph advance formula
so both scrolling decision and rendering use identical metrics.

---

Duplicate comments:
In `@wled00/FX.cpp`:
- Around line 6445-6446: SEGENV.aux0 is being set as a centered left-origin X
offset but later treated as a scrolling offset (using cols - aux0) which pushes/
clips short non-scrolling text; change the code paths that use SEGENV.aux0 for
non-scrolling/centered text (the logic around where cols - SEGENV.aux0 is used)
to use SEGENV.aux0 directly as the left-origin base X for drawing centered text,
and stop inverting it (remove the cols - ... usage) so short centered text is
placed and not clipped; look for uses of SEGENV.aux0 around the centering block
and at the usage site currently doing cols - SEGENV.aux0 and replace that
behavior to consume aux0 as the left-origin X.

---

Nitpick comments:
In `@wled00/FX.h`:
- Around line 1144-1146: The getters getFontHeight(), getFontWidth(), and
getFontSpacing() dereference _fontBase via reinterpret_cast<FontHeader*> without
null checks; update each to defensively check whether _fontBase is non-null (and
optionally valid) and return a safe default (e.g., 0) when it is null instead of
dereferencing, so calls to getFontHeight/getFontWidth/getFontSpacing do not
cause undefined behavior if loadFont() failed or wasn't called; locate these
methods in FX.h and guard access to
reinterpret_cast<FontHeader*>(_fontBase)->height/width/spacing accordingly.

In `@wled00/src/font/font_6x12.h`:
- Around line 40-49: Remove the duplicated comment block describing the font
(the repeated lines starting with "Font: 6x12", "Height: 12, Max Width: 5,
Spacing: 1", "Characters: 32-127 (96 glyphs)", "Unicode Offset: 0x00000000",
"Variable Width: Yes") so only a single header comment remains; locate the
duplicate by searching for the "Font: 6x12" comment block in font_6x12.h and
delete the second occurrence.
- Line 14: The header comment's "Last Char" value is inconsistent with the
actual font data: update the header layout comment (the "[6]   Last Char" entry
in font_6x12.h) to 127 to match the header byte 0x7F in the file and the width
table covering characters 32–127 (96 entries, with char 127 width 0); ensure the
comment and the stored last-char byte (0x7F) are consistent.

In `@wled00/src/font/old` fonts/console_font_6x8.h:
- Around line 41-170: The console_font_6x8 array (static const unsigned char
console_font_6x8[] PROGMEM) uses 4-space indentation; update the file to use
2-space indentation throughout (including the array header line, each byte row,
and commented code/metadata lines) to match project C++ header style; ensure
alignment is preserved so each comment like /* code=32, ... */ and each hex row
keeps the same relative spacing but with two spaces per indent level.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 619d700 and 6092ff7.

⛔ Files ignored due to path filters (5)
  • wled00/src/font/old fonts/console_font_4x6_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_5x12_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_5x8_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_6x8_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_7x9_showcase.png is excluded by !**/*.png
📒 Files selected for processing (25)
  • wled00/FX.cpp
  • wled00/FX.h
  • wled00/FX_2Dfcn.cpp
  • wled00/src/font/6x12.wbf
  • wled00/src/font/console_font_6x8.h
  • wled00/src/font/console_font_6x8.wbf
  • wled00/src/font/console_font_7x9.h
  • wled00/src/font/console_font_7x9.wbf
  • wled00/src/font/font_6x12.h
  • wled00/src/font/font_TinyUnicode_8px.h
  • wled00/src/font/font_TinyUnicode_8px.wbf
  • wled00/src/font/font_tom-thumb_6px.wbf
  • wled00/src/font/font_tom_thumb_6px.h
  • wled00/src/font/old fonts/console_font_4x6.h
  • wled00/src/font/old fonts/console_font_4x6.wbf
  • wled00/src/font/old fonts/console_font_5x12.h
  • wled00/src/font/old fonts/console_font_5x12.wbf
  • wled00/src/font/old fonts/console_font_5x8.h
  • wled00/src/font/old fonts/console_font_5x8.wbf
  • wled00/src/font/old fonts/console_font_6x8.h
  • wled00/src/font/old fonts/console_font_6x8.wbf
  • wled00/src/font/old fonts/console_font_7x9.h
  • wled00/src/font/old fonts/console_font_7x9.wbf
  • wled00/src/font/old fonts/tom-thumb_6px.wbf
  • wled00/util.cpp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
wled00/FX.cpp (2)

6407-6419: ⚠️ Potential issue | 🟠 Major

Unify width calculation with glyph advance logic.

totalTextWidth and per-glyph advance are still computed with different spacing rules, so fit/scroll selection can be wrong and clipping still occurs.

🔧 Proposed fix
-  for (int c = 0; c < numberOfChars; c++) {
+  const int interGlyphSpacing = isRotated ? 1 : letterSpacing;
+  for (int c = 0; c < numberOfChars; c++) {
     uint8_t charLen;
     uint32_t unicode = utf8_decode(&text[idx], &charLen);
     idx += charLen;
-
-    if (isRotated) {
-      totalTextWidth += glyphHeight + 1; // use height when rotated, spacing of 1
-    } else {
-      totalTextWidth += fontManager.getGlyphWidth(unicode) + letterSpacing;
-    }
-    if (c < numberOfChars - 1) totalTextWidth += letterSpacing;
+    const int glyphAdvance = isRotated
+      ? (glyphHeight + interGlyphSpacing)
+      : (fontManager.getGlyphWidth(unicode) + interGlyphSpacing);
+    totalTextWidth += glyphAdvance;
   }
-  totalTextWidth -= letterSpacing; // remove spacing after last character
+  if (numberOfChars > 0) totalTextWidth -= interGlyphSpacing; // remove trailing spacing
...
-    int advance = isRotated ? glyphHeight + 1 : glyphWidth + letterSpacing; // when rotated use spacing of 1
+    int advance = isRotated ? glyphHeight + interGlyphSpacing : glyphWidth + interGlyphSpacing;

Also applies to: 6475-6476

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6407 - 6419, The width accumulation in the loop
computing totalTextWidth uses different spacing rules than the glyph advance
calculation, causing mismatched fit/scroll decisions and clipping; modify the
totalTextWidth computation to mirror the per-glyph advance logic used elsewhere:
for each decoded glyph (unicode, charLen) add the exact advance used for
rendering (use glyphHeight when isRotated, otherwise use
fontManager.getGlyphWidth(unicode)), include letterSpacing only when the glyph
advance logic does, and remove the extra subtraction of letterSpacing at the
end—also apply the same unified rule where totalTextWidth is computed in the
other occurrence referenced (the block around the second occurrence of the same
computation).

6445-6446: ⚠️ Potential issue | 🟠 Major

Fix centered-text X origin (left-origin vs right-origin mixup).

In non-scrolling mode, SEGENV.aux0 is set as a centered left-origin offset, but drawX still uses cols - SEGENV.aux0, pushing text rightward.

🔧 Proposed fix
-  idx = 0;
-  int currentXOffset = 0; // offset of current glyph from text start
+  idx = 0;
+  int currentXOffset = 0; // offset of current glyph from text start
+  const int baseX = (totalTextWidth > cols)
+    ? int(cols) - int(SEGENV.aux0)
+    : int(SEGENV.aux0);

   for (int c = 0; c < numberOfChars; c++) {
...
-    int drawX = int(cols) - int(SEGENV.aux0) + currentXOffset; // AUX0 is scrolling offset
+    int drawX = baseX + currentXOffset;

Also applies to: 6474-6475

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6445 - 6446, SEGENV.aux0 is computed as a
left-origin centered offset (SEGENV.aux0 = (cols - totalTextWidth) / 2) but
drawX uses cols - SEGENV.aux0 which treats aux0 as a right-origin offset,
causing rightward shift; fix by making drawX use SEGENV.aux0 directly (i.e.,
drawX = SEGENV.aux0 + currentCharX or equivalent) in the non-scrolling path
where SEGENV.aux0 is set, and apply the same change to the duplicate instance
around the block referenced by lines ~6474-6475 so both places consistently
interpret SEGENV.aux0 as a left-origin X offset.
wled00/FX_2Dfcn.cpp (1)

812-820: ⚠️ Potential issue | 🟡 Minor

Missing return value check for file.read in bitmap loading loop.

The width table read on line 751 correctly validates the return value, but the bitmap read on line 815 does not check if the expected number of bytes were actually read. If the font file is truncated, this could result in partially uninitialized bitmap data being cached.

Proposed fix
     // read from file or flash
     if (file) {
       file.seek(offset);
-      file.read(dataptr, bytes);
+      if (file.read(dataptr, bytes) != bytes) {
+        file.close();
+        meta->glyphCount = 0; // invalidate cache
+        return;
+      }
     } else {
       memcpy_P(dataptr, flashFont + offset, bytes);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` around lines 812 - 820, The bitmap-read branch in the
font loading loop (in FX_2Dfcn.cpp) calls file.read(dataptr, bytes) without
checking its return value, so if file.read returns fewer than bytes the cached
bitmap (dataptr) may be partially uninitialized; update the bitmap read logic to
check the return value from file.read and handle short/failed reads (e.g., abort
the load and return an error, or zero-fill the remaining bytes and mark the font
as invalid) — modify the block that uses file.read/dataptr/bytes/offset to
validate the number of bytes read and take a deterministic failure path (same
style as the width-table check) instead of assuming a full read.
🧹 Nitpick comments (6)
wled00/src/font/font_6x12.h (1)

5-52: Minor comment inconsistencies.

  1. The header description at line 14 says Last Char: 126 but the actual header byte at line 52 is 0x7F (127), and lines 42-43 say "Characters: 32-127 (96 glyphs)". The data is correct; the comment at line 14 should say Last Char: 127.

  2. Lines 40-49 contain duplicate metadata comments ("Font: 6x12... Variable Width: Yes" appears twice).

These are cosmetic issues—the font data itself is correctly structured.

🔧 Suggested comment cleanup
 * [5]   First Char: 32
-* [6]   Last Char: 126
+* [6]   Last Char: 127
 * [7]   reserved: 0x00

And remove the duplicate block at lines 45-49.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/font_6x12.h` around lines 5 - 52, Update the header comments
for font_6x12: change the "Last Char: 126" text to "Last Char: 127" to match the
header byte 0x7F in the font_6x12 array and the "Characters: 32-127" lines, and
remove the duplicated metadata block that repeats "Font: 6x12 ... Variable
Width: Yes" so there is only a single metadata comment block describing the
font.
wled00/src/font/old fonts/console_font_6x8.h (2)

42-170: Use 2-space indentation in this non-generated header.

Current array row indentation is 4 spaces; project guideline for .h files is 2 spaces.

As per coding guidelines: wled00/**/!(html_*)*.h: Use 2-space indentation for non-generated C++ header files (.h).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/old` fonts/console_font_6x8.h around lines 42 - 170, The
header's font data array uses 4-space indentation for each byte row (e.g., lines
starting with "0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     /* code=32..." and all
subsequent glyph lines), but the project guideline requires 2-space indentation
for non-generated .h files; update the indentation for the array rows and any
commented glyph lines (such as entries for code=32..126 and the header line
"0x57, 0x08, 0x06, 0x01, 0x00, 0x20, 0x7E, ...") to use 2 spaces instead of 4
while preserving all byte values and comments.

41-300: Add a compile-time payload-size assertion for the packed font table.

This asset is tightly packed; a single accidental byte drift can desync glyph indexing. Add a static_assert on total size to fail fast at compile time.

Proposed hardening diff
+static constexpr uint8_t CONSOLE_FONT_6X8_HEADER_SIZE = 12;
+static constexpr uint8_t CONSOLE_FONT_6X8_FIRST_CHAR = 0x20;
+static constexpr uint8_t CONSOLE_FONT_6X8_LAST_CHAR  = 0x7E;
+static constexpr uint8_t CONSOLE_FONT_6X8_BYTES_PER_GLYPH = 6;
+
 static const unsigned char console_font_6x8[] PROGMEM = {
@@
 };
+
+static_assert(
+  sizeof(console_font_6x8) ==
+    CONSOLE_FONT_6X8_HEADER_SIZE +
+    ((CONSOLE_FONT_6X8_LAST_CHAR - CONSOLE_FONT_6X8_FIRST_CHAR + 1) * CONSOLE_FONT_6X8_BYTES_PER_GLYPH),
+  "console_font_6x8 payload size does not match header/glyph layout"
+);

Based on learnings: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/old` fonts/console_font_6x8.h around lines 41 - 300, The
packed font array console_font_6x8 can silently shift if a byte is
added/removed; add a compile-time check: introduce meaningful constants (e.g.,
FIRST_GLYPH = 0x20, LAST_GLYPH = 0x7E, GLYPH_BYTES = 6, HEADER_BYTES = 8) and a
static_assert that sizeof(console_font_6x8) == (HEADER_BYTES + (LAST_GLYPH -
FIRST_GLYPH + 1) * GLYPH_BYTES) with a clear message so compilation fails if the
table size drifts.
wled00/FX_2Dfcn.cpp (2)

596-642: Redundant _fontNum assignment on line 605.

_fontNum is already set to fontNum on line 598, making the assignment on line 605 redundant.

Proposed fix
   SegmentFontMetadata* meta = getMetadata();
   if (!meta)
     return false; // ensure segment data exists
 
-  _fontNum = fontNum; // store font to be used
   if (useFile) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` around lines 596 - 642, The loadFont function
redundantly assigns _fontNum twice; remove the second assignment of _fontNum in
FontManager::loadFont (the line that reassigns _fontNum = fontNum just before
the useFile block) so the initial _fontNum = fontNum at the top remains the
single initialization, keeping the later fallback logic that modifies _fontNum
intact and ensuring no behavioral change to cacheID creation or
scanAvailableFonts handling.

629-634: Consider defining a named constant for the file-font flag 0x80.

The magic number 0x80 is used here (line 629) and again on line 708 to indicate a file-based font vs. flash font. A named constant would improve readability and maintainability.

Proposed fix

Add near the other font constants in FX.h:

static constexpr uint8_t FONT_FILE_FLAG = 0x80; // highest bit indicates file vs flash font

Then update usage:

-  uint8_t cacheID = _fontNum | (_useFileFont ? 0x80 : 0x00);
+  uint8_t cacheID = _fontNum | (_useFileFont ? FONT_FILE_FLAG : 0x00);

Based on learnings: In WLED code reviews, look for "magic numbers" and suggest replacing them with defined constants when meaningful.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` around lines 629 - 634, Replace the magic 0x80 file-font
flag with a named constant: add a constexpr uint8_t FONT_FILE_FLAG = 0x80 near
the other font constants in FX.h, then use FONT_FILE_FLAG when building cacheID
(uint8_t cacheID = _fontNum | (_useFileFont ? FONT_FILE_FLAG : 0x00)) and in the
other location that checks/sets the file-font bit so references to the flag are
clear; update any occurrences that currently use 0x80 to use FONT_FILE_FLAG and
keep meta->cachedFontNum and meta->glyphCount logic unchanged.
wled00/FX.h (1)

1144-1146: Inline accessors dereference _fontBase without null checks.

getFontHeight(), getFontWidth(), and getFontSpacing() will crash if called before a successful loadFont() or if font loading failed. While the function comment notes that loadFont() must succeed first, consider adding defensive null checks for robustness:

Proposed fix
-  inline uint8_t getFontHeight() { return reinterpret_cast<FontHeader*>(_fontBase)->height; }
-  inline uint8_t getFontWidth()  { return reinterpret_cast<FontHeader*>(_fontBase)->width; }
-  inline uint8_t getFontSpacing() { return reinterpret_cast<FontHeader*>(_fontBase)->spacing; }
+  inline uint8_t getFontHeight()  { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->height : 0; }
+  inline uint8_t getFontWidth()   { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->width : 0; }
+  inline uint8_t getFontSpacing() { return _fontBase ? reinterpret_cast<FontHeader*>(_fontBase)->spacing : 0; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.h` around lines 1144 - 1146, The three inline accessors
getFontHeight(), getFontWidth(), and getFontSpacing() currently dereference
_fontBase without checking for null and can crash if loadFont() hasn't
succeeded; update each to first check _fontBase (or
reinterpret_cast<FontHeader*>(_fontBase)) for nullptr and return a safe default
(e.g., 0) when no font is loaded, otherwise return the respective FontHeader
field (height, width, spacing) so callers are protected from null dereference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/FX_2Dfcn.cpp`:
- Around line 854-876: Move null checks for _fontBase and _segment->data before
any dereference: at the top of FontManager::getGlyphBitmap validate _fontBase
(and _segment and _segment->data) and return nullptr if any are null, then
construct FontHeader* hdr = reinterpret_cast<FontHeader*>(_fontBase) and call
getGlyphIndex(unicode, hdr); this prevents dereferencing _fontBase when it may
be invalid. Also ensure that getGlyphIndex is only called after hdr is created
and that subsequent code (accessing _segment->data, SegmentFontMetadata,
GlyphEntry, and _fontBase + FONT_HEADER_SIZE + bitmapOffset) runs after these
checks.

In `@wled00/FX.cpp`:
- Line 6395: Do not early-return when fontManager.loadFont(fontNum, text,
useCustomFont) fails; instead implement a deterministic fallback: on false, set
useCustomFont = false and attempt to load a known-safe default font (e.g., call
fontManager.loadFont(0, text, false) or your project’s default font index), and
if that also fails ensure you clear or reset the pixel/frame state via the
existing frame-clear/reset function so stale pixels aren’t left on screen; keep
the rendering path continuing after the fallback so FX rendering proceeds
predictably.

In `@wled00/src/font/old` fonts/console_font_6x8.h:
- Line 3: The font file console_font_6x8.h currently attributes the source but
lacks license provenance; update the top-of-file comment to include an explicit
license statement or written permission from the original author (e.g., add
"License: [license name] — confirmed by author: [name/email] on [date]" or a
SPDX identifier) or replace the glyph data with a font that has a clear
open-source license; ensure the change is applied in console_font_6x8.h (top
comment) and any README or attribution metadata referencing this font is updated
accordingly.

---

Duplicate comments:
In `@wled00/FX_2Dfcn.cpp`:
- Around line 812-820: The bitmap-read branch in the font loading loop (in
FX_2Dfcn.cpp) calls file.read(dataptr, bytes) without checking its return value,
so if file.read returns fewer than bytes the cached bitmap (dataptr) may be
partially uninitialized; update the bitmap read logic to check the return value
from file.read and handle short/failed reads (e.g., abort the load and return an
error, or zero-fill the remaining bytes and mark the font as invalid) — modify
the block that uses file.read/dataptr/bytes/offset to validate the number of
bytes read and take a deterministic failure path (same style as the width-table
check) instead of assuming a full read.

In `@wled00/FX.cpp`:
- Around line 6407-6419: The width accumulation in the loop computing
totalTextWidth uses different spacing rules than the glyph advance calculation,
causing mismatched fit/scroll decisions and clipping; modify the totalTextWidth
computation to mirror the per-glyph advance logic used elsewhere: for each
decoded glyph (unicode, charLen) add the exact advance used for rendering (use
glyphHeight when isRotated, otherwise use fontManager.getGlyphWidth(unicode)),
include letterSpacing only when the glyph advance logic does, and remove the
extra subtraction of letterSpacing at the end—also apply the same unified rule
where totalTextWidth is computed in the other occurrence referenced (the block
around the second occurrence of the same computation).
- Around line 6445-6446: SEGENV.aux0 is computed as a left-origin centered
offset (SEGENV.aux0 = (cols - totalTextWidth) / 2) but drawX uses cols -
SEGENV.aux0 which treats aux0 as a right-origin offset, causing rightward shift;
fix by making drawX use SEGENV.aux0 directly (i.e., drawX = SEGENV.aux0 +
currentCharX or equivalent) in the non-scrolling path where SEGENV.aux0 is set,
and apply the same change to the duplicate instance around the block referenced
by lines ~6474-6475 so both places consistently interpret SEGENV.aux0 as a
left-origin X offset.

---

Nitpick comments:
In `@wled00/FX_2Dfcn.cpp`:
- Around line 596-642: The loadFont function redundantly assigns _fontNum twice;
remove the second assignment of _fontNum in FontManager::loadFont (the line that
reassigns _fontNum = fontNum just before the useFile block) so the initial
_fontNum = fontNum at the top remains the single initialization, keeping the
later fallback logic that modifies _fontNum intact and ensuring no behavioral
change to cacheID creation or scanAvailableFonts handling.
- Around line 629-634: Replace the magic 0x80 file-font flag with a named
constant: add a constexpr uint8_t FONT_FILE_FLAG = 0x80 near the other font
constants in FX.h, then use FONT_FILE_FLAG when building cacheID (uint8_t
cacheID = _fontNum | (_useFileFont ? FONT_FILE_FLAG : 0x00)) and in the other
location that checks/sets the file-font bit so references to the flag are clear;
update any occurrences that currently use 0x80 to use FONT_FILE_FLAG and keep
meta->cachedFontNum and meta->glyphCount logic unchanged.

In `@wled00/FX.h`:
- Around line 1144-1146: The three inline accessors getFontHeight(),
getFontWidth(), and getFontSpacing() currently dereference _fontBase without
checking for null and can crash if loadFont() hasn't succeeded; update each to
first check _fontBase (or reinterpret_cast<FontHeader*>(_fontBase)) for nullptr
and return a safe default (e.g., 0) when no font is loaded, otherwise return the
respective FontHeader field (height, width, spacing) so callers are protected
from null dereference.

In `@wled00/src/font/font_6x12.h`:
- Around line 5-52: Update the header comments for font_6x12: change the "Last
Char: 126" text to "Last Char: 127" to match the header byte 0x7F in the
font_6x12 array and the "Characters: 32-127" lines, and remove the duplicated
metadata block that repeats "Font: 6x12 ... Variable Width: Yes" so there is
only a single metadata comment block describing the font.

In `@wled00/src/font/old` fonts/console_font_6x8.h:
- Around line 42-170: The header's font data array uses 4-space indentation for
each byte row (e.g., lines starting with "0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   
/* code=32..." and all subsequent glyph lines), but the project guideline
requires 2-space indentation for non-generated .h files; update the indentation
for the array rows and any commented glyph lines (such as entries for
code=32..126 and the header line "0x57, 0x08, 0x06, 0x01, 0x00, 0x20, 0x7E,
...") to use 2 spaces instead of 4 while preserving all byte values and
comments.
- Around line 41-300: The packed font array console_font_6x8 can silently shift
if a byte is added/removed; add a compile-time check: introduce meaningful
constants (e.g., FIRST_GLYPH = 0x20, LAST_GLYPH = 0x7E, GLYPH_BYTES = 6,
HEADER_BYTES = 8) and a static_assert that sizeof(console_font_6x8) ==
(HEADER_BYTES + (LAST_GLYPH - FIRST_GLYPH + 1) * GLYPH_BYTES) with a clear
message so compilation fails if the table size drifts.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 619d700 and 6092ff7.

⛔ Files ignored due to path filters (5)
  • wled00/src/font/old fonts/console_font_4x6_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_5x12_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_5x8_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_6x8_showcase.png is excluded by !**/*.png
  • wled00/src/font/old fonts/console_font_7x9_showcase.png is excluded by !**/*.png
📒 Files selected for processing (25)
  • wled00/FX.cpp
  • wled00/FX.h
  • wled00/FX_2Dfcn.cpp
  • wled00/src/font/6x12.wbf
  • wled00/src/font/console_font_6x8.h
  • wled00/src/font/console_font_6x8.wbf
  • wled00/src/font/console_font_7x9.h
  • wled00/src/font/console_font_7x9.wbf
  • wled00/src/font/font_6x12.h
  • wled00/src/font/font_TinyUnicode_8px.h
  • wled00/src/font/font_TinyUnicode_8px.wbf
  • wled00/src/font/font_tom-thumb_6px.wbf
  • wled00/src/font/font_tom_thumb_6px.h
  • wled00/src/font/old fonts/console_font_4x6.h
  • wled00/src/font/old fonts/console_font_4x6.wbf
  • wled00/src/font/old fonts/console_font_5x12.h
  • wled00/src/font/old fonts/console_font_5x12.wbf
  • wled00/src/font/old fonts/console_font_5x8.h
  • wled00/src/font/old fonts/console_font_5x8.wbf
  • wled00/src/font/old fonts/console_font_6x8.h
  • wled00/src/font/old fonts/console_font_6x8.wbf
  • wled00/src/font/old fonts/console_font_7x9.h
  • wled00/src/font/old fonts/console_font_7x9.wbf
  • wled00/src/font/old fonts/tom-thumb_6px.wbf
  • wled00/util.cpp

@@ -0,0 +1,300 @@
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
// which could be used with an UTF-8 to CP437 conversion
// font courtesy of https://github.com/idispatch/raster-fonts
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

github idispatch raster-fonts license

💡 Result:

The GitHub repo idispatch/raster-fonts does not appear to include a LICENSE/COPYING file, and GitHub does not show a detected repository license in the file listing we can access. [1]

Practically, that means you should treat the contents as copyrighted / “all rights reserved” (i.e., no permission to reuse/modify/redistribute beyond what copyright law allows) unless you get permission from the author(s) or they add an explicit license. [1]

Source

  1. idispatch/raster-fonts repository file listing (no license file shown) [1]

🏁 Script executed:

# Check the file contents and any license information
head -50 "wled00/src/font/old fonts/console_font_6x8.h" 2>/dev/null || echo "File path may need adjustment"

Repository: wled/WLED

Length of output: 2293


Add explicit license provenance for imported font data.

The source repository https://github.com/idispatch/raster-fonts contains no LICENSE file and no detected license terms. Without explicit permission, redistributing this font data in WLED (an open-source project) is not authorized under copyright law. Either add proper license documentation to Line 3 (e.g., explicit license confirmation from the original author) or replace this asset with a font that has an explicit open-source compatible license.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/old` fonts/console_font_6x8.h at line 3, The font file
console_font_6x8.h currently attributes the source but lacks license provenance;
update the top-of-file comment to include an explicit license statement or
written permission from the original author (e.g., add "License: [license name]
— confirmed by author: [name/email] on [date]" or a SPDX identifier) or replace
the glyph data with a font that has a clear open-source license; ensure the
change is applied in console_font_6x8.h (top comment) and any README or
attribution metadata referencing this font is updated accordingly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this font has been used by WLED for years. but you are right, license is unclear

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DedeHai, thanks for the context! Since this is legacy code in the "old fonts" directory, here are a few paths forward to resolve the license uncertainty:

  1. Contact the original author – Reach out to the idispatch/raster-fonts maintainer to request explicit permission or ask them to add a license file
  2. Document existing permission – If WLED obtained permission previously, add that documentation to the file header
  3. Find/substitute a clearly-licensed alternative – Replace with fonts from sources like GNU Unifont, Terminus, or other explicitly open-licensed bitmap fonts
  4. Remove if unused – If the new FontManager system makes these "old fonts" obsolete, consider removing them in a future cleanup

No urgency if this has been stable for years, but documenting the license status (even if it's "used with implicit permission since [year]") would help future contributors. Would you like me to help search for suitable open-licensed replacement fonts?

🐰💭
((
( -.-)
o_(")(")


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX_2Dfcn.cpp:854-876
Timestamp: 2026-02-26T18:46:21.931Z
Learning: In WLED (repo wled/WLED), file wled00/FX_2Dfcn.cpp, class FontManager: null pointer checks are intentionally omitted in methods like getGlyphBitmap(), getGlyphWidth(), and drawCharacter() because the API contract requires callers to first call loadFont() and verify it returns true before using any other FontManager functions. This precondition is documented in the loadFont() comment.

Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX.cpp:6395-6395
Timestamp: 2026-02-26T18:45:23.115Z
Learning: In WLED (repo wled/WLED), file wled00/FX.cpp, function mode_2Dscrollingtext(): the early return when fontManager.loadFont(...) fails is intentional to signal that a custom font wasn’t loaded (not that the effect failed). Do not replace this with a visual fallback that clears or renders with a default font.

Learnt from: DedeHai
Repo: wled/WLED PR: 4982
File: wled00/data/matrixtool/omggif.js:32-35
Timestamp: 2025-10-05T15:41:15.810Z
Learning: The file `wled00/data/matrixtool/omggif.js` is an external third-party library from https://github.com/deanm/omggif by Dean McNamee and should not be reviewed for code issues as it's maintained externally.

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit permission was given AFAIK but the font's original sources are unknow

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DedeHai, thanks for confirming! If explicit permission was given, that resolves the core licensing concern. Since the original sources are unknown, further provenance tracking isn't practical.

Optional follow-up: Consider adding a brief note to the file header documenting the permission status, something like:

// Font courtesy of https://github.com/idispatch/raster-fonts
// Used with explicit permission; original sources unknown

This would help future contributors understand the licensing situation without needing to trace the history. But given this is legacy code in the "old fonts" directory with established permission, no urgent action needed.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX_2Dfcn.cpp:854-876
Timestamp: 2026-02-26T18:46:21.931Z
Learning: In WLED (repo wled/WLED), file wled00/FX_2Dfcn.cpp, class FontManager: null pointer checks are intentionally omitted in methods like getGlyphBitmap(), getGlyphWidth(), and drawCharacter() because the API contract requires callers to first call loadFont() and verify it returns true before using any other FontManager functions. This precondition is documented in the loadFont() comment.

Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX.cpp:6395-6395
Timestamp: 2026-02-26T18:45:23.115Z
Learning: In WLED (repo wled/WLED), file wled00/FX.cpp, function mode_2Dscrollingtext(): the early return when fontManager.loadFont(...) fails is intentional to signal that a custom font wasn’t loaded (not that the effect failed). Do not replace this with a visual fallback that clears or renders with a default font.

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: DedeHai
Repo: wled/WLED PR: 4982
File: wled00/data/matrixtool/omggif.js:32-35
Timestamp: 2025-10-05T15:41:15.810Z
Learning: The file `wled00/data/matrixtool/omggif.js` is an external third-party library from https://github.com/deanm/omggif by Dean McNamee and should not be reviewed for code issues as it's maintained externally.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: softhack007
Repo: wled/WLED PR: 5381
File: wled00/data/update.htm:2-2
Timestamp: 2026-02-19T13:24:28.983Z
Learning: In WLED, generated embedded header files (html_*.h, js_iro.h, etc.) are NOT stored in the repository and have not been for over 2 years. These files are automatically regenerated during the build process from the source files in wled00/data/. Only the source .htm, .html, .css, and .js files in wled00/data/ should be committed when making web UI changes.

Learnt from: softhack007
Repo: wled/WLED PR: 5381
File: wled00/data/dmxmap.htm:2-2
Timestamp: 2026-02-19T13:23:42.152Z
Learning: In WLED, html_*.h files are NOT stored in the repository. They have been excluded for more than two years and are automatically regenerated as part of the build process. Do not request these files to be committed.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/FX.cpp (1)

6339-6341: ⚠️ Potential issue | 🟠 Major

Cast input to unsigned char before std::toupper.

At line 6340, passing raw char into std::toupper is undefined for negative byte values. With UTF-8 text support, this can occur when a # is followed by non-ASCII bytes in the segment name.

🔧 Proposed fix
-          token[j] = std::toupper(SEGMENT.name[i + j]);
+          token[j] = char(std::toupper(static_cast<unsigned char>(SEGMENT.name[i + j])));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6339 - 6341, The call to std::toupper uses raw
char from SEGMENT.name which is undefined for negative bytes; update the
assignment in the loop that sets token[j] to pass an unsigned char cast: call
std::toupper(static_cast<unsigned char>(SEGMENT.name[i + j])) (or C-style
(unsigned char)) so non-ASCII bytes are handled safely, leaving the rest of the
loop and variables (token, SEGMENT.name, i, j) unchanged.
🧹 Nitpick comments (5)
wled00/src/font/font_5x12.h (1)

48-48: Use 2-space indentation per coding guidelines.

The array content uses 4-space indentation, but the project guideline for non-generated header files under wled00/ requires 2-space indentation.

As per coding guidelines: "wled00/**/!(html_*)*.h: Use 2-space indentation for non-generated C++ header files (.h)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/font/font_5x12.h` at line 48, The array initializer line in
font_5x12.h (the byte header row starting with 0x57, 0x0C, ...) is indented with
4 spaces; change its leading indentation to 2 spaces to comply with the wled00
header-file style rule (non-generated .h files use 2-space indentation). Locate
the array/byte header in font_5x12.h and adjust the whitespace at the start of
that line (and any adjacent non-generated header lines in the same file) from 4
spaces to 2 spaces so the file matches the project's indentation guideline.
wled00/FX.cpp (2)

6331-6331: Cache numeric glyphs once per frame instead of once per token.

fontManager.cacheNumbers(true) is invoked repeatedly inside token parsing. Functionally fine, but unnecessary repeated calls on the hot path.

♻️ Suggested refactor
+  bool shouldCacheNumbers = false;
...
-    fontManager.cacheNumbers(true); // cache all numbers when using clock to avoid frequent re-caching
+    shouldCacheNumbers = true;
...
-        fontManager.cacheNumbers(true); // cache all numbers when using clocks to avoid frequent re-caching
+        shouldCacheNumbers = true;
...
+  if (shouldCacheNumbers) fontManager.cacheNumbers(true);

Also applies to: 6374-6375

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` at line 6331, fontManager.cacheNumbers(true) is being called
inside the token parsing hot path causing redundant work; move this call to a
per-frame location (e.g., at the start of the frame render/update routine that
drives token parsing) so numeric glyphs are cached once per frame instead of per
token. Locate the token-parsing code paths that call
fontManager.cacheNumbers(true) (the current occurrences including the one shown
and the other calls around the positions marked 6374-6375), remove those
in-token calls, and add a single fontManager.cacheNumbers(true) at the frame
entry point (the function that begins each render/update cycle) so caching
happens once per frame. Ensure behavior is unchanged by keeping the true
argument and that the new placement executes before token rendering.

6388-6392: Consider named constants for font-count and rotation mapping literals.

The literals 4 (font index max) and 31/-2/2 (rotation mapping) are meaningful domain constants and would be easier to maintain as named constants.

Based on learnings: In WLED reviews, meaningful magic numbers should be replaced with defined constants to improve maintainability and reduce inconsistencies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6388 - 6392, Replace the magic numbers used in
font and rotation mapping with named constants: introduce a constant for the
font index max (used in map(SEGMENT.custom2, 0, 255, 0, 4)) and constants for
the rotation mapping input/output bounds (used in map(SEGMENT.custom3, 0, 31,
-2, 2)); then update the expressions that compute fontNum, rotate and isRotated
(references: fontNum, rotate, isRotated, SEGMENT.custom2, SEGMENT.custom3) to
use those constants so the meanings (max font index and rotation mapping ranges)
are explicit and maintainable.
wled00/FX_2Dfcn.cpp (2)

629-629: Consider replacing magic number 0x80 with a named constant.

The bit flag 0x80 is used to distinguish file-based vs flash fonts in the cache ID. A named constant would improve readability.

Suggested improvement
+// At file/class scope:
+static constexpr uint8_t CACHE_ID_FILE_FLAG = 0x80;
+
-  uint8_t cacheID = _fontNum | (_useFileFont ? 0x80 : 0x00);
+  uint8_t cacheID = _fontNum | (_useFileFont ? CACHE_ID_FILE_FLAG : 0x00);

Based on learnings: "look for 'magic numbers' (hardcoded numeric literals) and suggest replacing them with appropriate defined constants."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` at line 629, The cache ID assembly uses a magic literal
0x80; define a named constant (e.g., FONT_CACHE_FILE_FLAG or FONT_FLAG_FILE =
0x80) and replace the literal in the expression "uint8_t cacheID = _fontNum |
(_useFileFont ? 0x80 : 0x00);" with that constant (and use 0x00 as a named zero
flag if desired); add the constant near other font-related constants or at file
scope and update any other occurrences of 0x80 used for this purpose to use the
new constant for clarity (refer to _fontNum, _useFileFont, and cacheID to locate
the change).

746-746: Consider heap allocation for widthTable to avoid stack pressure.

The VLA uint8_t widthTable[numGlyphs] can be up to 256 bytes. While typically safe, this is called during effect rendering where stack depth may already be significant. Heap allocation would be safer on memory-constrained ESP devices.

Alternative using heap
-  uint8_t widthTable[numGlyphs];
+  uint8_t* widthTable = new (std::nothrow) uint8_t[numGlyphs];
+  if (!widthTable) { if (file) file.close(); return; }
   // ... use widthTable ...
+  delete[] widthTable;
   if (file) file.close();

Or use a small fixed-size buffer since numGlyphs max is 256.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` at line 746, The local VLA "uint8_t
widthTable[numGlyphs]" can cause stack pressure; replace it with a heap
allocation or a fixed max-size buffer: allocate with "uint8_t *widthTable =
(uint8_t*)malloc(numGlyphs);" (check for nullptr and handle error) and
free(widthTable) when done, or use a static/fixed buffer "static uint8_t
widthTable[256];" and ensure numGlyphs <= 256 before writing; update the code
paths that populate and read widthTable to use the pointer or fixed array
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/FX_2Dfcn.cpp`:
- Around line 788-793: The loop populating registry uses widthTable[code]
without validating code against numGlyphs; add a bounds check (e.g., if (code <
numGlyphs)) before accessing widthTable[code] in the loop that writes to
registry[k], registry[k].width, registry[k].height, and handle the else-case by
skipping the invalid code and adjusting how entries are written (either
decrement k to retry with the next valid code, maintain a separate write index
for registry, or reduce neededCount/track validCount) so you never read past the
VLA bounds and registry contains only valid entries.

In `@wled00/FX.cpp`:
- Around line 6472-6474: The rotated glyph height logic loses per-glyph width
because glyphWidth is forced to fontHeight when isRotated; change the order so
glyphWidth is always obtained from fontManager.getGlyphWidth(unicode) (do not
override it with fontHeight), and then set glyphHeight = isRotated ? glyphWidth
: fontHeight to swap dimensions for 90° rotation; update the same pattern at the
other occurrence (the block around glyphWidth/glyphHeight near the second
mention) referencing glyphWidth, glyphHeight, isRotated,
fontManager.getGlyphWidth(unicode), and fontHeight.
- Line 6489: The metadata string _data_FX_MODE_2DSCROLLTEXT currently labels the
control for custom2 as "Font size" which is outdated; update that token in the
semicolon/comma-separated metadata (the "...,Trail,Font size,Rotate,..." segment
inside the static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM) to a clearer
label such as "Font" or "Font selection" to reflect that custom2 now selects a
font (mapped with map(..., 0, 4)); leave the rest of the string intact so UI
parsing continues to work.

---

Outside diff comments:
In `@wled00/FX.cpp`:
- Around line 6339-6341: The call to std::toupper uses raw char from
SEGMENT.name which is undefined for negative bytes; update the assignment in the
loop that sets token[j] to pass an unsigned char cast: call
std::toupper(static_cast<unsigned char>(SEGMENT.name[i + j])) (or C-style
(unsigned char)) so non-ASCII bytes are handled safely, leaving the rest of the
loop and variables (token, SEGMENT.name, i, j) unchanged.

---

Nitpick comments:
In `@wled00/FX_2Dfcn.cpp`:
- Line 629: The cache ID assembly uses a magic literal 0x80; define a named
constant (e.g., FONT_CACHE_FILE_FLAG or FONT_FLAG_FILE = 0x80) and replace the
literal in the expression "uint8_t cacheID = _fontNum | (_useFileFont ? 0x80 :
0x00);" with that constant (and use 0x00 as a named zero flag if desired); add
the constant near other font-related constants or at file scope and update any
other occurrences of 0x80 used for this purpose to use the new constant for
clarity (refer to _fontNum, _useFileFont, and cacheID to locate the change).
- Line 746: The local VLA "uint8_t widthTable[numGlyphs]" can cause stack
pressure; replace it with a heap allocation or a fixed max-size buffer: allocate
with "uint8_t *widthTable = (uint8_t*)malloc(numGlyphs);" (check for nullptr and
handle error) and free(widthTable) when done, or use a static/fixed buffer
"static uint8_t widthTable[256];" and ensure numGlyphs <= 256 before writing;
update the code paths that populate and read widthTable to use the pointer or
fixed array accordingly.

In `@wled00/FX.cpp`:
- Line 6331: fontManager.cacheNumbers(true) is being called inside the token
parsing hot path causing redundant work; move this call to a per-frame location
(e.g., at the start of the frame render/update routine that drives token
parsing) so numeric glyphs are cached once per frame instead of per token.
Locate the token-parsing code paths that call fontManager.cacheNumbers(true)
(the current occurrences including the one shown and the other calls around the
positions marked 6374-6375), remove those in-token calls, and add a single
fontManager.cacheNumbers(true) at the frame entry point (the function that
begins each render/update cycle) so caching happens once per frame. Ensure
behavior is unchanged by keeping the true argument and that the new placement
executes before token rendering.
- Around line 6388-6392: Replace the magic numbers used in font and rotation
mapping with named constants: introduce a constant for the font index max (used
in map(SEGMENT.custom2, 0, 255, 0, 4)) and constants for the rotation mapping
input/output bounds (used in map(SEGMENT.custom3, 0, 31, -2, 2)); then update
the expressions that compute fontNum, rotate and isRotated (references: fontNum,
rotate, isRotated, SEGMENT.custom2, SEGMENT.custom3) to use those constants so
the meanings (max font index and rotation mapping ranges) are explicit and
maintainable.

In `@wled00/src/font/font_5x12.h`:
- Line 48: The array initializer line in font_5x12.h (the byte header row
starting with 0x57, 0x0C, ...) is indented with 4 spaces; change its leading
indentation to 2 spaces to comply with the wled00 header-file style rule
(non-generated .h files use 2-space indentation). Locate the array/byte header
in font_5x12.h and adjust the whitespace at the start of that line (and any
adjacent non-generated header lines in the same file) from 4 spaces to 2 spaces
so the file matches the project's indentation guideline.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6092ff7 and d355ab4.

📒 Files selected for processing (4)
  • wled00/FX.cpp
  • wled00/FX_2Dfcn.cpp
  • wled00/src/font/font_5x12.h
  • wled00/src/font/font_5x12.wbf

Comment on lines +788 to +793
for (uint8_t k = 0; k < neededCount; k++) {
uint8_t code = neededCodes[k];
registry[k].code = code;
registry[k].width = widthTable[code];
registry[k].height = hdr.height;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Bounds check missing: widthTable[code] access without validation.

At line 766, there's a bounds check if (code < numGlyphs) when calculating ramFontSize, but the registry population loop at lines 788-793 unconditionally accesses widthTable[code] without the same check. If code >= numGlyphs (possible with extended Unicode ranges or corrupted cache data), this reads beyond the VLA bounds.

Proposed fix
   // write registry (GlyphEntry array)
   GlyphEntry* registry = (GlyphEntry*)dataptr;
   for (uint8_t k = 0; k < neededCount; k++) {
     uint8_t code = neededCodes[k];
+    if (code >= numGlyphs) continue; // skip invalid codes
     registry[k].code = code;
     registry[k].width = widthTable[code];
     registry[k].height = hdr.height;
   }

Note: If codes are skipped, you may also need to adjust neededCount or track valid entries separately to avoid gaps in the registry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_2Dfcn.cpp` around lines 788 - 793, The loop populating registry
uses widthTable[code] without validating code against numGlyphs; add a bounds
check (e.g., if (code < numGlyphs)) before accessing widthTable[code] in the
loop that writes to registry[k], registry[k].width, registry[k].height, and
handle the else-case by skipping the invalid code and adjusting how entries are
written (either decrement k to retry with the next valid code, maintain a
separate write index for registry, or reduce neededCount/track validCount) so
you never read past the VLA bounds and registry contains only valid entries.

Comment on lines +6472 to +6474
int glyphWidth = isRotated ? fontHeight : fontManager.getGlyphWidth(unicode);
int glyphHeight = isRotated ? glyphWidth : fontHeight; // use (variable) glyph-width for height if 90° rotated
int drawX = int(cols) - int(SEGENV.aux0) + currentXOffset; // aux0 is (scrolling) offset, no offset position is right side boarder (cols)
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix rotated glyph height calculation for variable-width fonts.

At Line 6473, glyphHeight in rotated mode is derived from glyphWidth, but glyphWidth is already forced to fontHeight. This loses per-glyph width and skews vertical centering/clipping for rotated variable-width fonts.

🔧 Proposed fix
-    int glyphWidth = isRotated ? fontHeight : fontManager.getGlyphWidth(unicode);
-    int glyphHeight = isRotated ? glyphWidth : fontHeight; // use (variable) glyph-width for height if 90° rotated
+    int unrotatedGlyphWidth = fontManager.getGlyphWidth(unicode);
+    int glyphWidth = isRotated ? fontHeight : unrotatedGlyphWidth;
+    int glyphHeight = isRotated ? unrotatedGlyphWidth : fontHeight; // use variable width as rotated height

Also applies to: 6483-6483

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6472 - 6474, The rotated glyph height logic loses
per-glyph width because glyphWidth is forced to fontHeight when isRotated;
change the order so glyphWidth is always obtained from
fontManager.getGlyphWidth(unicode) (do not override it with fontHeight), and
then set glyphHeight = isRotated ? glyphWidth : fontHeight to swap dimensions
for 90° rotation; update the same pattern at the other occurrence (the block
around glyphWidth/glyphHeight near the second mention) referencing glyphWidth,
glyphHeight, isRotated, fontManager.getGlyphWidth(unicode), and fontHeight.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DedeHai Thanks! Here’s a minimal fix that preserves per-glyph width when rotated 90°, while keeping the horizontal advance equal to fontHeight.

Patch (FX.cpp, mode_2Dscrollingtext draw loop; Lines around 6472–6484):

-    int glyphWidth = isRotated ? fontHeight : fontManager.getGlyphWidth(unicode);
-    int glyphHeight = isRotated ? glyphWidth : fontHeight; // use (variable) glyph-width for height if 90° rotated
+    int unrotatedGlyphWidth = fontManager.getGlyphWidth(unicode);
+    int glyphWidth  = isRotated ? fontHeight            : unrotatedGlyphWidth;     // rotated: advance by fontHeight
+    int glyphHeight = isRotated ? unrotatedGlyphWidth   : fontHeight;              // rotated: height = original glyph width

This keeps:

  • advance = glyphWidth = fontHeight when rotated (correct), and
  • vertical centering/clipping based on each glyph’s original (variable) width.

Let me know if you want me to prep a follow-up commit/PR with this change.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX.cpp:6395-6395
Timestamp: 2026-02-26T18:45:23.115Z
Learning: In WLED (repo wled/WLED), file wled00/FX.cpp, function mode_2Dscrollingtext(): the early return when fontManager.loadFont(...) fails is intentional to signal that a custom font wasn’t loaded (not that the effect failed). Do not replace this with a visual fallback that clears or renders with a default font.

Learnt from: DedeHai
Repo: wled/WLED PR: 5372
File: wled00/FX_2Dfcn.cpp:854-876
Timestamp: 2026-02-26T18:46:21.931Z
Learning: In WLED (repo wled/WLED), file wled00/FX_2Dfcn.cpp, class FontManager: null pointer checks are intentionally omitted in methods like getGlyphBitmap(), getGlyphWidth(), and drawCharacter() because the API contract requires callers to first call loadFont() and verify it returns true before using any other FontManager functions. This precondition is documented in the loadFont() comment.

Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Learnt from: blazoncek
Repo: wled/WLED PR: 4995
File: wled00/FX.cpp:5209-5212
Timestamp: 2025-10-20T09:41:50.374Z
Learning: In WLED 2D effects, SEGMENT.length() returns the physical pixel count (upper bound), while SEG_W * SEG_H can be smaller depending on mapping (mirroring/grouping). For per-cell state buffers (e.g., mode_2Dgameoflife in wled00/FX.cpp), allocate using SEGMENT.length() to ensure sufficient capacity and avoid reallocations, while bounding indexing by cols*rows.

Learnt from: DedeHai
Repo: wled/WLED PR: 5040
File: wled00/image_loader.cpp:84-96
Timestamp: 2025-11-14T05:48:44.673Z
Learning: In WLED (wled00/FX_2Dfcn.cpp), the Segment::setPixelColorXY() function performs internal bounds checking against vWidth() and vHeight(), returning early if coordinates are out of bounds. No additional guards are needed when calling this function, even in upscaling loops where coordinates might exceed segment dimensions.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 4891
File: wled00/FX.cpp:3333-3349
Timestamp: 2025-08-31T03:38:14.114Z
Learning: WLED PacMan effect (wled00/FX.cpp): Keep pacmancharacters_t position fields as signed int (not int16_t). Maintainer preference (blazoncek) prioritizes avoiding potential overhead/regressions over minor RAM savings. Avoid type shrinking here unless memory pressure is demonstrated.

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cyrilyc font 6x8, and special character support for scrolling text

1 participant