KEPUB Output: Add an option to generate KEPUB files that have better text justification at the cost of gaps in highlighting when used on the Kobo. Fixes #2107778 [Native KEPUB conversion causing some justification issues](https://bugs.launchpad.net/calibre/+bug/2107778)

Note that this option also affects on-the-fly generation of KEPUB done
by the Kobo driver.
This commit is contained in:
Kovid Goyal 2025-05-06 13:31:52 +05:30
parent 6b67ed1e66
commit d2363c0c0a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 37 additions and 13 deletions

View File

@ -2347,7 +2347,10 @@ class KOBOTOUCH(KOBO):
return result return result
def _kepubify(self, path, name, mi) -> None: def _kepubify(self, path, name, mi) -> None:
from calibre.ebooks.conversion.config import load_defaults
from calibre.ebooks.oeb.polish.kepubify import kepubify_path, make_options from calibre.ebooks.oeb.polish.kepubify import kepubify_path, make_options
prefs = load_defaults('kepub_output')
prefer_justification = prefs.get('kepub_prefer_justification', False)
debug_print(f'Starting conversion of {mi.title} ({name}) to kepub') debug_print(f'Starting conversion of {mi.title} ({name}) to kepub')
opts = make_options( opts = make_options(
extra_css=self.extra_css or '', extra_css=self.extra_css or '',
@ -2359,6 +2362,7 @@ class KOBOTOUCH(KOBO):
hyphenation_limit_lines=self.get_pref('hyphenation_limit_lines'), hyphenation_limit_lines=self.get_pref('hyphenation_limit_lines'),
remove_at_page_rules=self.extra_css_options.get('has_atpage', False), remove_at_page_rules=self.extra_css_options.get('has_atpage', False),
remove_widows_and_orphans=self.extra_css_options.get('has_widows_orphans', False), remove_widows_and_orphans=self.extra_css_options.get('has_widows_orphans', False),
prefer_justification=prefer_justification,
) )
try: try:
kepubify_path(path, outpath=path, opts=opts, allow_overwrite=True) kepubify_path(path, outpath=path, opts=opts, allow_overwrite=True)

View File

@ -282,7 +282,7 @@ OPTIONS = {
'preserve_cover_aspect_ratio', 'epub_flatten', 'epub_version', 'epub_max_image_size',), 'preserve_cover_aspect_ratio', 'epub_flatten', 'epub_version', 'epub_max_image_size',),
'kepub': ( 'kepub': (
'dont_split_on_page_breaks', 'flow_size', 'kepub_max_image_size', 'dont_split_on_page_breaks', 'flow_size', 'kepub_max_image_size', 'kepub_prefer_justification',
'kepub_affect_hyphenation', 'kepub_disable_hyphenation', 'kepub_hyphenation_min_chars', 'kepub_affect_hyphenation', 'kepub_disable_hyphenation', 'kepub_hyphenation_min_chars',
'kepub_hyphenation_min_chars_before', 'kepub_hyphenation_min_chars_after', 'kepub_hyphenation_limit_lines', 'kepub_hyphenation_min_chars_before', 'kepub_hyphenation_min_chars_after', 'kepub_hyphenation_limit_lines',
), ),

View File

@ -611,6 +611,16 @@ class KEPUBOutput(OutputFormatPlugin):
help=max_image_size_help help=max_image_size_help
), ),
OptionRecommendation(name='kepub_prefer_justification', recommended_value=False,
help=_(
'The KEPUB renderer on the Kobo has a bug when text justification is turned on.'
' It will either not justify text properly or when highlighting there will be gaps'
' between neighboring highlighted parts of text. By default, calibre generates'
' KEPUB that avoid the highlighting gaps at the expense of worse text justification.'
' This option reverses that tradeoff. Use this option if you use justification when'
' reading on your Kobo device.'
)),
OptionRecommendation(name='kepub_affect_hyphenation', recommended_value=False, OptionRecommendation(name='kepub_affect_hyphenation', recommended_value=False,
help=_('Modify how hyphenation is performed for this book. Note that hyphenation' help=_('Modify how hyphenation is performed for this book. Note that hyphenation'
' does not perform well for all languages, as it depends on the dictionaries' ' does not perform well for all languages, as it depends on the dictionaries'
@ -653,6 +663,7 @@ class KEPUBOutput(OutputFormatPlugin):
hyphenation_min_chars_before=opts.kepub_hyphenation_min_chars_before, hyphenation_min_chars_before=opts.kepub_hyphenation_min_chars_before,
hyphenation_min_chars_after=opts.kepub_hyphenation_min_chars_after, hyphenation_min_chars_after=opts.kepub_hyphenation_min_chars_after,
hyphenation_limit_lines=opts.kepub_hyphenation_limit_lines, hyphenation_limit_lines=opts.kepub_hyphenation_limit_lines,
prefer_justification=opts.kepub_prefer_justification,
) )
kepubify_container(container, kopts) kepubify_container(container, kopts)
container.commit() container.commit()

View File

@ -609,6 +609,7 @@ def make_options(
hyphenation_min_chars_before: int = 3, hyphenation_min_chars_before: int = 3,
hyphenation_min_chars_after: int = 3, hyphenation_min_chars_after: int = 3,
hyphenation_limit_lines: int = 2, hyphenation_limit_lines: int = 2,
prefer_justification: bool = False,
remove_widows_and_orphans: bool | None = None, remove_widows_and_orphans: bool | None = None,
remove_at_page_rules: bool | None = None, remove_at_page_rules: bool | None = None,
@ -652,7 +653,7 @@ h1, h2, h3, h4, h5, h6, td {{
''' '''
return Options( return Options(
extra_css=extra_css, hyphenation_css=hyphen_css, remove_widows_and_orphans=remove_widows_and_orphans, extra_css=extra_css, hyphenation_css=hyphen_css, remove_widows_and_orphans=remove_widows_and_orphans,
remove_at_page_rules=remove_at_page_rules) remove_at_page_rules=remove_at_page_rules, prefer_justification=prefer_justification)
def profile(): def profile():

View File

@ -63,21 +63,21 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="opt_kepub_max_image_size"/> <widget class="QLineEdit" name="opt_kepub_max_image_size"/>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_kepub_affect_hyphenation"> <widget class="QCheckBox" name="opt_kepub_affect_hyphenation">
<property name="text"> <property name="text">
<string>Enable/disable &amp;hyphenation for this book</string> <string>Enable/disable &amp;hyphenation for this book</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<widget class="QCheckBox" name="opt_kepub_disable_hyphenation"> <widget class="QCheckBox" name="opt_kepub_disable_hyphenation">
<property name="text"> <property name="text">
<string>&amp;Prevent all hyphenation</string> <string>&amp;Prevent all hyphenation</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Hyphenation: &amp;minimum word length:</string> <string>Hyphenation: &amp;minimum word length:</string>
@ -87,7 +87,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="6" column="1">
<widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars"> <widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars">
<property name="specialValueText"> <property name="specialValueText">
<string>Disabled</string> <string>Disabled</string>
@ -97,7 +97,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Hyphenation: minimum characters &amp;before:</string> <string>Hyphenation: minimum characters &amp;before:</string>
@ -107,14 +107,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="7" column="1">
<widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars_before"> <widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars_before">
<property name="suffix"> <property name="suffix">
<string> characters</string> <string> characters</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Hyphenation: minimum characters &amp;after:</string> <string>Hyphenation: minimum characters &amp;after:</string>
@ -124,14 +124,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="8" column="1">
<widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars_after"> <widget class="QSpinBox" name="opt_kepub_hyphenation_min_chars_after">
<property name="suffix"> <property name="suffix">
<string> characters</string> <string> characters</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="9" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>Hyphenation: &amp;limit lines:</string> <string>Hyphenation: &amp;limit lines:</string>
@ -141,14 +141,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="9" column="1">
<widget class="QSpinBox" name="opt_kepub_hyphenation_limit_lines"> <widget class="QSpinBox" name="opt_kepub_hyphenation_limit_lines">
<property name="suffix"> <property name="suffix">
<string> lines</string> <string> lines</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="10" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -161,6 +161,13 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_kepub_prefer_justification">
<property name="text">
<string>Prefer improved text &amp;justification to proper highlighting</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -558,6 +558,7 @@ def kepub_output(container):
g.appendChild(checkbox('dont_split_on_page_breaks', _('Do not &split on page breaks'))) g.appendChild(checkbox('dont_split_on_page_breaks', _('Do not &split on page breaks')))
g.appendChild(int_spin('flow_size', _('Split files &larger than:'), unit='KB', max=1000000, step=20)) g.appendChild(int_spin('flow_size', _('Split files &larger than:'), unit='KB', max=1000000, step=20))
g.appendChild(lineedit('kepub_max_image_size', _('Shrink &images larger than:'))) g.appendChild(lineedit('kepub_max_image_size', _('Shrink &images larger than:')))
g.appendChild(lineedit('kepub_prefer_justification', _('Prefer improved text justification to proper highlighting')))
g.appendChild(checkbox('kepub_affect_hyphenation', _('Enable/disable &hyphenation for this book'))) g.appendChild(checkbox('kepub_affect_hyphenation', _('Enable/disable &hyphenation for this book')))
g.appendChild(checkbox('kepub_disable_hyphenation', _('&Prevent all hyphenation'))) g.appendChild(checkbox('kepub_disable_hyphenation', _('&Prevent all hyphenation')))
c = _('characters') c = _('characters')