Hyphenation: add custom hyphenation rules (#7746)

The hyphenation of a word can be changed from its default
by long pressing for 3 seconds and selecting 'Hyphenate'.
These overrides are stored in a per-language file, i.e:
koreader/settings/user-German.hyph.
This commit is contained in:
zwim
2021-05-31 20:34:26 +02:00
committed by GitHub
parent b30e366ccd
commit f25da5d0d5
39 changed files with 355 additions and 256 deletions

View File

@@ -800,7 +800,7 @@ function ReaderDictionary:startSdcv(word, dict_names, fuzzy_search)
-- dummy results
final_results = {
{
dict = _("Not available"),
dict = "",
word = word,
definition = lookup_cancelled and _("Dictionary lookup interrupted.") or _("No results."),
no_result = true,
@@ -849,22 +849,6 @@ function ReaderDictionary:stardictLookup(word, dict_names, fuzzy_search, box, li
return
end
-- If the user disabled all the dictionaries, go away.
if dict_names and #dict_names == 0 then
-- Dummy result
local nope = {
{
dict = _("Not available"),
word = word,
definition = _("There are no enabled dictionaries.\nPlease check the 'Dictionary settings' menu."),
no_result = true,
lookup_cancelled = false,
}
}
self:showDict(word, nope, box, link)
return
end
self:showLookupInfo(word, self.lookup_msg_delay)
self._lookup_start_tv = UIManager:getTime()

View File

@@ -209,7 +209,7 @@ function ReaderFont:onSetFontSize(new_size)
self.font_size = new_size
self.ui.document:setFontSize(Screen:scaleBySize(new_size))
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Font size set to: %1."), self.font_size))
Notification:notify(T(_("Font size set to %1."), self.font_size))
return true
end
@@ -217,7 +217,7 @@ function ReaderFont:onSetLineSpace(space)
self.line_space_percent = math.min(200, math.max(50, space))
self.ui.document:setInterlineSpacePercent(self.line_space_percent)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Line spacing set to: %1%."), self.line_space_percent))
Notification:notify(T(_("Line spacing set to %1%."), self.line_space_percent))
return true
end
@@ -225,7 +225,7 @@ function ReaderFont:onSetFontBaseWeight(weight)
self.font_base_weight = weight
self.ui.document:setFontBaseWeight(weight)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Font weight set to: %1."), optionsutil:getOptionText("SetFontBaseWeight", weight)))
Notification:notify(T(_("Font weight set to %1."), optionsutil:getOptionText("SetFontBaseWeight", weight)))
return true
end
@@ -233,7 +233,7 @@ function ReaderFont:onSetFontHinting(mode)
self.font_hinting = mode
self.ui.document:setFontHinting(mode)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Font hinting set to: %1"), optionsutil:getOptionText("SetFontHinting", mode)))
Notification:notify(T(_("Font hinting set to %1."), optionsutil:getOptionText("SetFontHinting", mode)))
return true
end
@@ -241,7 +241,7 @@ function ReaderFont:onSetFontKerning(mode)
self.font_kerning = mode
self.ui.document:setFontKerning(mode)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Font kerning set to: %1"), optionsutil:getOptionText("SetFontKerning", mode)))
Notification:notify(T(_("Font kerning set to %1."), optionsutil:getOptionText("SetFontKerning", mode)))
return true
end
@@ -249,7 +249,7 @@ function ReaderFont:onSetWordSpacing(values)
self.word_spacing = values
self.ui.document:setWordSpacing(values)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Word spacing set to: %1%, %2%"), values[1], values[2]))
Notification:notify(T(_("Word spacing set to %1%, %2%."), values[1], values[2]))
return true
end
@@ -257,7 +257,7 @@ function ReaderFont:onSetWordExpansion(value)
self.word_expansion = value
self.ui.document:setWordExpansion(value)
self.ui:handleEvent(Event:new("UpdatePos"))
Notification:notify(T(_("Word expansion set to: %1%."), value))
Notification:notify(T(_("Word expansion set to %1%."), value))
return true
end
@@ -266,7 +266,7 @@ function ReaderFont:onSetFontGamma(gamma)
self.ui.document:setGammaIndex(self.gamma_index)
local gamma_level = self.ui.document:getGammaLevel()
self.ui:handleEvent(Event:new("RedrawCurrentView"))
Notification:notify(T(_("Font gamma set to: %1."), optionsutil:getOptionText("SetFontGamma", gamma_level)))
Notification:notify(T(_("Font gamma set to %1."), optionsutil:getOptionText("SetFontGamma", gamma_level)))
return true
end

View File

@@ -169,6 +169,20 @@ function ReaderHighlight:init()
}
end)
-- User hyphenation dict
self:addToHighlightDialog("11_user_dict", function(_self)
return {
text= _("Hyphenate"),
show_in_highlight_dialog_func = function()
return _self.ui.userHyph and _self.ui.userhyph:isAvailable() and not _self.selected_text.text:find("[ ,;-%.\n]")
end,
callback = function()
_self.ui.userhyph:modifyUserEntry(_self.selected_text.text)
_self:onClose()
end,
}
end)
self.ui:registerPostInitCallback(function()
self.ui.menu:registerToMainMenu(self)
end)

View File

@@ -138,27 +138,19 @@ end
function ReaderTypeset:onToggleEmbeddedStyleSheet(toggle)
self:toggleEmbeddedStyleSheet(toggle)
if toggle then
Notification:notify(_("Enabled embedded styles."))
else
Notification:notify(_("Disabled embedded styles."))
end
Notification:notify(T( _("Embedded styles are %1."), optionsutil:getOptionText("ToggleEmbeddedStyleSheet", toggle)))
return true
end
function ReaderTypeset:onToggleEmbeddedFonts(toggle)
self:toggleEmbeddedFonts(toggle)
if toggle then
Notification:notify(_("Enabled embedded fonts."))
else
Notification:notify(_("Disabled embedded fonts."))
end
Notification:notify(T( _("Embedded fonts are %1."), optionsutil:getOptionText("ToggleEmbeddedFonts", toggle)))
return true
end
function ReaderTypeset:onToggleImageScaling(toggle)
self:toggleImageScaling(toggle)
Notification:notify(T( _("Image scaling set to: %1"), optionsutil:getOptionText("ToggleImageScaling", toggle)))
Notification:notify(T( _("Image scaling set to %1."), optionsutil:getOptionText("ToggleImageScaling", toggle)))
return true
end
@@ -169,7 +161,7 @@ end
function ReaderTypeset:onSetBlockRenderingMode(mode)
self:setBlockRenderingMode(mode)
Notification:notify(T( _("Render mode set to: %1"), optionsutil:getOptionText("SetBlockRenderingMode", mode)))
Notification:notify(T( _("Render mode set to %1."), optionsutil:getOptionText("SetBlockRenderingMode", mode)))
return true
end
@@ -191,7 +183,7 @@ local OBSOLETED_CSS = {
function ReaderTypeset:onSetRenderDPI(dpi)
self:setRenderDPI(dpi)
Notification:notify(T( _("Zoom set to: %1"), optionsutil:getOptionText("SetRenderDPI", dpi)))
Notification:notify(T( _("Zoom set to %1."), optionsutil:getOptionText("SetRenderDPI", dpi)))
return true
end

View File

@@ -237,6 +237,7 @@ When the book's language tag is not among our presets, no specific features will
})
self.text_lang_tag = lang_tag
self.ui.document:setTextMainLang(lang_tag)
self.ui:handleEvent(Event:new("TypographyLanguageChanged"))
self.ui:handleEvent(Event:new("UpdatePos"))
end,
hold_callback = function(touchmenu_instance)
@@ -427,8 +428,8 @@ These settings will apply to all books with any hyphenation dictionary.
enabled_func = function()
return self.hyphenation and not self.hyph_soft_hyphens_only
end,
separator = true,
})
table.insert(hyphenation_submenu, self.ui.userhyph:getMenuEntry())
table.insert(hyphenation_submenu, {
text_func = function()
-- Show the current language default hyph dict (ie: English_US for zh)
@@ -488,7 +489,7 @@ These settings will apply to all books with any hyphenation dictionary.
end,
})
table.insert(hyphenation_submenu, {
text = _("Soft-hyphens only"),
text = _("Soft hyphens only"),
callback = function()
self.hyph_soft_hyphens_only = not self.hyph_soft_hyphens_only
self.hyph_force_algorithmic = false
@@ -760,6 +761,7 @@ function ReaderTypography:onReadSettings(config)
logger.dbg("Typography lang: no lang set, using", self.text_lang_tag)
end
self.ui.document:setTextMainLang(self.text_lang_tag)
self.ui:handleEvent(Event:new("TypographyLanguageChanged"))
end
function ReaderTypography:onPreRenderDocument(config)
@@ -780,6 +782,7 @@ function ReaderTypography:onPreRenderDocument(config)
self.text_lang_tag = self.book_lang_tag
self.ui.doc_settings:saveSetting("text_lang", self.text_lang_tag)
self.ui.document:setTextMainLang(self.text_lang_tag)
self.ui:handleEvent(Event:new("TypographyLanguageChanged"))
self.ui:handleEvent(Event:new("UpdatePos"))
end,
enabled_func = function()
@@ -809,6 +812,7 @@ function ReaderTypography:onPreRenderDocument(config)
end
self.text_lang_tag = self.book_lang_tag
self.ui.document:setTextMainLang(self.text_lang_tag)
self.ui:handleEvent(Event:new("TypographyLanguageChanged"))
end
end

View File

@@ -0,0 +1,228 @@
local DataStorage = require("datastorage")
local Event = require("ui/event")
local FFIUtil = require("ffi/util")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local _ = require("gettext")
local T = require("ffi/util").template
local ReaderUserHyph = WidgetContainer:new{
-- return values from setUserHyphenationDict (crengine's UserHyphDict::init())
USER_DICT_RELOAD = 0,
USER_DICT_NOCHANGE = 1,
USER_DICT_MALFORMED = 2,
USER_DICT_ERROR_NOT_SORTED = 3,
}
-- returns path to the user dictionary
function ReaderUserHyph:getDictionaryPath()
return FFIUtil.joinPath(DataStorage:getSettingsDir(),
"user-" .. tostring(self.ui.document:getTextMainLangDefaultHyphDictionary():gsub(".pattern$", "")) .. ".hyph")
end
-- Load the user dictionary suitable for the actual language
-- if reload==true, force a reload
-- Unload is done automatically when a new dictionary is loaded.
function ReaderUserHyph:loadDictionary(name, reload)
if G_reader_settings:isTrue("hyph_user_dict") and lfs.attributes(name, "mode") == "file" then
local ret = self.ui.document:setUserHyphenationDict(name, reload)
-- this should only happen, if a user edits a dictionary by hand or the user messed
-- with the dictionary file by hand. -> Warning and disable.
if ret == self.USER_DICT_ERROR_NOT_SORTED then
UIManager:show(InfoMessage:new{
text = T(_("The user dictionary\n%1\nis not alphabetically sorted.\n\nIt has been disabled."), name),
})
logger.warn("UserHyph: Dictionary " .. name .. " is not sorted alphabetically.")
G_reader_settings:makeFalse("hyph_user_dict")
elseif ret == self.USER_DICT_MALFORMED then
UIManager:show(InfoMessage:new{
text = T(_("The user dictionary\n%1\nhas corrupted entries.\n\nOnly valid entries will be used."), name),
})
logger.warn("UserHyph: Dictionary " .. name .. " has corrupted entries.")
end
else
self.ui.document:setUserHyphenationDict() -- clear crengine user hyph dict
end
end
-- Reload on change of the hyphenation language
function ReaderUserHyph:onTypographyLanguageChanged()
self:loadUserDictionary()
end
-- Reload on "ChangedUserDictionary" event,
-- doesn't load dictionary if filesize and filename haven't changed
-- if reload==true reload
function ReaderUserHyph:loadUserDictionary(reload)
self:loadDictionary(self:isAvailable() and self:getDictionaryPath() or "", reload and true or false)
self.ui:handleEvent(Event:new("UpdatePos"))
end
-- Functions to use with the UI
function ReaderUserHyph:isAvailable()
return G_reader_settings:isTrue("hyph_user_dict") and self:_enabled()
end
function ReaderUserHyph:_enabled()
return self.ui.typography.hyphenation
end
-- add Menu entry
function ReaderUserHyph:getMenuEntry()
return {
text = _("Custom hyphenation rules"),
help_text = _("The hyphenation of a word can be changed from its default by long pressing for 3 seconds and selecting 'Hyphenate'."),
callback = function()
local hyph_user_dict = not G_reader_settings:isTrue("hyph_user_dict")
G_reader_settings:saveSetting("hyph_user_dict", hyph_user_dict)
self:loadUserDictionary() -- not needed to force a reload here
end,
checked_func = function()
return self:isAvailable()
end,
enabled_func = function()
return self:_enabled()
end,
separator = true,
}
end
-- Helper functions for dictionary entries-------------------------------------------
-- checks if suggestion is well formated
function ReaderUserHyph:checkHyphenation(suggestion, word)
if suggestion:find("%-%-") then
return false -- two or more consecutive '-'
end
suggestion = suggestion:gsub("-","")
if self.ui.document:getLowercasedWord(suggestion) == self.ui.document:getLowercasedWord(word) then
return true -- characters match (case insensitive)
end
return false
end
function ReaderUserHyph:updateDictionary(word, hyphenation)
local dict_file = self:getDictionaryPath()
local new_dict_file = dict_file .. ".new"
local new_dict = io.open(new_dict_file, "w")
if not new_dict then
logger.err("UserHyph: could not open " .. new_dict_file)
return
end
local word_lower = self.ui.document:getLowercasedWord(word)
local line
local dict = io.open(dict_file, "r")
if dict then
line = dict:read()
--search entry
while line and self.ui.document:getLowercasedWord(line:sub(1, line:find(";") - 1)) < word_lower do
new_dict:write(line .. "\n")
line = dict:read()
end
-- last word = nil if EOF, else last_word=word if found in file, else last_word is word after the new entry
if line then
local last_word = self.ui.document:getLowercasedWord(line:sub(1, line:find(";") - 1))
if last_word == self.ui.document:getLowercasedWord(word) then
line = nil -- word found
end
else
line = nil -- EOF
end
end
-- write new entry
if hyphenation and hyphenation ~= "" then
new_dict:write(string.format("%s;%s\n", word, hyphenation))
end
-- write old entry if there was one
if line then
new_dict:write(line .. "\n")
end
if dict then
repeat
line = dict:read()
if line then
new_dict:write(line .. "\n")
end
until (not line)
dict:close()
os.remove(dict_file)
end
new_dict:close()
os.rename(new_dict_file, dict_file)
self:loadUserDictionary(true) -- dictionary has changed, force a reload here
end
function ReaderUserHyph:modifyUserEntry(word)
if word:find("[ ,;-%.]") then return end -- no button if more than one word
if not self.ui.document then return end
local suggested_hyphenation = self.ui.document:getHyphenationForWord(word)
local input_dialog
input_dialog = InputDialog:new{
title = T(_("Hyphenate: %1"), word),
description = _("Add hyphenation positions with hyphens ('-') or spaces (' ')."),
input = suggested_hyphenation,
old_hyph_lowercase = self.ui.document:getLowercasedWord(suggested_hyphenation),
input_type = "string",
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(input_dialog)
end,
},
{
text = _("Remove"),
callback = function()
UIManager:close(input_dialog)
self:updateDictionary(word)
end,
},
{
text = _("Save"),
is_enter_default = true,
callback = function()
local new_suggestion = input_dialog:getInputText()
new_suggestion = new_suggestion:gsub(" ","-") -- replace spaces with hyphens
new_suggestion = new_suggestion:gsub("^-","") -- remove leading hypenations
new_suggestion = new_suggestion:gsub("-$","") -- remove trailing hypenations
if self:checkHyphenation(new_suggestion, word) then
-- don't save if no changes
if self.ui.document:getLowercasedWord(new_suggestion) ~= input_dialog.old_hyph_lowercase then
self:updateDictionary(word, new_suggestion)
end
UIManager:close(input_dialog)
else
UIManager:show(InfoMessage:new{
text = T(_("Invalid hyphenation!"), self.dict_file),
})
end
end,
},
},
},
}
UIManager:show(input_dialog)
input_dialog:onShowKeyboard()
end
return ReaderUserHyph

View File

@@ -715,7 +715,7 @@ function ReaderView:onSetRotationMode(rotation)
self.ui:handleEvent(Event:new("SetDimensions", new_screen_size))
self.ui:onScreenResize(new_screen_size)
self.ui:handleEvent(Event:new("InitScrollPageStates"))
Notification:notify(T(_("Rotation mode set to: %1"), optionsutil:getOptionText("SetRotationMode", rotation)))
Notification:notify(T(_("Rotation mode set to %1."), optionsutil:getOptionText("SetRotationMode", rotation)))
return true
end
@@ -849,12 +849,12 @@ function ReaderView:onGammaUpdate(gamma)
if self.page_scroll then
self.ui:handleEvent(Event:new("UpdateScrollPageGamma", gamma))
end
Notification:notify(T(_("Font gamma set to: %1."), gamma))
Notification:notify(T(_("Font gamma set to %1."), gamma))
end
function ReaderView:onFontSizeUpdate(font_size)
self.ui:handleEvent(Event:new("ReZoom", font_size))
Notification:notify(T(_("Font zoom set to: %1."), font_size))
Notification:notify(T(_("Font zoom set to %1."), font_size))
end
function ReaderView:onDefectSizeUpdate()
@@ -874,7 +874,7 @@ function ReaderView:onSetViewMode(new_mode)
self.view_mode = new_mode
self.ui.document:setViewMode(new_mode)
self.ui:handleEvent(Event:new("ChangeViewMode"))
Notification:notify(T( _("View mode set to: %1"), optionsutil:getOptionText("SetViewMode", new_mode)))
Notification:notify(T( _("View mode set to %1."), optionsutil:getOptionText("SetViewMode", new_mode)))
end
end