eol's blog

Breathing new life in an old eReader

In 2016, I bought my first eReader, an Inkbook Obsidian, to avoid reading on my phone. It is an Android eReader, running Android 4.2.2, based on the chinese Boyue T63. Since then, it was my trusted reading platform. Not very modern, not very responsive, but to read books, I never needed that.

I mostly read in english, sometimes in french, but since October 2024, I’ve also been reading in Romanian, to help me learn more vocabulary and use the language. But if in either english or french, I never needed a dictionnary, in Romanian, I can’t go a page without looking up a word. Since I’m reading in the train, sometimes there is not cellular network, and I cant use the internet.

I found KOreader, and eReading software intended to run on many platforms (android, linux, remarkable, kobo, kindle…), and I went back to reading on my phone, as the last release on fdroid were not compatible with my reader. I’ve combined it with QuickDic, a third party app for offline dictionnaries, but the experience has been subpar. Dictionnary searches were buggy, often without finding a match because of the declension in the Romanian language.

Also, I don’t like reading on my phone. I always end up doing something else than reading. So I went back to look for a better option.

KOreader

What I found is that KOreader had stopped supporting Android 4.2.2 since version 2023.06.1. When installed on my reader, it works, but there was not support for the eInk display. Without the full refresh, I had too much ghosting for confortable reading.

Also, when using the KOreader dictionnary support, the Romanian-English dict was always first in the results. Since it is less comprehensive and makes me practice less, I found in this pull requests on github a way to better sort the results based on similarity and dict priority.

Dev Environment

My first attempt to compile it for android was on a recent linux system (up to date archlinux). The libraries used were too recent to compile properly. I made a virtual machine, and set the repo to the Archlinux Archive, set to 2023-06-01. This way, I could have all the libs from the correct time period, avoiding compatibilities issues.

/etc/pacman.d/mirrorlist

Server=https://archive.archlinux.org/repos/2023/06/01/$repo/os/$arch

Then, I checkout the repo to the v2023.06.1 tag.

KOreader uses subrepositories, so I had to recursively checkout every subrepositories :

git checkout v2023.06.1
git submodule update --recursive

Fixing deps

I had to change the zlib url : base/thirdparty/zlib/CMakeLists.txt

56c56
<     URL http://gentoo.osuosl.org/distfiles/zlib-${ZLIB_VER}.tar.xz
---
>     URL https://github.com/madler/zlib/releases/download/v${ZLIB_VER}/zlib-${ZLIB_VER}.tar.xz

eINK support

There is a compatibility test embedded to check for drivers support. I found that the backlighting worked fine with the generic driver, but the display should use the rk30sdk

I suspect that the check I used in the diff below are specific to my own reader, I remember flashing it back in 2016 with (if I remember correctly) a non standard firmware

Below, the git diff :

diff --git a/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt b/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt
index 61cfc0c..497a4e7 100644
--- a/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt
+++ b/app/src/main/java/org/koreader/launcher/device/DeviceInfo.kt
@@ -36,6 +36,7 @@ object DeviceInfo {
         BOYUE_S62,
         BOYUE_T61,
         BOYUE_T62,
+        BOYUE_T63,
         BOYUE_T65S,
         BOYUE_T78D,
         BOYUE_T80D,
@@ -155,6 +156,7 @@ object DeviceInfo {
     private val BOYUE_S62: Boolean
     private val BOYUE_T61: Boolean
     private val BOYUE_T62: Boolean
+    private val BOYUE_T63: Boolean
     private val BOYUE_T65S: Boolean
     private val BOYUE_T78D: Boolean
     private val BOYUE_T80D: Boolean
@@ -249,7 +251,12 @@ object DeviceInfo {
         // Boyue T62
         BOYUE_T62 = (BOYUE
             && (PRODUCT.startsWith("t62") || MODEL.contentEquals("rk30sdk"))
             && DEVICE.startsWith("t62"))
+
+        // Boyue T63
+        BOYUE_T63 = (BOYUE
+            && (PRODUCT.startsWith("t63") || MODEL.contentEquals("rk30sdk"))
+            && DEVICE.contentEquals("inkbook"))

         // Boyue/JDRead T65S
         BOYUE_T65S = BOYUE && PRODUCT.contentEquals("t65s")
@@ -529,6 +536,7 @@ object DeviceInfo {
         deviceMap[EinkDevice.BOYUE_S62] = BOYUE_S62
         deviceMap[EinkDevice.BOYUE_T61] = BOYUE_T61
         deviceMap[EinkDevice.BOYUE_T62] = BOYUE_T62
+        deviceMap[EinkDevice.BOYUE_T63] = BOYUE_T63
         deviceMap[EinkDevice.BOYUE_T65S] = BOYUE_T65S
         deviceMap[EinkDevice.BOYUE_T78D] = BOYUE_T78D
         deviceMap[EinkDevice.BOYUE_T80D] = BOYUE_T80D
diff --git a/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt b/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt
index 986e946..af69e41 100644
--- a/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt
+++ b/app/src/main/java/org/koreader/launcher/device/EPDFactory.kt
@@ -19,6 +19,7 @@ object EPDFactory {
             return when (DeviceInfo.EINK) {
                 DeviceInfo.EinkDevice.BOYUE_T61,
                 DeviceInfo.EinkDevice.BOYUE_T62,
+                DeviceInfo.EinkDevice.BOYUE_T63,
                 DeviceInfo.EinkDevice.BOYUE_T80S,
                 DeviceInfo.EinkDevice.CREMA_0650L,
                 DeviceInfo.EinkDevice.ENERGY,

I based the change heavily on the T62 support which should be very close (previous gen from my device)

Romanian plugin

Romanian is a declension-heavy language; the article is suffixed, as the genitival marks. for example : “vârf” is “summit, peak” “vârful” is “the summit” “vârfului” is “summit’s” : “frigul vârfului” is “the summit’s cold”

This makes searching in a dictionnary quite annoying. Often, the word is in the dict, but if I’m not looking up an altered form, there is no return.

KOreader have a plugin framework to improve it. It uses lua.

I never wrote lua code, and wanted to try using ollama. Surprisingly, it gave me an almost working function at the first try ! I also based myself on the “japanese support” plugin, which gave me the architecture to work on. It leverage the LanguageSupport framework to enhance dict search

The plugin works by searching for common suffixes, removing them, and returning every word to be added to the dict search. It is quite simplistic, but it works correctly. One weird trick is that Lua doesnt know UTF-8, so the length is wrongly calculated, with diacritics counting for one character more

The first version used local root = word:sub(0, -#suffix), but this had the tendancies to eat more character than necessary. I settled on local root = word:gsub("(.-)(" .. suffix .. ")$", "%1") which works correctly. It captures everything before the suffix, therefore avoiding length errors due to encoding.

Here is the function code :

function Romana:extractRoots(word)
    -- Define a list of common Romanian suffixes to remove
    local suffixes = {
        "ul", "ei", "ui", "ai", "i", "e", "a", "tor",
        "ului",  "au", "lor", "rile", "uri", "urile"
    }
    logger.dbg("romana.koplugin: length of", word, "results:", #word)

    -- Try removing each suffix and check if the root exists in the dictionary
    local roots = {}
    for _, suffix in ipairs(suffixes) do
      logger.dbg("romana.koplugin: suffix of", word, "results:", suffix)
      if word:find(suffix) then
        logger.dbg("romana.koplugin: suffix found in ", word, " : ", suffix)
        local root = word:gsub("(.-)(" .. suffix .. ")$", "%1")
        table.insert(roots, root)
      end
    end

    return roots
end

On a side note, I was surprised because the local model I used qwen2.5-coder had some good suggestion relative to KOreader, like the plugin structure, and I think it even suggested using the LanguageSupport framework. While AI is not there yet to replace devs, I found it useful to bootstrap this project and as debbuging aid (a rubber duck of sorts)

backporting the dict sort

With the plugin working, I wanted to sort the results based on similarity and dictionnary preference. I found this pull request that implemented it. I backported the changes into my old version, fixed a few errors.

Nothing too remarkable here :)

Building and installing

It was the first time I built for android. I had to create a signing key to signed the package for installation. I dont see anything to relevant to say here except :

I then installed the package using adb. the adb logcat command was very useful to debug the package when working out the issues

Finished !

Thanks to this, I can now read my book in Romanian on my old eReader. Should I want to add something, I now have the tooling to improve the software.

I am currently learning Russian too, so I may have to write a russian plugin too :)

Dictionnaries search are working way better than when I was using QuickDic. KOreader supports more dictionnary format, the search interface is way better (I can recursively search the dict for words in a definition for example).

For an old machine (released in May 2016), this will make me keep using it until it stop working.

I find that Android devices are getting obsolete too quickly, generating e-waste and forcing use to change hardware because of software, even when the hardware is still perfectly functionning.

#Sysadmin #Ereader #Languages