mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Add option for custom hyphenation rules (#7787)
This is the successor of #7746.
This commit is contained in:
2
base
2
base
Submodule base updated: 96c002d526...502bba07e5
@@ -174,6 +174,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
228
frontend/apps/reader/modules/readeruserhyph.lua
Normal file
228
frontend/apps/reader/modules/readeruserhyph.lua
Normal 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
|
||||
@@ -48,6 +48,7 @@ local ReaderStyleTweak = require("apps/reader/modules/readerstyletweak")
|
||||
local ReaderToc = require("apps/reader/modules/readertoc")
|
||||
local ReaderTypeset = require("apps/reader/modules/readertypeset")
|
||||
local ReaderTypography = require("apps/reader/modules/readertypography")
|
||||
local ReaderUserHyph = require("apps/reader/modules/readeruserhyph")
|
||||
local ReaderView = require("apps/reader/modules/readerview")
|
||||
local ReaderWikipedia = require("apps/reader/modules/readerwikipedia")
|
||||
local ReaderZooming = require("apps/reader/modules/readerzooming")
|
||||
@@ -314,6 +315,12 @@ function ReaderUI:init()
|
||||
view = self.view,
|
||||
ui = self
|
||||
})
|
||||
-- user hyphenation (must be registered before typography)
|
||||
self:registerModule("userhyph", ReaderUserHyph:new{
|
||||
dialog = self.dialog,
|
||||
view = self.view,
|
||||
ui = self
|
||||
})
|
||||
-- typography menu (replaces previous hyphenation menu / ReaderHyphenation)
|
||||
self:registerModule("typography", ReaderTypography:new{
|
||||
dialog = self.dialog,
|
||||
|
||||
@@ -980,6 +980,25 @@ function CreDocument:setTextHyphenationSoftHyphensOnly(toggle)
|
||||
self._document:setStringProperty("crengine.textlang.hyphenation.soft.hyphens.only", toggle and 1 or 0)
|
||||
end
|
||||
|
||||
function CreDocument:setUserHyphenationDict(dict, reload)
|
||||
logger.dbg("CreDocument: set textlang hyphenation dict", dict or "none")
|
||||
return self._document:setUserHyphenationDict(dict or "", reload or false)
|
||||
end
|
||||
|
||||
function CreDocument:getHyphenationForWord(word)
|
||||
if word then
|
||||
return self._document:getHyphenationForWord(word)
|
||||
end
|
||||
return word
|
||||
end
|
||||
|
||||
function CreDocument:getLowercasedWord(word)
|
||||
if word then
|
||||
return self._document:getLowercasedWord(word)
|
||||
end
|
||||
return word
|
||||
end
|
||||
|
||||
function CreDocument:setTextHyphenationForceAlgorithmic(toggle)
|
||||
logger.dbg("CreDocument: set textlang hyphenation force algorithmic", toggle)
|
||||
self._document:setStringProperty("crengine.textlang.hyphenation.force.algorithmic", toggle and 1 or 0)
|
||||
|
||||
Reference in New Issue
Block a user