Skip to content

Add a LVContainerTransform for interaction between koreader and crengine - #2477

Open
pawanjay176 wants to merge 2 commits into
koreader:masterfrom
pawanjay176:streaming-api
Open

Add a LVContainerTransform for interaction between koreader and crengine#2477
pawanjay176 wants to merge 2 commits into
koreader:masterfrom
pawanjay176:streaming-api

Conversation

@pawanjay176

@pawanjay176 pawanjay176 commented Jul 25, 2026

Copy link
Copy Markdown

Related to koreader/koreader#15649

Add a wrapper LVContainerTransform wrapper that allows koreader to provide replacement bytes for individual EPUB entries.

Exposes loadEpubWithEntryTransform function to be used in koreader. The transform object needs to provide the following interface:

{
  transformEntry = function(self, path) -- return replacement bytes, or nil to use the original entry
    end
}

Disclosure: Used AI assistance for the openTransformedEntry function. I don't have deep understanding of the lua C api.


This change is Reviewable

@poire-z

poire-z commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Can you ask your AI to add more comments, especially for the Lua stack interactions ?
One good documentation of mine, and it is really needed to understand years after how it works, can be seen at:

koreader-base/xtext.cpp

Lines 536 to 636 in 7aace42

// Get HB font data structures for font #num (create them and store them in the
// Lua font object, or get the previously created and stored ones)
// This must be a method of our XText object, as it uses the uservalue that has
// been associated with the userdata that is wrapping this XText instance.
xtext_hb_font_data * getHbFontData(int num) {
if ( num > MAX_FONT_NUM )
return NULL;
// This uses the stack for C <-> Lua interaction, but we should put this
// stack back in its original state, as it may carry additional arguments
// to the original function that was called.
int stack_orig_top = lua_gettop(m_L);
// The uservalue (the Lua font face_obj table) has been put at 1 on the stack
// by check_XText().
// Get the Lua font table for fallback font #num, by calling
// the Lua callback function: font.getFallbackFont(num).
lua_getfield(m_L, 1, XTEXT_LUA_FONT_GETFONT_CALLBACK_NAME);
lua_pushinteger(m_L, num);
lua_pcall(m_L, 1, 1, 0); // 1 argument, 1 returned value
if ( !lua_istable(m_L, -1) ) { // No #num font (we got "false")
lua_settop(m_L, stack_orig_top); // restore stack / drop our added work stuff
return NULL;
}
// We have a font, we'll be able to return something.
xtext_hb_font_data * hb_data;
// We got our font table. See if we already have the hb stuff stored
// as a userdata under the key '_hb_font_data'
lua_getfield(m_L, -1, XTEXT_LUA_HB_FONT_DATA_TABLE_KEY_NAME);
if ( lua_isuserdata(m_L, -1) ) {
// We do: just return the pointer to it (that we stored as the userdata)
hb_data = (xtext_hb_font_data *)luaL_checkudata(m_L, -1, XTEXT_HB_FONT_DATA_METATABLE_NAME);
lua_settop(m_L, stack_orig_top); // restore stack / drop our added work stuff
return hb_data;
}
lua_pop(m_L, 1); // remove nil
// Not previously stored: we have to create it and store it
// Get the 'ftsize' Freetype FFI wrapped object
lua_getfield(m_L, -1, "ftsize");
// printf("face type: %d %s\n", lua_type(m_L, -1), lua_typename(m_L, lua_type(m_L, -1)));
// We expect it to be a luajit ffi cdata, but the C API does not have a #define for
// that type. But it looks like its value is higher than the greatest LUA_T* type.
if ( lua_type(m_L, -1) <= LUA_TTHREAD ) {// Higher plain Lua datatype (lua.h)
luaL_typerror(m_L, -1, "cdata");
}
// Get the usable (for Harfbuzz) FT_Size object
FT_Size size = *(FT_Size *)lua_topointer(m_L, -1);
lua_pop(m_L, 1); // remove ftsize object
// Create a Lua userdata that will keep the reference to our hb_data
// (alloc/free of this userdata is managed by Lua, but not the cleanup
// of the Harfbuzz stuff allocated and stored in it. So, we have set
// to its metatable a __gc function, so it is called when the userdata
// is gc()'ed by Lua, so we can free these Harfbuzz structures).
hb_data = (xtext_hb_font_data *)lua_newuserdata(m_L, sizeof(xtext_hb_font_data));
luaL_getmetatable(m_L, XTEXT_HB_FONT_DATA_METATABLE_NAME);
lua_setmetatable(m_L, -2);
// Set this userdata as the '_hb_font_data' key of our Lua font table
lua_setfield(m_L, -2, XTEXT_LUA_HB_FONT_DATA_TABLE_KEY_NAME);
++*(int *)size->generic.data;
FT_Activate_Size(size);
hb_data->ft_size = size;
FT_Reference_Library((FT_Library)size->face->generic.data);
hb_data->hb_font = hb_ft_font_create_referenced(size->face);
// These flags should be sync'ed with freetype.lua FT_Load_Glyph_flags:
// hb_ft_font_set_load_flags(hb_data->hb_font, FT_LOAD_TARGET_LIGHT | FT_LOAD_FORCE_AUTOHINT);
// No hinting, as it would mess synthetized bold.
hb_ft_font_set_load_flags(hb_data->hb_font, FT_LOAD_TARGET_LIGHT | FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
hb_data->hb_buffer = hb_buffer_create();
hb_data->hb_features_nb = 0;
hb_data->hb_features = NULL;
// We can set what OTF features to use from Lua
lua_getfield(m_L, -1, "hb_features");
if ( lua_istable(m_L, -1) ) {
lua_pushnil(m_L); /* first key */
while ( lua_next(m_L, -2) != 0 ) {
if ( lua_isstring(m_L, -1) ) {
size_t len;
const char * feature = lua_tolstring(m_L, -1, &len);
// printf("hbfont feature: %s\n", feature);
hb_feature_t f;
if ( hb_feature_from_string(feature, len, &f) ) {
hb_data->hb_features_nb++;
hb_data->hb_features = (hb_feature_t*)realloc( hb_data->hb_features,
hb_data->hb_features_nb * sizeof(hb_feature_t) );
if ( hb_data->hb_features )
hb_data->hb_features[hb_data->hb_features_nb-1] = f;
}
}
lua_pop(m_L, 1); // remove fetched value, but keep key for next iteration
}
}
// printf("hbfont #features: %d\n", hb_data->hb_features_nb);
lua_settop(m_L, stack_orig_top); // restore stack / drop our added work stuff
return hb_data;
}

Comment thread cre.cpp
Comment on lines +481 to +493
virtual LVStreamRef OpenStream(const lChar32 * fname, lvopen_mode_t mode)
{
if (!fname)
return LVStreamRef();

if (mode == LVOM_READ) {
LVStreamRef transformed = openTransformedEntry(lString32(fname));
if (!transformed.isNull())
return transformed;
}

return _container->OpenStream(fname, mode);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd like it (and similar) as condensed as:

    virtual LVStreamRef OpenStream(const lChar32 * fname, lvopen_mode_t mode) {
        if (!fname)
            return LVStreamRef();
        if (mode == LVOM_READ) {
            LVStreamRef transformed = openTransformedEntry(lString32(fname));
            if (!transformed.isNull())
                return transformed;
        }
        return _container->OpenStream(fname, mode);
    }

Comment thread cre.cpp
ldomDocument *dom_doc;
} CreDocument;

class LVContainerTransform : public LVContainer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add some introduction comment, what it is for, that it is not used by KOReader but available to plugins that would like to do xyz, what it allows, etc...

Comment thread cre.cpp
Comment on lines +786 to +795
static int loadEpubWithEntryTransform(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
const char *file_name = luaL_checkstring(L, 2);
luaL_checktype(L, 3, LUA_TTABLE);
bool only_metadata = false;
if (lua_isboolean(L, 4)) {
only_metadata = lua_toboolean(L, 4);
}

lString32 file_name32 = LocalToUnicode(lString8(file_name));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add some introduction comment, what it is for, that it is not used by KOReader but available to plugins that would like to do xyz, what it allows, etc...
Document (with a comment on each line checking one arg) what the provided arguments are expected to be (as it's not used by KOReader, hard to guess what this table at 3 should be).

Comment thread cre.cpp
Comment on lines +808 to +810
lua_pushvalue(L, 3);
int transform_ref = luaL_ref(L, LUA_REGISTRYINDEX);
LVContainerRef container(new LVContainerTransform(L, transform_ref, archive));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is that our arg 3 ? Explain what it is and how it will be used.
Really, without comments, I can't understand any of the magic :)

@pawanjay176

Copy link
Copy Markdown
Author

Apologies, got busy at my day job. Will update these as soon as I can!

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