RSS Amplifier

nicoverbruggen.be · Jul 18, 2026

Fixing Kobo's typesetting quirks on firmware 4.x

0
Sign in to vote or save

Nico Verbruggen · nicoverbruggen.be

In the past, I’ve owned both Kindle and Kobo devices, and I think when it comes to examining these devices at stock, there’s no doubt that a Kindle has a better reading experience.

Kindles ship with better fonts out of the box, and the font rendering stack, which decides what text looks like: you know, that thing you’re effectively staring at for the majority of your time with the device. Compared to Kindle, how text is displayed is somewhat deficient on Kobo if you know what you’re looking at.

I should probably start with a bit of a disclaimer. I understand that a lot of people will probably never notice any of these issues and are perfectly fine reading even with the default settings. But as someone who enjoys not only beautiful typography but also excellent visual design in books, it is something that matters to me.

A quick recap

I’ve blogged about it before: I don’t really like what Amazon is doing with their grip on the digital book ecosystem. Conversely, I like supporting Rakuten Kobo, the underdog. Their devices are much more open and it’s thanks to that openness that I was able to make KoboPatch Web UI so easy to use if you want to mod your device.1

I’ll be honest: I’m a bit of a typography nerd. A snob, some might say. It’s been that way even before I purchased my first MacBook and I was astonished by the beautiful typefaces that I’d never seen before when I was on Windows and Linux: Baskerville, Cochin, Futura, Gill Sans, Helvetica Neue, Hoefler Text…2

Being fascinated by typography may very well be the only reason I can think of that properly explains why I went through the process of doing literal patching of fonts just to get them to look right on my e-reader to the point of automating the process.

But I didn’t stop there, I also worked on a derivative3 font, Readerly, which was based on Newsreader with automated transformations to get to the output font.

I also eventually started doing manual outline editing in FontForge to create Libron, which is the font that I recommend most these days for anyone who would like an alternative to Bookerly, since that is a proprietary font, and Libron is not.

(I should note: Libron is not a Bookerly clone. It intentionally isn’t, and that you can trace its origins back to Newsreader is a feature.)

Anyway, that’s a bit of a quick recap of what has happened prior to all of this. Until not too long ago, I was pretty sure my work was done. But there was one thing annoying me: something I had noticed myself, too. The wobble. The dang wobble.

The curious case of the inconsistent baseline

With wobble, I am, of course, referring to the sometimes inconsistent baseline rendering of certain fonts on Kobo devices. The issue that I’ve linked illustrates it clearly, despite it not being an issue with the actual font. (Initially, I thought it had been my fault!)

Ever since, I’ve started affectionately calling this “the wobble problem”, because it sort of feels like the letters are “wobbling” on the page due to the inconsistent baseline.

I had dismissed the problem for some time now, because it’s less pronounced when you use the font at a large text size. But I know that people are using the fonts from the eBook Fonts collection at various font sizes, so I decided that it was time to properly investigate.

Over the last months, I’ve made numerous attempts at trying to understand what causes this particular issue, and I’ve been attempting to fix it ever since. I noticed that applying hints to a given font allows it to be rendered slightly differently, usually with good results.

I say usually, because sometimes the result ends up being slightly worse, which is not really what I was aiming for. So for the last little while I’ve been tinkering around with all sorts of changes in an attempt to get the Kobo renderer to correctly render the glyphs without any baseline issues.

I don’t think it’s acceptable that a device that’s intended to be used for reading does a poor job at rendering text.

Breakthroughs via LLM

I had a breakthrough recently, as I was going through the process of debugging the entire renderer with Claude Code and OpenAI’s Codex. I was informed by someone quite knowledgeable that LLMs are actually generally speaking more useful for debugging compared to actual programming.

An important lesson here is this: if you build tools that LLMs can use to validate output, suddenly they become a lot more interesting and reliable. Over the last months, I was using both to try to learn more about how Kobo’s rendering stack works.

I learned a bunch of things. Here’s some of the essentials:

  • Kobo devices run Linux. They’re so open you can even get SSH access and remote connect to them. I was able to dump the entire filesystem for analysis because of this.
  • The main software layer is called Nickel. That’s why the popular mod is called NickelMenu.
  • ePub files, and also Kobo-specific ePub files, are nothing but a zip archive with some HTML and CSS. They’re like mini-webpages. To display them, you need a rendering engine usually based on browser technology.
  • On Kobo devices, there are two such renderers: one is based on WebKit (used for kepub files) and one is Adobe’s proprietary RMSDK which is actually migrating away from Adobe at the time of writing.
  • If you are reading store-bought books or converted kepub files, you can have some of the rendering issues I was running into.

Relevant for our investigation are these points:

  • The WebKit-based renderer uses an old version of WebKit as the main layout engine, but the actual text rendering happens via FreeType, which has a proprietary extension included (iType) which allows customizations of various parameters to modify how font outlines are rendered.
  • iType is likely a Monotype invention4. The “font weight” slider in the Advanced font panel in Kobo’s reader app uses these so-called CSM (contrast/sharpness modulation?) parameters. iType is actually the driver and rasterizer.

Here’s a visualization of how these libraries are loaded:

Nickel (Qt 5.2.1 application, libnickel.so.1.0.0)
  └─ kepub books rendered via QtWebKit (Kobo-patched)
       └─ QPainter / QRawFont / drawGlyphRun
            └─ libkobo.so   (Qt platform plugin)
                 └─ custom QFontEngineFT
                      └─ libfreetype.so.6.6.2   ← FreeType API surface
                           └─ Monotype iType    ← the actual scaler/rasterizer

To make it easier to understand:

   An EPUB book is like a tiny website (HTML + CSS + fonts)
   ┌────▼─────────────────────────────────────────────┐
   │  Nickel        the main Kobo app (libnickel.so)  │
   │                decides fonts, injects CSS        │
   ├──────────────────────────────────────────────────┤
   │  Qt WebKit     a web browser engine (Qt 5.2.1)   │
   │                lays out the page, like Chrome    │
   ├──────────────────────────────────────────────────┤
   │  Qt font engine (QFontEngineFT, HarfBuzz)        │
   │                 from text to positioned "glyphs" │
   ├──────────────────────────────────────────────────┤
   │  FreeType + iType   (libfreetype.so)             │
   │                     turns each glyph into pixels │
   └──────────────────────────────────────────────────┘
   Pixels on the e-ink screen

I needed to find out what was causing the actual rendering issue:

  • Was the font faulty? (Likely not.)
  • Was the text layout broken? (Perhaps?)
  • Was the outline rendering broken? (Likely?)

I was willing to explore all three, but my investigation eventually led me to prompt the LLMs to create a “pixel inspector”. I also went on a whole detour where I let Claude swap out the iType renderer, so it would use stock FreeType instead (which is what is used to render the UI).5

I can validate with my own eyes if something looks right, but Claude can’t (reliably). That’s why I had it build the “pixel perfect rendering” system: Claude can validate correctness by simply checking the raw pixels, which are output to a plain text file.

Once the pixels aren’t the same between the before and after, there’s altered rendering. If the altered pixels are only where the known mistakes are, we know there’s a potential fix. I knew that there were certain sentences I could try, and I could point out where I’d expect to see the fix apply.

I also asked Claude to write me a report at the end, and this is what the generated report included:

The breakthrough tool was a pixel-level ink inspector. After iType rasterizes a glyph into an alpha bitmap (FT_Outline_Get_Bitmap), the inspector scans the buffer row by row for the first and last rows containing ink (non-transparent pixels). This yields the glyph’s true rendered height in pixels, independent of metrics or claimed positions.

Emulating Kobo’s binaries

To do this, I needed to run some of the compiled code as-is in order to be able to reproduce the bug visually. That would allow me to confirm a fix. First, we’d render a flawed version, then we’d patch the binary, and then we’d render a fixed version.

So, I told Claude to get to work and surprisingly, it delivered. Using QEMU it was possible to run a selection of instructions as-is in order to construct the pixel inspector. It was explained to me as such:

The device library is a self-contained 32-bit ARM (hardfloat) shared object that depends only on libc and libgcc_s. So it runs unmodified under qemu-arm. With the toolchain gcc-arm-linux-gnueabihf we can cross-compile a tiny harness that links the device’s FreeType library (not the distro’s). qemu then emulates the ARM code.

We’ll get back to the importance of being able to run the actual code that also runs on the device without a back and forth roundtrip, because it has implications for later issues I encountered.

Where it goes wrong

The problem, essentially, is related to how the rasterizer in this particular case works. As I noted before, fonts used to be more pixelated, i.e. there were fewer pixels and rendering fonts in a certain amount of pixels requires work for it to look right.

If you used Windows and Mac OS X back in the day on a low-DPI screen, you probably preferred one over the other when it came to how they handle font rendering. It all comes down to a difference in philosophy, as it turns out. In a blog post written from 2007, Joel Spolsky wrote:

Apple generally believes that the goal of the algorithm should be to preserve the design of the typeface as much as possible, even at the cost of a little bit of blurriness.

This is in contrast to Microsoft, who did things differently:

Microsoft generally believes that the shape of each letter should be hammered into pixel boundaries to prevent blur and improve readability, even at the cost of not being true to the typeface.

On modern HiDPI displays, the old fight matters much less. High density displays give both systems enough pixels that they do not need to distort glyphs as aggressively. macOS has effectively leaned into grayscale antialiasing since the Mojave era, and Windows is all over the place because how fonts are rendered mostly depends on the software stack itself. For example, you have DirectWrite/Direct2D/Skia-style rendering, but also older Win32/GDI paths can still behave differently. ClearType tuning is also still relevant for those older apps.

But anyway, that’s enough for my little historical tangent. How you decide to render text on a device with limited pixels depends on your philosophy.

Kobo devices have a renderer that aggressively tries what we called “grid-fitting”, which seems to be similar to Microsoft’s approach, resulting in “sharper” text, sometimes at the cost of accurately displaying the various designs that are part of the actual font files.

Historically, that makes sense: E Ink displays used to be worse (lower resolution). But with Kobo devices these days being 200+ DPI, just rendering the outlines and not caring much about hinting or grid fitting is actually a sensible idea for a more honest representation of what a font looks like. That’s what I was aiming for.

Regardless, I was able to discern that it was a grid-fitting problem, and it was specific to some fonts under some circumstances. There’s an easy solution to this problem, too: simply stop grid-fitting.

My initial investigation led me to simply hinting the font, but that didn’t solve the issue. Sometimes fonts end up looking nicer when hinted, but the inconsistent baseline seemed to persist. I asked the LLMs to investigate, and after the “pixel inspector” was tasked to find differences and a further investigation was launched, this was reported:

And here’s where iType — the specific, proprietary rasterizer on this device — does something that mainstream FreeType doesn’t: iType’s automatic grid-fitting depends on the letter’s horizontal sub-pixel position.

That may sound like it’s a foreign language to you, and it did to me. I asked for clarification, and this is what Claude told me:

Why would horizontal position affect vertical height? Because of how text is laid out. As words are placed, the “pen” moves across the line and accumulates fractional positions — after some letters the pen sits at x = 10.0, after others at x = 10.3. When iType then grid-fits a letter sitting at x = 10.3, the rounding of its top edge comes out differently than for the same letter at x = 10.0. Same letter, two heights, purely because of where it landed horizontally.

This explains why sometimes an identical character would end up being correctly rendered on one line, and incorrectly on another. The position of where the glyph is matters when drawing and determining what pixel to snap to.

The fix, it seemed, was easy:

Inside iType there’s a decision gate (the disassembly pinned it to address 0x6f302 in libfreetype.so) that reads exactly this bit: if “no hinting” is set, it skips the grid-fitter entirely and renders the raw, smoothly-scaled outline. With no snapping, every instance of a letter is geometrically identical.

Sure enough, this turned out to be the solution. Judge for yourself, I made a little illustrative picture that highlights where you can look to see the differences:

A before and after grid-fitting, i.e. before and after the fix. The latter uses the smoothly-scaled outlines, without attempting to snap to a particular pixel row.
A before and after grid-fitting, i.e. before and after the fix. The latter uses the smoothly-scaled outlines, without attempting to snap to a particular pixel row.

NickelHome

You may recall that some time ago back in March, I was tinkering around with Kobo’s home screen.

I wrote about that in my blog post about KoboPatch Web UI. I wrote about how NickelMenu is basically persistent, which is what I wanted for these typesetting fixes, too:

I believe I mentioned NickelMenu earlier. It is actually a modification that persists after software updates, because of how it works. It patches onto the system partition a new file that never gets overwritten by Kobo’s software packages, but can uninstall itself if it deems itself incompatible.

I already worked on NickelHome, a standalone port of my modifications to NickelMenu, and it gave me a good starting point for hooking into more of Kobo’s software stack.

I wanted a durable fix, that would not vanish after installing a mod, so I decided I was going to do two things:

  • Make a mod so every font loads correctly.
  • Integrate something into KF fonts in kobo-font-fix so that the correct outlines are loaded on Kobo devices even without the mod.

This gives font enjoyers the solution without needing to apply a custom mod, and for type fanatics like me, a much better reading experience.

NickelTypeFix

Most mods just hook into libnickel, the main library driving Kobo’s software stack, but there are others you can hook into. In order to make this work, I’d need to hook into some other libraries.

Anyway, for a complete technical breakdown, I recommend reading through the documentation in the repository of the mod that I created to address this and a few other problems. The mod is called NickelTypeFix and it aims to fix these issues:

  • The baseline alignment issue (1 pixel glyph shift, the subject of this blog post).
  • Text justification.
  • Letter spacing not being applied to spaces.
  • Capital spacing (cpsp) being applied incorrectly.
  • Vertical CJK text rendering incorrectly.
  • The reader incorrectly rendering fonts with a number in the name.
  • The book falling back to the system font.

In particular, the baseline alignment issue was driving me mad, and that’s the one that I find the most impactful. If you’re curious, the repository contains more information about the various fixes and how the issues were fixed.

I also made another mod that emulates the animation that’s also on Kindles. I really like it. It probably deserves a dedicated blog post, too.

Enhancing kobofix.py

I decided that I also wanted to bake something into the kobo-font-fix repository, if possible. Doing so would enable all KF font variants that I distribute to benefit from this fix. To recap what causes the problem:

A font with no per-glyph instructions falls into iType’s automatic grid-fitting, which snaps each glyph’s top to a whole pixel row depending on its sub-pixel position, so the same letter renders at slightly different heights from one place to the next, which looks like an uneven baseline.

Notice how it says a font with no per-glyph instructions. After learning that font hinting does affect rendering, I wondered, and asked myself the question: could we bake hinting instructions into the font that effectively say: don’t do anything? That would avoid triggering the automatic grid-fitting path:

iType routes a glyph that carries any instructions through its interpreter instead, bypassing the auto-grid-fit. So the KF preset removes global hinting tables and replaces every outline glyph’s bytecode with a single one-byte no-op program (SVTCA[Y], opcode 0x00) that moves no points: the outline is emitted byte-for-byte unchanged, but iType now renders the raw scaled outline (unmodified).

Sure enough, this fixes the baseline issue conclusively on custom fonts, but doesn’t fix any of the other problems that the mod fixes, but it’s a quick win for everyone who loves having good typography on their e-reader.

To be continued…


  1. The idea of jailbreaking your device is not even a thing with these devices. You just need to change a file on the filesystem and you can easily enable SSH. At that point, you’re essentially free to do with your device what you want. Well, within reason, of course. But it’s effectively way less locked down than, say, a Kindle, which does require a jailbreak. 

  2. In case you didn’t know: Microsoft wasn’t keen on paying Linotype’s licensing fee for their fonts, so various similar typefaces were created that were identical in terms of layout. This is why and how Arial, Times New Roman and various other fonts were created. Font enthusiasts will often prefer the original Linotype fonts. For example, see Mark Simonson’s The Scourge of Arial written in 2001, when Arial was taking over as the default font in Microsoft Office. In 2001, 72 dpi screens were the norm, so Mark wrote: “The situation today is that Arial has displaced Helvetica as the standard font […] This is not such a big deal since at the low resolution of a computer screen, it might as well be Helvetica.“By comparison, Apple has always had ties to the desktop publishing industry and they always shipped proper fonts with their operating system. 

  3. Readerly isn’t that special, because I never did any manual editing on it. It was simply based on an existing font and various reproducible transformations were applied to the original variable font. Libron, on the other hand, has been a project that I’ve been working on a ton with lots of manual editing. 

  4. Given that I’ve theorized that Rakuten Kobo have ended their licensing deal with Monotype given the disappearance of their fonts on the Kobo Libra Colour, Kobo Clara Colour and Kobo Clara BW, I wonder what they will do in the future with font rendering on firmware 5.x. I don’t have many good things to say about the so called ‘accessibility preview’ (opt-in build) that you can install if you’re using a recent Kobo device in Europe. It’s slower and the text looks worse. Given that the primary activity on a Kobo is reading, I’d say this is pretty unforgivable. I can only hope that they properly fix the issues. There’s also an interesting thread with various observations on the MobileRead forums, of course. 

  5. The engine swap was an interesting experiment, but the actual text rendering afterwards was better in some ways, this meant that the CSM system which is governed by the font weight slider in the Advanced text panel doesn’t work. I tried getting that working, but I felt the results were not good, plus the font slider quickly impacted the font rendering of the UI due to how the hooking mechanism works, so I abandoned this experiment. 

This was not generated: Unless explicitly specified, the posts on my blog are my own creative work. 100% written (typos included) and edited by me; not generated using e.g. large language models.

Read the original on nicoverbruggen.be

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.