Skip to content

Expose txt_preformatted to UI#13392

Merged
hius07 merged 14 commits into
koreader:masterfrom
hius07:txt_preformatted
Mar 14, 2025
Merged

Expose txt_preformatted to UI#13392
hius07 merged 14 commits into
koreader:masterfrom
hius07:txt_preformatted

Conversation

@hius07

@hius07 hius07 commented Mar 10, 2025

Copy link
Copy Markdown
Member

To help chinese and cyrillic users who read a lot of txt files.

1


This change is Reviewable

function ReaderRolling:onCheckDomStyleCoherence()
if self.ui.document and self.ui.document:isBuiltDomStale() then
local has_bookmarks_warn_txt = ""
-- When using an older DOM version, bookmarks may break

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.

Comments killer, again !

Comment on lines +316 to +319
function ReaderRolling:showReloadConfirmBox(has_bookmarks_warn)
local has_bookmarks_warn_txt = has_bookmarks_warn
and _("\nNote that this change in styles may render your bookmarks or highlights no more valid.\nIf some of them do not show anymore, you can just revert the change you just made to have them shown again.\n\n")
or ""

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.

Dunno if this flag could be used anywhere - but this text makes more sense in its original place than here in a supposedly generic showReloadConfirmBox, which could accept any text rather than just a boolean.

})
table.insert(style_table, {
text_func = function()
return _("Preformatted text in TXT files") .. (G_reader_settings:has("txt_preformatted") and "" or " ★")

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.

What does this star icon really means ?
Below, it looks like G_reader_settings:has("txt_preformatted") can only be 0 or nil - so it indicates only when it is set globally to 0 - and not when I reset it to nil and the global ends up being 1 ?
Feels a bit odd that it would show only when disabled - meaning the default state is on, which has been our historical default, but users don't know our history - and future users would feel that odd.
Do we have that kind of strange behaviour elsewhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As usual, the checkbox shows the status of a setting per document, and the star - the status for new documents.

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.

But the star won't show if it's enabled (our default) for all document, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

By default there is no txt_preformatted in G_reader_settings and the star is shown.

Comment on lines +323 to +326
callback = function()
self.txt_preformatted = self.txt_preformatted == 1 and 0 or 1
self.ui.doc_settings:saveSetting("txt_preformatted", self.txt_preformatted)
self.ui.rolling:showReloadConfirmBox()

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.

Do you need here to explicitely show that ConfirmBox ?
Haven't looked, but how does toggling this affect the DOM/HTML build from the document ?
Does a change in txt_preformatted doesn't by itself cause isBuiltDomStale and implicitely a "style has changed in such a way" warning ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Trying to change txt_preformatted on the fly causes segmentation fault.

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.

So tell us :), and I may look at it.
And at least, mention that in a comment, so we remember it and understand why the ConfirmBox (and so that, later, you may have the joy of killing that comment :))

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.

For reference:

Thread 1 "luajit" received signal SIGSEGV, Segmentation fault.
0x00007fffe036ade1 in LDOMNameIdMap::findItem (this=this@entry=0x55555716e9f8, name=name@entry=0x5555571b3e60 U"FictionBook") at thirdparty/kpvcrlib/crengine/crengine/src/lstridmap.cpp:223
223         r = lStr_cmp( name, m_by_name[c]->value.c_str() );
(gdb) bt
#0  0x00007fffe036ade1 in LDOMNameIdMap::findItem (this=this@entry=0x55555716e9f8, name=name@entry=0x5555571b3e60 U"FictionBook") at thirdparty/kpvcrlib/crengine/crengine/src/lstridmap.cpp:223
#1  0x00007fffe02e8552 in lxmlDocBase::getElementNameIndex (this=this@entry=0x55555715e710, name=0x5555571b3e60 U"FictionBook") at thirdparty/kpvcrlib/crengine/crengine/src/lvtinydom.cpp:3869
#2  0x00007fffe02fa596 in ldomDocument::createXPointerV1 (this=0x55555715e710, baseNode=<optimized out>, xPointerStr=...) at thirdparty/kpvcrlib/crengine/crengine/src/lvtinydom.cpp:10588
#3  0x00007fffe03233b1 in ldomDocument::createXPointer (this=this@entry=0x55555715e710, baseNode=<optimized out>, xPointerStr=...)
    at thirdparty/kpvcrlib/crengine/crengine/src/../include/lvtinydom.h:2789
#4  0x00007fffe02faaf0 in ldomDocument::createXPointer (this=this@entry=0x55555715e710, xPointerStr=...) at thirdparty/kpvcrlib/crengine/crengine/src/lvtinydom.cpp:9532
#5  0x00007fffe0255350 in getPageFromXPointer (L=0x7ffff7cb6380) at build/x86_64-linux-gnu-debug/cmake/koreader/build/cre.cpp:939
#6  0x00005555555b8396 in lj_BC_FUNCC () at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/buildvm_x86.dasc:857
#7  0x00005555555af01c in lua_pcall (L=L@entry=0x7ffff7cb6380, nargs=nargs@entry=1, nresults=-1, errfunc=errfunc@entry=2)
    at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/lj_api.c:1151
#8  0x000055555555c9a6 in docall (L=L@entry=0x7ffff7cb6380, narg=narg@entry=1, clear=clear@entry=0) at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/luajit.c:122
#9  0x000055555555d221 in handle_script (L=L@entry=0x7ffff7cb6380, argx=argx@entry=0x7fffffffd6a0) at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/luajit.c:292
#10 0x000055555555d904 in pmain (L=0x7ffff7cb6380) at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/luajit.c:550
#11 0x00005555555b8396 in lj_BC_FUNCC () at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/buildvm_x86.dasc:857
#12 0x00005555555af1c0 in lua_cpcall (L=L@entry=0x7ffff7cb6380, func=func@entry=0x55555555d7af <pmain>, ud=ud@entry=0x0)
    at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/lj_api.c:1179
#13 0x000055555555d9c4 in main (argc=3, argv=0x7fffffffd698) at build/x86_64-linux-gnu-debug/thirdparty/luajit/source/src/luajit.c:581
(gdb) p c
$2 = 54
(gdb) p name
$3 = (const lChar32 *) 0x5555571b3e60 U"FictionBook"
(gdb) p m_by_name[54]
Cannot access memory at address 0xe0409000101b3
(gdb) p m_by_name[1]
Cannot access memory at address 0xe04090001000b

No idea yet how/why this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So tell us

You are so busy at the moment, didn't want to bother you with the C code.
I'll add the comment.

})
table.insert(style_table, {
text_func = function()
return _("Preformatted text in TXT files") .. (G_reader_settings:has("txt_preformatted") and "" or " ★")

@Frenzie Frenzie Mar 10, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just thinking aloud a bit.

Preformatted text means spaces and such are kept rather than collapsed, imo this is what people expect as "normal" TXT behavior. Preformatted as a term primarily makes sense in the context of HTML, LaTeX or some such where you tell the program to stop doing things like collapsing spaces but just to render it as is instead. Additionally it's a bit of technical jargon, maybe a simple Render text as is would do the trick.

Given the above would it make more sense to revert it, for example as Typeset TXT files? Although that's not quite right.

Other potentially relevant terms include fixed width.

That also raises the question as to whether the "special" behavior's actually desired or if the users in question just want a regular non-fixed-width font.

@hius07 hius07 Mar 10, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agree that "preformatted text" is not very clear.

I remember the discussion here that the main purpose of txt files (in the Western area) is the ASCII art.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ASCII art is a bit too specific really, even just some indentation or a basic table is mangled by the special behavior iirc.

But yes, here's a common type of text file:
DOSnDonts-017.txt

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It can be "Apply style to TXT files" with default unchecked.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not really about style though. :-) Using a regular non-fixed-width font doesn't mess with it the way the crengine special behavior does.

@poire-z

poire-z commented Mar 10, 2025

Copy link
Copy Markdown
Contributor

A few random notes having a look at "txt preformatted" handling in crengine:

The alternative to "preformatted" is actually not just non-preformatted, it's auto-magically-detect-txt-format-layout (that I mentionned elsewhere):

/// text format import options
typedef enum {
    txt_format_pre,  // no formatting, leave lines as is
    txt_format_auto  // autodetect format
} txt_format_t;

There's a lot of history and magic (and possibly false decisions on some books) in that bit of code, that I promise I will never look at :)

Using one or the other of these formats, it is the only thing that I know of in crengine that makes and keeps 2 distinct cache files:

 3763200 Mar 10 12:34 Mfu.txt.6d3fd46b.1.cr3   1= preformatted, and with all the other-than-txt formats
 3645440 Mar 10 12:35 Mfu.txt.6d3fd46b.0.cr3   0 = auto

But even if this has (or should, haven't looked at it yet) effect only on txt files, the flag is used also with other formats to end up in the cache filename, even with EPUBs:

193fe12a9 (Vadim Lopatin      2010-03-29 23:49:18 +0400)|int tinyNodeCollection::getPersistenceFlags()
7fb1e1d07 (Vadim Lopatin      2009-07-29 12:20:10 +0000)|{
85e159f4e (Vadim Lopatin      2011-12-21 17:19:12 +0400)|    int format = 2; //getProps()->getIntDef(DOC_PROP_FILE_FORMAT, 0);
7fb1e1d07 (Vadim Lopatin      2009-07-29 12:20:10 +0000)|    int flag = ( format==2 && getDocFlag(DOC_FLAG_PREFORMATTED_TEXT) ) ? 1 : 0;
85e159f4e (Vadim Lopatin      2011-12-21 17:19:12 +0400)|    CRLog::trace("getPersistenceFlags() returned %d", flag);
7fb1e1d07 (Vadim Lopatin      2009-07-29 12:20:10 +0000)|    return flag;
7fb1e1d07 (Vadim Lopatin      2009-07-29 12:20:10 +0000)|}

so I end up with (same size, different content):

556032 Feb 20 20:24 Empire_byzantin.FR.epub.c75f66b2.1.cr3
556032 Mar 10 12:43 Empire_byzantin.FR.epub.c75f66b2.0.cr3

and these caches files will be rotting around (I remember we store one in the metatada.epub.lua and that we clean it on occasion).
Not sure about the reasons for the 2011 change above - I think with our "isBuiltDomStaled" evolution and koreader's ConfirmBox, we would handled the situation correctly without that 2-cache files hack.

So, I don't really know if we should introduce the above issues with enabling toggling this crengine feature.
But if we do, I think:

  • it should always feed crengine with 1 on other formats than txt, even if the global is 0 (I think I don't care about the duplicated lingering .0/.1 cache files for txt files, rather have that that lots of kludge to handle that fact)
  • The menu item name should not just be about pre-formatted or "apply style", but about txt format/layout auto-detection. (And the day we get our first issue about bad txt autodetection, I'll remember you :))

@hius07

hius07 commented Mar 10, 2025

Copy link
Copy Markdown
Member Author

txt_preformatted is always 1 for other types. The menu item is not shown.

Default state for TXT:

2

@poire-z poire-z left a comment

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.

Some suggested comments to keep track of what I understand (or failed to understand :/).

end
end

function ReaderRolling:showReloadConfirmBox(warn_txt)

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.

May be showSuggestReloadConfirmBox() ? as it has some hardcoded message in that sense, it's not a generic reload document thingie.

or G_reader_settings:readSetting("txt_preformatted")
or 1
if self.ui.document.is_txt then
-- default to disable TXT formatting as it does more harm than good

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.

-- Default to no fancy detection and formatting, leave lines as is

or G_reader_settings:readSetting("txt_preformatted")
or 1
else
-- for other formats, it affects crengine cache

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.

-- For other formats than txt, we should keep this setting fixed or it could create multiple cache files

Comment on lines +332 to +333
-- setting txt_preformatted for the opened document causes segfault, hence reload
self.ui.rolling:showReloadConfirmBox()

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.

-- Calling document:setTxtPreFormatted() here could cause a segfault (there is something
-- really fishy about its handling, like bits of partial rerenderings happening while it is
-- disabled...). It's safer to just not notify crengine, and propose the user to reload the
-- document and restart from a sane state.

There's something really fishy I can't figure out... and won't have time, so fine with reloading the document and pretend there's no fish :)

sub_item_table = obsoleted_table,
separator = true,
})
if self.ui.document.is_txt then

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.

Thank you for that - and not showing this hard to explain item to people who don't read TXT files.
Might be worth having a help_text though. Quick proposal (possibly not accurate):

When enabled, the rendering engine will use heuristics (text wrapped under some max line length, single or double blank lines marking paragraphs, indentation or centered text considered heading...) to guess how to better render the text.
When disabled, the text is rendered as is in a monospace font, which may be best if it has some ASCII art drawing or format things with spaces.

@hius07

hius07 commented Mar 11, 2025

Copy link
Copy Markdown
Member Author

Added all comments, except help text because long-pressing is reserved to make it default without additional ConfirmBox.

Asking @offset-torque to add a short description to the "TOP MENU → Style" chapter of the guide:

Auto-detect TXT files layout
When enabled, the rendering engine will use heuristics (text wrapped under some max line length, single or double blank lines marking paragraphs, indentation or centered text considered heading...) to guess how to better render the text.
When disabled, the text is rendered as is in a monospace font, which may be best if it has some ASCII art drawing or format things with spaces.

This menu item is visible when a txt file is opened only.

@poire-z

poire-z commented Mar 11, 2025

Copy link
Copy Markdown
Contributor

Don't we have somewhere some long-press ConfirmBox with some additional info in its text - where we could put this text ?

@hius07

hius07 commented Mar 11, 2025

Copy link
Copy Markdown
Member Author

Not yet, it requires MultiConfirmBox.

@offset-torque

Copy link
Copy Markdown

I am adding, thanks for already writing the text. So the aim is to make txt files more readable by formatting it a bit? I guess most of the time users would want this enabled, unless there is formatting they want to keep as you said.

@Frenzie

Frenzie commented Mar 11, 2025

Copy link
Copy Markdown
Member

Imo it absolutely destroys any and all text files I've ever tried and you never want it enabled.

@offset-torque

Copy link
Copy Markdown

Imo it absolutely destroys any and all text files I've ever tried and you never want it enabled.

Soo let's complete the sentence:

We have a "Auto-detect TXT files layout" which you can enable if you want to...

There should be a benefit, right? Otherwise why this effort.

@Frenzie

Frenzie commented Mar 11, 2025

Copy link
Copy Markdown
Member

There are aspects of it that are okay, a bit like Markdown. It just does… more. And perhaps somehow not enough at the same time. :-) Though mind, there's the "obsolete" txt.css that may or may not look a tiny bit better in some cases.

What I think people want is a regular non-fixed-width font option, without completely collapsing spaces and such. (And some of the stylistic replacements can be overly enthusiastic, though I can't find an example right now.) It would break ASCII art, but it wouldn't break the TXT file.

@mergen3107

Copy link
Copy Markdown
Contributor

Thanks, @Frenzie! Cool illustration.

So basically this PR is to support quite old format of books: there are in txt without any formatting involved. If I remember it correctly, this was done to have very low requirements for the reading apps back in the days of PDAs - personal digital assistants in early 2000s. I remember reading a book or two on some HTC mobile phones :D

Those are polar opposite of text files designed for monotype console fonts - where all those fancy graphics spelled out by symbols are possible.

My 2 cents is that KOReader is mainly a reader - I presume books as in literature, not coding text files and such :D
Thus, I am inclined to have this option enabled by default.

I only read one book in txt and I remember making a style tweak to make it bearable to read. I will share it here later

@Frenzie

Frenzie commented Mar 11, 2025

Copy link
Copy Markdown
Member

The terrible effect on books is why I disabled this in the first place, but for the record the above newsletter full of articles meant for reading was published mere days ago.

@Frenzie

Frenzie commented Mar 11, 2025

Copy link
Copy Markdown
Member

Incidentally, if you try to select the title that was too embarrassed to show up you get a crash.

03/11/25-22:12:05 DEBUG CreDocument: get text range {
  pos0 = "/FictionBook/body/section[5]/empty-line[1].0",
  pos1 = "/FictionBook/body/section[5]/empty-line[3].0",
  text = ""
} --[[table: 0x7536fa9d9700]] 
./luajit: frontend/document/credocument.lua:705: attempt to get length of local 'word_boxes' (a nil value)
stack traceback:
        frontend/document/credocument.lua:705: in function 'func'
        frontend/document/credocument.lua:1924: in function 'getScreenBoxesFromPositions'
        frontend/document/credocument.lua:691: in function 'getTextFromPositions'
        frontend/apps/reader/modules/readerhighlight.lua:1855: in function 'handler'
        frontend/ui/widget/container/inputcontainer.lua:258: in function 'handleEvent'
        frontend/ui/uimanager.lua:896: in function 'sendEvent'
        frontend/ui/uimanager.lua:53: in function '__default__'
        frontend/ui/uimanager.lua:1423: in function 'handleInputEvent'
        frontend/ui/uimanager.lua:1523: in function 'handleInput'
        frontend/ui/uimanager.lua:1567: in function 'run'
        reader.lua:280: in main chunk
        [C]: at 0x5756bf3ef82a
make: *** [make/emulator.mk:20: run] Error 1

Edit: it's probably self-evident, but here's the document: https://www.gutenberg.org/cache/epub/45839/pg45839.txt

@Frenzie

Frenzie commented Mar 11, 2025

Copy link
Copy Markdown
Member

To be abundantly clear, I've marked all the spots that are completely missing (x), completely broken (o) or incorrectly positioned (_).

@hius07

hius07 commented Mar 12, 2025

Copy link
Copy Markdown
Member Author

Can reproduce in one place of the book only

03/12/25-09:45:47 DEBUG CreDocument: get text range {
  pos0 = "/FictionBook/body/section[5]/title[1]/p/text().2",
  pos1 = "/FictionBook/body/section[5]/empty-line[1].0",
  text = ""
} --[[table: 0x42accb40]] 
./luajit: frontend/document/credocument.lua:705: attempt to get length of local 'word_boxes' (a nil value)
stack traceback:
	frontend/document/credocument.lua:705: in function 'func'
	frontend/document/credocument.lua:1924: in function 'getScreenBoxesFromPositions'
	frontend/document/credocument.lua:691: in function 'getTextFromPositions'
	frontend/apps/reader/modules/readerhighlight.lua:1853: in function 'handler'
	frontend/ui/widget/container/inputcontainer.lua:258: in function 'handleEvent'
	frontend/ui/uimanager.lua:896: in function 'sendEvent'
	frontend/ui/uimanager.lua:53: in function '__default__'
	frontend/ui/uimanager.lua:1423: in function 'handleInputEvent'
	frontend/ui/uimanager.lua:1523: in function 'handleInput'
	frontend/ui/uimanager.lua:1567: in function 'run'
	./reader.lua:280: in main chunk
	[C]: at 0x00013e85
lipc-wait-event exited normally with status: 0

Strange xpointer /FictionBook/body/section[5]/empty-line[1].0.

@hius07

hius07 commented Mar 12, 2025

Copy link
Copy Markdown
Member Author

To be abundantly clear, I've marked all the spots that are completely missing (x), completely broken (o) or incorrectly positioned (_).

With txt.css it looks differently but broken as well.

@poire-z

poire-z commented Mar 12, 2025

Copy link
Copy Markdown
Contributor

Strange xpointer /FictionBook/body/section[5]/empty-line[1].0.

I guess it's allowed to xpoint to an empty element.
(Reminded me to a recent fix koreader/crengine@2db70cd4f6 for <br><br>, but it would feel more related if you had empty-line[1].1 - and I guess the issue is in frontend, not in crengine.)

With txt.css it looks differently but broken as well.

txt.css is old and crap (a clone of fb2.css with may be or not old stuff not removed or new fb2.css stuff not added).
Given that crengine builds a /FictionBook/body/ DOM (and not a <body> or <DocFragment> one), any fancy tags created by the autoformat code (your <section>, maybe headings would be created as <section><title>) would work better with fb2.css.

But maybe you're expecting too much from this old code from 15 years ago that we haven't visisted since, which maybe lag and will fail will all the work we've done since :/

I'd really like to see this autoformat stuff do some magic with some specific txt file where it would detect things with success and really do better than preformatted.

A style tweak with pre { font-family: inherit !important } may have work just as fine to kill the monospace font :) #13349 (comment)

@Frenzie

Frenzie commented Mar 12, 2025

Copy link
Copy Markdown
Member

A style tweak with pre { font-family: inherit !important } may have work just as fine to kill the monospace font :) #13349 (comment)

I wrote much the same thing back in 2015. #1607 (comment)

In the end I kept it with the fixed width font by default for better compatibility.

@hius07

hius07 commented Mar 12, 2025

Copy link
Copy Markdown
Member Author

I think we can safeguard CreDocument:getScreenBoxesFromPositions() to avoid crashes, keep txt_preformatted on by default, and let users do their choice.
(Just do not highlight as we cannot highlight without sboxes)

@Frenzie

Frenzie commented Mar 12, 2025

Copy link
Copy Markdown
Member

A choice between working properly and completely broken is not worth having. What specifically is it that these Chinese and Cyrillic users are hoping to get out of this?

@Frenzie

Frenzie commented Mar 12, 2025

Copy link
Copy Markdown
Member

fb2.css indeed performs better, because the other one would eat <title> elements and possibly others. Mind, I consider the killing of spaces and tabs an act of hostility, but at least it's not actively broken then.

Comment thread frontend/apps/reader/modules/readerrolling.lua Outdated
Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>

local old_text = self.selected_text and self.selected_text.text
self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.holdpan_pos)
if self.selected_text and self.selected_text.sboxes and #self.selected_text.sboxes == 0 then

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.

A little comment about when this can happen ?

Comment thread frontend/apps/reader/readerui.lua Outdated
end
end
provider.is_fb2 = file_type:sub(1, 2) == "fb"
provider.is_txt = file_type == "txt" or nil

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 this or nil really needed ? (and why is_fb2 is fine with false and is_txt not ?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To save a couple of bytes of memory in 99.9% of cases. May be more, says ChatGPT.

In Lua, a boolean variable (true or false) typically takes 1 byte of memory for storage. However, due to Lua's internal memory management and the way Lua handles values dynamically, the actual memory consumption may vary depending on the Lua implementation and the underlying system architecture.

Lua represents all values as tagged variants (also known as tagged unions), meaning a boolean is stored within a structure that includes type information. If you are using a standard Lua implementation (such as PUC-Rio Lua), the internal representation of a boolean might take more than 1 byte due to memory alignment and Lua’s internal data structures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lua as such isn't terribly relevant. But without checking I wouldn't know what LuaJIT does here specifically. :-)

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.

It's one per document, and so very little compared to everything else :)
(Also, I'm not certain looking up the key is_txt in the provider table is cheaper when it doesn't exist - need to iterate all keys to see it's not there - than just finding it and stopping the iteration).

But I'm more interested in readability and consistency than any kind of microoptimisations (and given our large code base and the kind of software this is (a Reader where we are idle most of the time), I think readability and consistency matter most.
Consistency being that if you add it for is_txt, it should be there for is_fb2 in the line just above - so any reader of this code should not wonder why there are different, and if there are differents there may be a reason, and wtf is the reason ?! - like I did :)

@mergen3107

mergen3107 commented Mar 12, 2025

Copy link
Copy Markdown
Contributor

Works great!
I found what I was doing in Style Tweaks before with one txt book I had:

  • Ignore vertical margins
  • Justify all elements
  • Ignore publisher font families
  • Ignore publisher font sizes

With this PR I just enabled it once and it all worked!
I now enabled it by default. Thanks, @hius07 !

@offset-torque

Copy link
Copy Markdown

I see this is kind of a hot topic now so benefits are debatable I guess. But considering mergen3107's observation above, probably I can say something like this in the guide (open to corrections):

If you have books in .txt format, you can try enabling this option. In some cases it can improve the rendering and make the text more "book like".

@hius07 hius07 merged commit 475280a into koreader:master Mar 14, 2025
@hius07 hius07 deleted the txt_preformatted branch March 14, 2025 06:04
@hius07 hius07 added this to the 2025.03 milestone Mar 14, 2025
@Frenzie

Frenzie commented Mar 14, 2025

Copy link
Copy Markdown
Member

I think you accidentally didn't squash. No biggie, but letting you know in case you didn't notice 'cause it's a bit messy.

@hius07

hius07 commented Mar 14, 2025

Copy link
Copy Markdown
Member Author

Thanks, the default button has changed in the web interface, it was "Squash and merge" previously.

@dmalinovsky

Copy link
Copy Markdown
Contributor

@hius07, thanks a lot for this change. Now I can read TXT files in KOReader as well instead of the built-in Pocketbook reader.

@hius07

hius07 commented Mar 17, 2025

Copy link
Copy Markdown
Member Author

Good to know, thanks for the feedback.

@poire-z

poire-z commented Jan 12, 2026

Copy link
Copy Markdown
Contributor

@hius07 : do you remember why the need for this: ?
readerhighlight: stop highlighting when sboxes are absent

self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.holdpan_pos)
if self.selected_text and self.selected_text.sboxes and #self.selected_text.sboxes == 0 then
-- abort highlighting if crengine doesn't provide sboxes for current positions
-- may happen in TXT files with disabled txt_preformatted
self:clear()
return true
end

Been looking at #12135 again, and I often when testing (on some EPUB, haven't tried a TXT) and possibly moving back my cursor/finger to where it was when I started the selection, I so get an empty selection while I'm still panning - so I'll possibly be continuing my selection to get something non-empty when I release it.
Your commit make it so that as soon as, while still panning, we get to have an empty selection, the whole process is abandonned (self.clear()), so later onHoldPan and onHoldRelease as nothing to work on.

I feel that in this onHoldPan, if we get empty sboxes, we should just ignore/do nothing (highlight nothing): on the next onHoldPan we may get something. It's in onHoldRelease that we may want to abord if the sboxes are empty/there is no content selected to highglight or work with.

@hius07

hius07 commented Jan 12, 2026

Copy link
Copy Markdown
Member Author

we get to have an empty selection

Empty selection, when selected_text is not nil, means #sboxes is 1 (not 0). It's a zero sbox (x,y,h,w all 0). So the condition is not met and the process is not aborted.
I cannot abort highlighting in epub by panning back to the start point. Can you?

@poire-z

poire-z commented Jan 12, 2026

Copy link
Copy Markdown
Contributor

Yes, I can - at least with the changes I made - which may have issues, that I may fix - but I think this should not get in my way :).
I'm targetting some precise pixel to fight edge cases, with the mouse (so, possibly more do'able than with fingers on a device).
Here, targeting the boundary after a "crengine word" (which includes its folloxing space) and the blank space before the next word (added by text justification, not part of any "crengine word"), moving around and trying to get back on this same pixel:

image

I get:
(the lines without a date/DEBUG are printf from crengine):

01/12/26-19:01:05 DEBUG hold position in page "0x0+144+324"
looking for word
word 0/3, len=11 indice=10 (103 < 0 + 105
getTextFromPositions
looking for word
word 0/3, len=11 indice=10 (103 < 0 + 105
looking for word
word 0/3, len=11 indice=10 (103 < 0 + 105
 before adjustXPointer start /body/DocFragment/body/div/p[80]/text()[1].10
 before adjustXPointer end   /body/DocFragment/body/div/p[80]/text()[1].10
  after adjustXPointer start /body/DocFragment/body/div/p[80]/text()[1].10
  after adjustXPointer end   /body/DocFragment/body/div/p[80]/text()[1].11
01/12/26-19:01:05 DEBUG CreDocument: get text range {
  pos0 = "/body/DocFragment/body/div/p[80]/text()[1].2",
  pos1 = "/body/DocFragment/body/div/p[80]/text()[1].11",
  text = "économie "
} --[[table: 0x7f85b40e0d80]]
01/12/26-19:01:05 DEBUG selected word: {
  page = 108,
  pos0 = "/body/DocFragment/body/div/p[80]/text()[1].2",
  pos1 = "/body/DocFragment/body/div/p[80]/text()[1].11",
  sbox = "91x26+55+307",
  word = "économie "
} --[[table: 0x7f85b40e0d00]]
looking for word
word 0/3, len=11 indice=10 (103 < 0 + 105

(moving around while hold'ing)

01/12/26-19:01:07 DEBUG holdpan position in page "0x0+173+319"
getTextFromPositions
looking for word
word 0/3, len=11 indice=10 (103 < 0 + 105
looking for word
word 0/3, len=11 indice=11 (132 < 0 + 0
 before adjustXPointer start /body/DocFragment/body/div/p[80]/text()[1].10
 before adjustXPointer end   /body/DocFragment/body/div/p[80]/text()[1].11
x<left
  after adjustXPointer start /body/DocFragment/body/div/p[80]/text()[1].10
  after adjustXPointer end   /body/DocFragment/body/div/p[80]/text()[1].11
01/12/26-19:01:07 DEBUG CreDocument: get text range {
  pos0 = "/body/DocFragment/body/div/p[80]/text()[1].10",
  pos1 = "/body/DocFragment/body/div/p[80]/text()[1].11",
  text = " "
} --[[table: 0x7f85b40b4490]]
01/12/26-19:01:07 WARN  NO SBOXES {
  pos0 = "/body/DocFragment/body/div/p[80]/text()[1].10",
  pos1 = "/body/DocFragment/body/div/p[80]/text()[1].11",
  sboxes = {} --[[table: 0x7f85b40b5a58]],
  text = " "
} --[[table: 0x7f85b40b5aa0]] {} --[[table: 0x7f85b40b5a58]] 0
01/12/26-19:01:07 DEBUG clear selection

I think it might be when the selected text (between the 2 xpointers I'm trying to make better) is a space (maybe it's something that never happened previously, but it feels that (even it I later feel it might be a bug and that I should fix it) it might be a legitimate outcome that frontend should not just give up and abort the current selection, but only deal with it - possibly ignoring the selection - when onHoldRelease.

Why did you feel the need to add that in onHoldPan ? What kind of issue did you have before adding it ?

@hius07

hius07 commented Jan 12, 2026

Copy link
Copy Markdown
Member Author

I don't remember exactly, sorry. Some txt files caused problems, maybe my solution was not perfect.
Your logic looks correct, let's move handling of this case to onHoldRelease.

@hius07

hius07 commented Jan 12, 2026

Copy link
Copy Markdown
Member Author
01/12/26-19:01:07 WARN  NO SBOXES {
  pos0 = "/body/DocFragment/body/div/p[80]/text()[1].10",
  pos1 = "/body/DocFragment/body/div/p[80]/text()[1].11",
  sboxes = {} --[[table: 0x7f85b40b5a58]],
  text = " "

Is this a new behavior?
We cannot highlight a single space currently, right?

@hius07

hius07 commented Jan 12, 2026

Copy link
Copy Markdown
Member Author

But even when highlighting a single space, shouldn't sboxes be not empty?

@poire-z

poire-z commented Jan 12, 2026

Copy link
Copy Markdown
Contributor

Is this a new behavior?

Possibly, dunno. It's happening with my current work in progress, it's possible that when I'm done they will keep happening, or that they won't - can't say yet :)

We cannot highlight a single space currently, right?

I dunno. Logically, we can have a pair of xpointers pointing to a single (or multiple) space. May be frontend or base/cre.cpp with it's extension to word start/end avoid this.
May be onHoldRelease will/should discard the highlight if it's a space (like you were doing in onHoldPan).

(
About selecting a space, things are already strange (here on v2024.07) when you move the boundaries of a word to get them on the space after:
image
image
image
image
(^the xpointers contains only that single space (re-using the same button to move the start again has no effect), but the boxes somehow include the letter after...)
image
)

But even when highlighting a single space, shouldn't sboxes be not empty?

I dunno, it might be that getSegmentedRects tries to ignore them (so boxes adjust to the words, so we don't get blank on the sides, which would be ugly). May be this has some strange effect (like the above) when the selection happens to only be a space.

@poire-z

poire-z commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

I don't remember exactly, sorry. Some txt files caused problems, maybe my solution was not perfect.
Your logic looks correct, let's move handling of this case to onHoldRelease.

I removed the selection empty check from onHoldPan in the 2nd commit of #14979 .
Not sure how/where to check for that in onHoldRelease, and if it's really needed. Maybe let's do nothing and see ?
Once it is merged, can you try again playing with txt documents and see if there is any issue like the one you have fixed in a commit from this PR ?

@hius07

hius07 commented Feb 14, 2026

Copy link
Copy Markdown
Member Author

Okay

0xstillb pushed a commit to 0xstillb/koreader-thai that referenced this pull request May 9, 2026
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.

6 participants