Skip to content

fix: render missing Unicode reader glyphs#104

Merged
uxjulia merged 1 commit intouxjulia:mainfrom
chriskd:fix/synthetic-unicode-glyphs
May 8, 2026
Merged

fix: render missing Unicode reader glyphs#104
uxjulia merged 1 commit intouxjulia:mainfrom
chriskd:fix/synthetic-unicode-glyphs

Conversation

@chriskd
Copy link
Copy Markdown

@chriskd chriskd commented May 7, 2026

Summary

  • Fixes rendering problems in There Is No Antimemetics Division on the Xteink X4/CrossInk tiny firmware, where black-bar redactions could collapse into whitespace and some symbolic text rendered as replacement diamonds.
  • Adds small synthetic renderer fallbacks for the missing Unicode glyphs that motivated that failure but can also appear in other EPUB text: full block redactions, black square ornaments, Greek Γ/ε/ω, and U+02BB turned-comma punctuation via an existing curly-quote alias.
  • Keeps the change in renderer/measurement code rather than regenerating or expanding font assets, so it avoids adding large glyph tables and keeps RAM usage unchanged.
  • The black redaction bars render as solid bars now. The non-black-bar fallbacks, especially the Greek glyphs, are intentionally approximate and may not look as good as real font glyphs; this seems like a reasonable compromise between somewhat proper rendering and not increasing firmware image size with broader font coverage.
  • Adds a changelog entry for the reader rendering fix.

Why

The primary motivator was There Is No Antimemetics Division, which includes black-bar redactions and symbolic/category text that exposed missing glyph coverage in the tiny firmware fonts. On device, those missing glyphs either collapsed into confusing whitespace or displayed replacement diamonds. This was especially confusing for black-bar redactions because the redacted span looked like a normal gap in the sentence.

I scanned the visible text in that EPUB against the tiny firmware font headers and found the unsupported visible text codepoints were U+02BB, U+0393, U+03B5, U+03C9, U+2588, and U+25A0. This patch handles that set generically rather than adding a book-specific workaround.

Validation

  • Ran ./bin/clang-format-fix using clang-format 22.1.4.
  • Ran pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high; passed for tiny, xlarge, and no_emoji.
  • Ran pio run; passed for tiny, xlarge, and no_emoji.
  • Compared tiny build size before/after: RAM stayed unchanged for tiny, while flash increased by about 2.1 KB.
  • Manually flashed the tiny firmware and checked There Is No Antimemetics Division pages on Xteink X4/CrossInk: page 6/9 redaction bars rendered correctly; the Greek/turned punctuation page improved but remains visually approximate.
  • Cleared the CrossInk EPUB cache before retesting cached layout/rendering behavior.

Disclosure

This patch was written with Codex assistance; in plain terms, it was vibecoded and then locally formatted, built, and checked before opening this PR.

Summary by CodeRabbit

  • New Features

    • Added support for synthetic glyph rendering with improved Unicode character fallback options.
    • Enhanced EPUB document rendering for horizontal rule elements.
  • Bug Fixes

    • Fixed rendering of missing Unicode-related character artifacts in reader fonts.
    • Improved system stability with safer cache and memory management.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Warning

Rate limit exceeded

@chriskd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 48 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a08058ef-39cc-4fcf-b0f8-ddff5a58f75c

📥 Commits

Reviewing files that changed from the base of the PR and between 22ab03f and 23096f9.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • lib/EpdFont/EpdFontData.h
  • lib/EpdFont/EpdFontFamily.cpp
  • lib/EpdFont/EpdFontFamily.h
  • lib/GfxRenderer/GfxRenderer.cpp
📝 Walkthrough

Walkthrough

This PR adds procedural rendering of synthetic glyphs to fill missing Unicode blocks and Greek characters. The implementation defines classification and metric helpers, integrates measurement into text sizing, and wires rendering into the main text pipeline for normal and rotated text.

Changes

Synthetic Glyph Rendering for Unicode Block & Greek Fallback Characters

Layer / File(s) Summary
Data Contracts & Synthetic Glyph Constants
lib/EpdFont/EpdFontData.h
New syntheticGlyph namespace with codepoint constants (solid blocks, Greek characters) and helper functions to classify glyphs, alias codepoints, and compute metrics for solid and Greek fallback variants.
Text Measurement Integration
lib/EpdFont/EpdFontFamily.cpp
getTextDimensions now aliases codepoints and intercepts synthetic glyphs to compute bounding boxes using synthetic metrics, including kerning and state tracking.
Rendering Helpers & Solid Glyph Drawing
lib/GfxRenderer/GfxRenderer.cpp
Introduces SyntheticSolidGlyphMetrics and helper functions to compute solid glyph metrics and stroke thickness for procedural drawing.
Greek Glyph Rendering
lib/GfxRenderer/GfxRenderer.cpp
Adds rendering utilities for Greek characters (gamma, epsilon, omega) in normal and 90° clockwise rotated orientations using template patterns and composed pixel logic.
Text Rendering Pipeline Integration
lib/GfxRenderer/GfxRenderer.cpp
drawText, getTextAdvanceX, and drawTextRotated90CW now alias codepoints and short-circuit for synthetic glyphs, using synthetic metric/draw functions instead of font glyph lookup.
Documentation
CHANGELOG.md
Adds entries for EPUB <hr> rendering, Unicode block and Greek character rendering, plus unrelated fixes for SD-card serialization and EPUB cache hardening.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit hops through fonts so small,
With blocks and Greek for one and all—
Solid squares now dance with gamma bright,
When real fonts fail, synthetic saves the sight! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The PR title 'fix: render missing Unicode reader glyphs' directly and concisely describes the main change—adding synthetic renderer fallbacks for missing Unicode glyphs in EPUB text. The title clearly conveys the primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/EpdFont/EpdFontFamily.cpp`:
- Around line 46-47: The code currently applies
syntheticGlyph::aliasCodepoint(cp) based only on the input codepoint; instead,
first perform the real glyph lookup (the same lookup used elsewhere in this
file, e.g., getGlyphIndex/lookupGlyph or the existing "real glyph" path) and
only if that lookup returns a miss (glyph index == missing/0/-1) then call
syntheticGlyph::aliasCodepoint(cp) and retry the lookup; update all
corresponding renderer paths to follow the exact same miss-then-fallback flow
(the blocks around syntheticGlyph::aliasCodepoint and their counterparts in
lines ~49-98) so fonts that actually contain those glyphs use the real metrics
and future table additions take effect.

In `@lib/GfxRenderer/GfxRenderer.cpp`:
- Around line 162-184: The rotated-90-CW Greek glyph Y coordinate is computed
incorrectly; in drawSyntheticGreekGlyphRotated90CW update the per-pixel Y to
match the bitmap renderer's mapping (cursorY - metrics.left - glyphX).
Concretely, stop using baseY = cursorY - metrics.left - metrics.width + 1 and
baseY - gx; instead compute the Y as cursorY - metrics.left - gx (or set baseY =
cursorY - metrics.left and call renderer.drawPixel(baseX + gy, baseY - gx,
pixelState)), keeping the X calculation (baseX + gy) unchanged so the rotated
glyph covers the same Y span as renderCharImpl<TextRotation::Rotated90CW>.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c12151c-8967-4dc9-aefd-bea4db9cf7af

📥 Commits

Reviewing files that changed from the base of the PR and between 7db792e and 22ab03f.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • lib/EpdFont/EpdFontData.h
  • lib/EpdFont/EpdFontFamily.cpp
  • lib/GfxRenderer/GfxRenderer.cpp

Comment thread lib/EpdFont/EpdFontFamily.cpp Outdated
Comment thread lib/GfxRenderer/GfxRenderer.cpp
@chriskd chriskd force-pushed the fix/synthetic-unicode-glyphs branch from 22ab03f to 23096f9 Compare May 7, 2026 06:45
@chriskd
Copy link
Copy Markdown
Author

chriskd commented May 7, 2026

Addressed the CodeRabbit findings in the latest force-push (23096f9):

  • Valid finding: early aliasing/fallback could bypass future real font glyphs. Added EpdFontFamily::findGlyphData() for real glyph lookup without replacement fallback, and getFallbackCodepoint() now aliases only after that real lookup misses. Measurement, normal draw, advance measurement, and rotated draw now gate synthetic glyphs behind !hasRealGlyph.
  • Valid finding: rotated Greek fallback Y mapping was off. Updated it to match renderCharImpl<TextRotation::Rotated90CW>: baseY = cursorY - metrics.left, then baseY - gx per pixel.

Extra validation after the fix:

  • Ran a targeted source inspection for the same class of issue: aliasCodepoint() is only used inside the miss-aware helper; all synthetic solid/Greek branches are gated by !hasRealGlyph; rotated Greek mapping matches the normal rotated renderer formula.
  • Ran git diff --check.
  • Ran ./bin/clang-format-fix via the pre-commit hook.
  • Ran pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high; passed for tiny, xlarge, and no_emoji.
  • Ran pio run; passed for tiny, xlarge, and no_emoji.

@uxjulia uxjulia merged commit c4357d0 into uxjulia:main May 8, 2026
5 checks passed
@uxjulia uxjulia mentioned this pull request May 9, 2026
@chriskd
Copy link
Copy Markdown
Author

chriskd commented May 9, 2026

@uxjulia hi there! I spotted a bug in the implementation I submitted, but wasn't able to open a new PR to fix it - you can find it in https://github.com/chriskd/CrossInk/tree/fix/synthetic-unicode-glyphs

The bug was that synthetic glyph drawing could write outside the display bounds. This can happen when a synthetic glyph is partially off-screen during layout/rendering, especially in books with lots of block characters such as U+2588 FULL BLOCK. The renderer logs out-of-bounds pixel writes heavily, and on device this manifested as instability/crashes while opening or rendering affected EPUBs.

The way this manifested, and how I caught it, was when trying to run KOReader sync on a page where the glyphs were rendered. Doing that would lead to a crash.

Sorry for the hassle, and thank you for the great work on this project!

@uxjulia
Copy link
Copy Markdown
Owner

uxjulia commented May 9, 2026

Hey thanks for letting me know! I'll take a look. Sorry, I had to lock down the PRs, I felt like the project was starting to slip away from me a bit with people submitting things left and right that didn't fit with my vision and I felt like I needed to reign things in before it got out of hand. I appreciated your PR though as it addressed an interesting gap without bloating the firmware.

@chriskd
Copy link
Copy Markdown
Author

chriskd commented May 9, 2026

Totally understand! People don’t appreciate the work that goes into maintaining FOSS projects, especially when they catch on and get popular in niche communities. And then I come along with a buggy vibe coded patch 😅

thank you again for the hard work!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants