From 7f6aebe3994419efef4e1ddec0a8185adad3fb26 Mon Sep 17 00:00:00 2001 From: poire-z Date: Thu, 19 Apr 2018 14:24:04 +0200 Subject: [PATCH] cre hyphenation: allow for setting min left/right fragment length (#3890) When hyphenating a word, the existing algorithms enforced a minimal length of 2 for each word fragments on left or right side. This adds a widget to allow changing these minimal sizes (from 1 to 10). --- .../apps/reader/modules/readerhyphenation.lua | 51 ++++ frontend/document/credocument.lua | 11 + frontend/ui/widget/hyphenationlimits.lua | 283 ++++++++++++++++++ frontend/ui/widget/numberpickerwidget.lua | 21 +- 4 files changed, 357 insertions(+), 9 deletions(-) create mode 100644 frontend/ui/widget/hyphenationlimits.lua diff --git a/frontend/apps/reader/modules/readerhyphenation.lua b/frontend/apps/reader/modules/readerhyphenation.lua index 7ad25d266..8e35d7ef7 100644 --- a/frontend/apps/reader/modules/readerhyphenation.lua +++ b/frontend/apps/reader/modules/readerhyphenation.lua @@ -1,3 +1,4 @@ +local Event = require("ui/event") local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local JSON = require("json") @@ -15,13 +16,53 @@ local ReaderHyphenation = InputContainer:new{ function ReaderHyphenation:init() self.lang_table = {} self.hyph_table = {} + self.hyph_algs_settings = {} self.hyph_alg = cre.getSelectedHyphDict() + table.insert(self.hyph_table, { + text_func = function() + local limits_text = _("language defaults") + if G_reader_settings:readSetting("hyph_left_hyphen_min") + or G_reader_settings:readSetting("hyph_right_hyphen_min") then + limits_text = T("%1 - %2", G_reader_settings:readSetting("hyph_left_hyphen_min"), + G_reader_settings:readSetting("hyph_right_hyphen_min")) + end + return T(_("Left/right minimal sizes: %1"), limits_text) + end, + callback = function() + local HyphenationLimitsWidget = require("ui/widget/hyphenationlimits") + local hyph_settings = self.hyph_algs_settings[self.hyph_alg] or {} + local alg_left_hyphen_min = hyph_settings.left_hyphen_min + local alg_right_hyphen_min = hyph_settings.right_hyphen_min + local hyph_limits_widget = HyphenationLimitsWidget:new{ + left_value = G_reader_settings:readSetting("hyph_left_hyphen_min") or alg_left_hyphen_min or 2, + right_value = G_reader_settings:readSetting("hyph_right_hyphen_min") or alg_right_hyphen_min or 2, + left_default = alg_left_hyphen_min or 2, + right_default = alg_right_hyphen_min or 2, + callback = function(left_hyphen_min, right_hyphen_min) + G_reader_settings:saveSetting("hyph_left_hyphen_min", left_hyphen_min) + G_reader_settings:saveSetting("hyph_right_hyphen_min", right_hyphen_min) + self.ui.document:setHyphLeftHyphenMin(G_reader_settings:readSetting("hyph_left_hyphen_min") or alg_left_hyphen_min) + self.ui.document:setHyphRightHyphenMin(G_reader_settings:readSetting("hyph_right_hyphen_min") or alg_right_hyphen_min) + self.ui.toc:onUpdateToc() + -- signal readerrolling to update pos in new height, and redraw page + self.ui:handleEvent(Event:new("UpdatePos")) + end + } + UIManager:show(hyph_limits_widget) + end, + enabled_func = function() + return self.hyph_alg ~= "@none" + end, + separator = true, + }) + local lang_data_file = assert(io.open("./data/hyph/languages.json"), "r") local ok, lang_data = pcall(JSON.decode, lang_data_file:read("*all")) if ok and lang_data then for k,v in ipairs(lang_data) do + self.hyph_algs_settings[v.filename] = v -- just store full table table.insert(self.hyph_table, { text = v.name, callback = function() @@ -31,7 +72,12 @@ function ReaderHyphenation:init() text = T(_("Changed hyphenation to %1."), v.name), }) self.ui.document:setHyphDictionary(v.filename) + -- Apply hyphenation sides limits + self.ui.document:setHyphLeftHyphenMin(G_reader_settings:readSetting("hyph_left_hyphen_min") or v.left_hyphen_min) + self.ui.document:setHyphRightHyphenMin(G_reader_settings:readSetting("hyph_right_hyphen_min") or v.right_hyphen_min) self.ui.toc:onUpdateToc() + -- signal readerrolling to update pos in new height, and redraw page + self.ui:handleEvent(Event:new("UpdatePos")) end, hold_callback = function() UIManager:show(MultiConfirmBox:new{ @@ -109,6 +155,7 @@ function ReaderHyphenation:getDictForLanguage(lang_tag) end return dict end + function ReaderHyphenation:onPreRenderDocument(config) -- This is called after the document has been loaded -- so we can use the document language. @@ -129,6 +176,10 @@ function ReaderHyphenation:onPreRenderDocument(config) end -- If we haven't set any, hardcoded English_US_hyphen_(Alan).pdb (in cre.cpp) will be used self.hyph_alg = cre.getSelectedHyphDict() + -- Apply hyphenation sides limits + local hyph_settings = self.hyph_algs_settings[self.hyph_alg] or {} + self.ui.document:setHyphLeftHyphenMin(G_reader_settings:readSetting("hyph_left_hyphen_min") or hyph_settings.left_hyphen_min) + self.ui.document:setHyphRightHyphenMin(G_reader_settings:readSetting("hyph_right_hyphen_min") or hyph_settings.right_hyphen_min) end function ReaderHyphenation:addToMainMenu(menu_items) diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 0ee7d9599..58d00bb3a 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -411,6 +411,17 @@ function CreDocument:setHyphDictionary(new_hyph_dictionary) end end +function CreDocument:setHyphLeftHyphenMin(value) + -- default crengine value is 2: reset it if no value provided + logger.dbg("CreDocument: set hyphenation left hyphen min", value or 2) + self._document:setIntProperty("crengine.hyphenation.left.hyphen.min", value or 2) +end + +function CreDocument:setHyphRightHyphenMin(value) + logger.dbg("CreDocument: set hyphenation right hyphen min", value or 2) + self._document:setIntProperty("crengine.hyphenation.right.hyphen.min", value or 2) +end + function CreDocument:clearSelection() logger.dbg("clear selection") self._document:clearSelection() diff --git a/frontend/ui/widget/hyphenationlimits.lua b/frontend/ui/widget/hyphenationlimits.lua new file mode 100644 index 000000000..592b3d6a4 --- /dev/null +++ b/frontend/ui/widget/hyphenationlimits.lua @@ -0,0 +1,283 @@ +local Blitbuffer = require("ffi/blitbuffer") +local ButtonTable = require("ui/widget/buttontable") +local CenterContainer = require("ui/widget/container/centercontainer") +local CloseButton = require("ui/widget/closebutton") +local Device = require("device") +local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local Font = require("ui/font") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local InputContainer = require("ui/widget/container/inputcontainer") +local LineWidget = require("ui/widget/linewidget") +local MovableContainer = require("ui/widget/container/movablecontainer") +local OverlapGroup = require("ui/widget/overlapgroup") +local NumberPickerWidget = require("ui/widget/numberpickerwidget") +local Size = require("ui/size") +local TextBoxWidget = require("ui/widget/textboxwidget") +local TextWidget = require("ui/widget/textwidget") +local UIManager = require("ui/uimanager") +local VerticalGroup = require("ui/widget/verticalgroup") +local VerticalSpan = require("ui/widget/verticalspan") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local _ = require("gettext") +local Screen = Device.screen + +local HyphenationLimitsWidget = InputContainer:new{ + title_text = _("Hyphenation limits"), + title_face = Font:getFace("x_smalltfont"), + width = nil, + height = nil, + -- Min (2) and max (10) values are enforced by crengine + left_min = 1, + left_max = 10, + left_value = 2, + left_default = nil, + right_min = 1, + right_max = 10, + right_value = 2, + right_default = nil, +} + +function HyphenationLimitsWidget:init() + self.medium_font_face = Font:getFace("ffont") + self.screen_width = Screen:getWidth() + self.screen_height = Screen:getHeight() + -- let room on the widget sides so we can see + -- the hyphenation changes happening + self.width = self.screen_width * 0.6 + self.picker_width = self.screen_width * 0.25 + if Device:hasKeys() then + self.key_events = { + Close = { {"Back"}, doc = "close time widget" } + } + end + if Device:isTouchDevice() then + self.ges_events = { + TapClose = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + w = self.screen_width, + h = self.screen_height, + } + }, + }, + } + end + self:update() +end + +function HyphenationLimitsWidget:update() + -- This picker_update_callback will be redefined later. It is needed + -- so we can have our MovableContainer repainted on NumberPickerWidgets + -- update It is needed if we have enabled transparency on MovableContainer, + -- otherwise the NumberPicker area gets opaque on update. + local picker_update_callback = function() end + local left_widget = NumberPickerWidget:new{ + show_parent = self, + width = self.picker_width, + value = self.left_value, + value_min = self.left_min, + value_max = self.left_max, + wrap = false, + update_callback = function() picker_update_callback() end, + } + local right_widget = NumberPickerWidget:new{ + show_parent = self, + width = self.picker_width, + value = self.right_value, + value_min = self.right_min, + value_max = self.right_max, + wrap = false, + update_callback = function() picker_update_callback() end, + } + local hyph_group = HorizontalGroup:new{ + align = "center", + VerticalGroup:new{ + align = "center", + VerticalSpan:new{ width = Size.span.vertical_large }, + TextBoxWidget:new{ + text = _("Left"), + alignment = "center", + face = self.title_face, + width = self.picker_width, + }, + left_widget, + }, + VerticalGroup:new{ + align = "center", + VerticalSpan:new{ width = Size.span.vertical_large }, + TextBoxWidget:new{ + text = _("Right"), + alignment = "center", + face = self.title_face, + width = self.picker_width, + }, + right_widget, + }, + } + + local hyph_title = FrameContainer:new{ + padding = Size.padding.default, + margin = Size.margin.title, + bordersize = 0, + TextWidget:new{ + text = self.title_text, + face = self.title_face, + bold = true, + width = self.width, + }, + } + local hyph_line = LineWidget:new{ + dimen = Geom:new{ + w = self.width, + h = Size.line.thick, + } + } + local hyph_bar = OverlapGroup:new{ + dimen = { + w = self.width, + h = hyph_title:getSize().h + }, + hyph_title, + CloseButton:new{ window = self, padding_top = Size.margin.title, }, + } + + local hyph_into_text = _([[ +Set minimum length before hyphenation occurs. +These settings will apply to all books with any hyphenation dictionary. +'Use language defaults' resets them.]]) + local hyph_info = FrameContainer:new{ + padding = Size.padding.default, + margin = Size.margin.small, + bordersize = 0, + TextBoxWidget:new{ + text = hyph_into_text, + face = Font:getFace("x_smallinfofont"), + width = self.width * 0.9, + } + } + + local buttons = { + { + { + text = _("Close"), + callback = function() + self:onClose() + end, + }, + { + text = _("Apply"), + callback = function() + if self.callback then + self.callback(left_widget:getValue(), right_widget:getValue()) + end + end, + }, + }, + { + { + text = _("Use language defaults"), + callback = function() + left_widget.value = self.left_default + right_widget.value = self.right_default + left_widget:update() + right_widget:update() + self.callback(nil, nil) + end, + }, + } + } + + local button_table = ButtonTable:new{ + width = self.width - 2*Size.padding.default, + buttons = buttons, + zero_sep = true, + show_parent = self, + } + + self.hyph_frame = FrameContainer:new{ + radius = Size.radius.window, + padding = 0, + margin = 0, + background = Blitbuffer.COLOR_WHITE, + VerticalGroup:new{ + align = "left", + hyph_bar, + hyph_line, + hyph_info, + VerticalSpan:new{ width = Size.span.vertical_large }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = hyph_group:getSize().h, + }, + hyph_group + }, + VerticalSpan:new{ width = Size.span.vertical_large }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = button_table:getSize().h, + }, + button_table + } + } + } + self.movable = MovableContainer:new{ + self.hyph_frame, + } + self[1] = WidgetContainer:new{ + align = "center", + dimen = Geom:new{ + x = 0, y = 0, + w = self.screen_width, + h = self.screen_height, + }, + self.movable, + } + UIManager:setDirty(self, function() + return "ui", self.hyph_frame.dimen + end) + picker_update_callback = function() + UIManager:setDirty("all", function() + return "ui", self.movable.dimen + end) + -- If we'd like to have the values auto-applied, uncomment this: + -- self.callback(left_widget:getValue(), right_widget:getValue()) + end +end + +function HyphenationLimitsWidget:onCloseWidget() + UIManager:setDirty(nil, function() + return "partial", self.hyph_frame.dimen + end) + return true +end + +function HyphenationLimitsWidget:onShow() + UIManager:setDirty(self, function() + return "ui", self.hyph_frame.dimen + end) + return true +end + +function HyphenationLimitsWidget:onAnyKeyPressed() + UIManager:close(self) + return true +end + +function HyphenationLimitsWidget:onTapClose(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.hyph_frame.dimen) then + self:onClose() + end + return true +end + +function HyphenationLimitsWidget:onClose() + UIManager:close(self) + return true +end + +return HyphenationLimitsWidget diff --git a/frontend/ui/widget/numberpickerwidget.lua b/frontend/ui/widget/numberpickerwidget.lua index a12722d36..8f287425a 100644 --- a/frontend/ui/widget/numberpickerwidget.lua +++ b/frontend/ui/widget/numberpickerwidget.lua @@ -25,6 +25,8 @@ local NumberPickerWidget = InputContainer:new{ value_step = 1, value_hold_step = 4, value_table = nil, + wrap = true, + update_callback = function() end, -- in case we need calculate number of days in a given month and year date_month = nil, date_year = nil, @@ -60,14 +62,14 @@ function NumberPickerWidget:paintWidget() if self.date_month and self.date_year then self.value_max = self:getDaysInMonth(self.date_month:getValue(), self.date_year:getValue()) end - self.value = self:changeValue(self.value, self.value_step, self.value_max, self.value_min) + self.value = self:changeValue(self.value, self.value_step, self.value_max, self.value_min, self.wrap) self:update() end, hold_callback = function() if self.date_month and self.date_year then self.value_max = self:getDaysInMonth(self.date_month:getValue(), self.date_year:getValue()) end - self.value = self:changeValue(self.value, self.value_hold_step, self.value_max, self.value_min) + self.value = self:changeValue(self.value, self.value_hold_step, self.value_max, self.value_min, self.wrap) self:update() end } @@ -83,14 +85,14 @@ function NumberPickerWidget:paintWidget() if self.date_month and self.date_year then self.value_max = self:getDaysInMonth(self.date_month:getValue(), self.date_year:getValue()) end - self.value = self:changeValue(self.value, self.value_step * -1, self.value_max, self.value_min) + self.value = self:changeValue(self.value, self.value_step * -1, self.value_max, self.value_min, self.wrap) self:update() end, hold_callback = function() if self.date_month and self.date_year then self.value_max = self:getDaysInMonth(self.date_month:getValue(), self.date_year:getValue()) end - self.value = self:changeValue(self.value, self.value_hold_step * -1, self.value_max, self.value_min) + self.value = self:changeValue(self.value, self.value_hold_step * -1, self.value_max, self.value_min, self.wrap) self:update() end } @@ -182,24 +184,25 @@ function NumberPickerWidget:update() UIManager:setDirty(self.show_parent, function() return "ui", self.dimen end) + self.update_callback() end -function NumberPickerWidget:changeValue(value, step, max, min) +function NumberPickerWidget:changeValue(value, step, max, min, wrap) if self.value_index then self.value_index = self.value_index + step if self.value_index > #self.value_table then - self.value_index = 1 + self.value_index = wrap and 1 or #self.value_table elseif self.value_index < 1 then - self.value_index = #self.value_table + self.value_index = wrap and #self.value_table or 1 end value = self.value_table[self.value_index] else value = value + step if value > max then - value = min + value = wrap and min or max elseif value < min then - value = max + value = wrap and max or min end end return value