local BD = require("ui/bidi") local ConfirmBox = require("ui/widget/confirmbox") local Event = require("ui/event") local InfoMessage = require("ui/widget/infomessage") local Math = require("optmath") local Notification = require("ui/widget/notification") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local lfs = require("libs/libkoreader-lfs") local optionsutil = require("ui/data/optionsutil") local _ = require("gettext") local C_ = _.pgettext local Screen = require("device").screen local T = require("ffi/util").template local ReaderTypeset = WidgetContainer:extend{ -- @translators This is style in the sense meant by CSS (cascading style sheets), relating to the layout and presentation of the document. See for more information. css_menu_title = C_("CSS", "Style"), css = nil, unscaled_margins = nil, } function ReaderTypeset:init() self.ui.menu:registerToMainMenu(self) end function ReaderTypeset:onReadSettings(config) self.css = config:readSetting("css") if not self.css then if self.ui.document.is_fb2 then self.css = G_reader_settings:readSetting("copt_fb2_css") else self.css = G_reader_settings:readSetting("copt_css") end end if not self.css then self.css = self.ui.document.default_css end local tweaks_css = self.ui.styletweak:getCssText() self.ui.document:setStyleSheet(self.css, tweaks_css) -- default to enable embedded fonts self.ui.document:setEmbeddedFonts(self.configurable.embedded_fonts) -- default to enable embedded CSS self.ui.document:setEmbeddedStyleSheet(self.configurable.embedded_css) -- Block rendering mode: stay with legacy rendering for books -- previously opened so bookmarks and highlights stay valid. -- For new books, use 'web' mode below in BLOCK_RENDERING_FLAGS if config:has("copt_block_rendering_mode") then self.block_rendering_mode = config:readSetting("copt_block_rendering_mode") else if config:has("last_xpointer") and not config:has("docsettings_reset_done") then -- We have a last_xpointer: this book was previously opened self.block_rendering_mode = 0 else self.block_rendering_mode = G_reader_settings:readSetting("copt_block_rendering_mode") or 3 -- default to 'web' mode end -- Let ConfigDialog know so it can update it on screen and have it saved on quit self.configurable.block_rendering_mode = self.block_rendering_mode end self:setBlockRenderingMode(self.block_rendering_mode) -- default to 96 dpi self.ui.document:setRenderDPI(self.configurable.render_dpi) -- uncomment if we want font size to follow DPI changes -- self.ui.document:setRenderScaleFontWithDPI(1) -- set page margins self.unscaled_margins = { self.configurable.h_page_margins[1], self.configurable.t_page_margin, self.configurable.h_page_margins[2], self.configurable.b_page_margin } self:onSetPageMargins(self.unscaled_margins) self.sync_t_b_page_margins = self.configurable.sync_t_b_page_margins == 1 and true or false -- default to disable TXT formatting as it does more harm than good (the setting is not in UI) self.txt_preformatted = config:readSetting("txt_preformatted") or G_reader_settings:readSetting("txt_preformatted") or 1 self.ui.document:setTxtPreFormatted(self.txt_preformatted) -- default to disable smooth scaling self.ui.document:setImageScaling(self.configurable.smooth_scaling == 1) -- default to automagic nightmode-friendly handling of images self.ui.document:setNightmodeImages(self.configurable.nightmode_images == 1) end function ReaderTypeset:onSaveSettings() self.ui.doc_settings:saveSetting("css", self.css) end function ReaderTypeset:onToggleEmbeddedStyleSheet(toggle) local text if toggle then self.configurable.embedded_css = 1 text = _("Enabled embedded styles.") else self.configurable.embedded_css = 0 text = _("Disabled embedded styles.") end self.ui.document:setEmbeddedStyleSheet(self.configurable.embedded_css) self.ui:handleEvent(Event:new("UpdatePos")) Notification:notify(text) return true end function ReaderTypeset:onToggleEmbeddedFonts(toggle) local text if toggle then self.configurable.embedded_fonts = 1 text = _("Enabled embedded fonts.") else self.configurable.embedded_fonts = 0 text = _("Disabled embedded fonts.") end self.ui.document:setEmbeddedFonts(self.configurable.embedded_fonts) self.ui:handleEvent(Event:new("UpdatePos")) Notification:notify(text) return true end function ReaderTypeset:onToggleImageScaling(toggle) self.configurable.smooth_scaling = toggle and 1 or 0 self.ui.document:setImageScaling(toggle) self.ui:handleEvent(Event:new("UpdatePos")) local text = T(_("Image scaling set to: %1"), optionsutil:getOptionText("ToggleImageScaling", toggle)) Notification:notify(text) return true end function ReaderTypeset:onToggleNightmodeImages(toggle) self.configurable.nightmode_images = toggle and 1 or 0 self.ui.document:setNightmodeImages(toggle) self.ui:handleEvent(Event:new("UpdatePos")) return true end function ReaderTypeset:onSetBlockRenderingMode(mode) self:setBlockRenderingMode(mode) local text = T(_("Render mode set to: %1"), optionsutil:getOptionText("SetBlockRenderingMode", mode)) Notification:notify(text) return true end function ReaderTypeset:onSetRenderDPI(dpi) self.configurable.render_dpi = dpi self.ui.document:setRenderDPI(dpi) self.ui:handleEvent(Event:new("UpdatePos")) local text = T(_("Zoom set to: %1"), optionsutil:getOptionText("SetRenderDPI", dpi)) Notification:notify(text) return true end -- June 2018: epub.css has been cleaned to be more conforming to HTML specs -- and to not include class name based styles (with conditional compatiblity -- styles for previously opened documents). It should be usable on all -- HTML based documents, except FB2 which has some incompatible specs. -- These other css files have not been updated in the same way, and are -- kept as-is for when a previously opened document requests one of them. local OBSOLETED_CSS = { "chm.css", "cr3.css", "doc.css", "dict.css", "htm.css", "rtf.css", "txt.css", } function ReaderTypeset:genStyleSheetMenu() local getStyleMenuItem = function(text, css_file, description, fb2_compatible, separator) return { text_func = function() local css_opt = self.ui.document.is_fb2 and "copt_fb2_css" or "copt_css" return text .. (css_file == G_reader_settings:readSetting(css_opt) and " ★" or "") end, callback = function() self:setStyleSheet(css_file or self.ui.document.default_css) end, hold_callback = function(touchmenu_instance) self:makeDefaultStyleSheet(css_file, text, description, touchmenu_instance) end, checked_func = function() if not css_file then -- "Auto" return self.css == self.ui.document.default_css end return css_file == self.css end, enabled_func = function() if fb2_compatible == true and not self.ui.document.is_fb2 then return false end if fb2_compatible == false and self.ui.document.is_fb2 then return false end -- if fb2_compatible==nil, we don't know (user css file) return true end, separator = separator, } end local style_table = {} local obsoleted_table = {} table.insert(style_table, getStyleMenuItem( _("None"), "", _("This sets an empty User-Agent stylesheet, and expects the document stylesheet to style everything (which publishers probably don't).\nThis is mostly only interesting for testing.") )) table.insert(style_table, getStyleMenuItem( _("Auto"), nil, _("This selects the default and preferred stylesheet for the document type."), nil, true -- separator )) local css_files = {} for f in lfs.dir("./data") do if lfs.attributes("./data/"..f, "mode") == "file" and string.match(f, "%.css$") then css_files[f] = "./data/"..f end end -- Add the 3 main styles if css_files["epub.css"] then table.insert(style_table, getStyleMenuItem( _("Traditional book look (epub.css)"), css_files["epub.css"], _([[ This is our book look-alike stylesheet: it extends the HTML standard stylesheet with styles aimed at making HTML content look more like a paper book (with justified text and indentation on paragraphs) than like a web page. It is perfect for unstyled books, and might make styled books more readable. It may cause some small issues on some books (miscentered titles, headings or separators, or unexpected text indentation), as publishers don't expect to have our added styles at play and need to reset them; try switching to html5.css when you notice such issues.]]), false -- not fb2_compatible )) css_files["epub.css"] = nil end if css_files["html5.css"] then table.insert(style_table, getStyleMenuItem( _("HTML Standard rendering (html5.css)"), css_files["html5.css"], _([[ This stylesheet conforms to the HTML Standard rendering suggestions (with a few limitations), similar to what most web browsers use. As most publishers nowadays make and test their book with tools based on web browser engines, it is the stylesheet to use to see a book as these publishers intended. On unstyled books though, it may give them the look of a web page (left aligned paragraphs without indentation and with spacing between them); try switching to epub.css when that happens.]]), false -- not fb2_compatible )) css_files["html5.css"] = nil end if css_files["fb2.css"] then table.insert(style_table, getStyleMenuItem( _("FictionBook (fb2.css)"), css_files["fb2.css"], _([[ This stylesheet is to be used only with FB2 and FB3 documents, which are not classic HTML, and need some specific styling. (FictionBook 2 & 3 are open XML-based e-book formats which originated and gained popularity in Russia.)]]), true, -- fb2_compatible true -- separator )) css_files["fb2.css"] = nil end -- Add the obsoleted ones to the Obsolete sub menu local obsoleted_css = {} -- for check_func of the Obsolete sub menu itself for __, css in ipairs(OBSOLETED_CSS) do obsoleted_css[css_files[css]] = css if css_files[css] then table.insert(obsoleted_table, getStyleMenuItem(css, css_files[css], _("This stylesheet is obsolete: don't use it. It is kept solely to be able to open documents last read years ago and to migrate their highlights."))) css_files[css] = nil end end -- Sort and add the remaining (user added) files if any local user_files = {} for css, css_file in pairs(css_files) do table.insert(user_files, css) end table.sort(user_files) for __, css in ipairs(user_files) do table.insert(style_table, getStyleMenuItem(css, css_files[css], _("This is a user added stylesheet."))) end style_table[#style_table].separator = true table.insert(style_table, { text_func = function() local text = _("Obsolete") if obsoleted_css[self.css] then text = T(_("Obsolete (%1)"), BD.filename(obsoleted_css[self.css])) end if obsoleted_css[G_reader_settings:readSetting("copt_css")] then text = text .. " ★" end return text end, sub_item_table = obsoleted_table, checked_func = function() return obsoleted_css[self.css] ~= nil end }) return style_table end function ReaderTypeset:onApplyStyleSheet() local tweaks_css = self.ui.styletweak:getCssText() self.ui.document:setStyleSheet(self.css, tweaks_css) self.ui:handleEvent(Event:new("UpdatePos")) return true end function ReaderTypeset:setStyleSheet(new_css) if new_css ~= self.css then self.css = new_css local tweaks_css = self.ui.styletweak:getCssText() self.ui.document:setStyleSheet(new_css, tweaks_css) self.ui:handleEvent(Event:new("UpdatePos")) end end -- Not used function ReaderTypeset:setEmbededStyleSheetOnly() if self.css ~= nil then -- clear applied css self.ui.document:setStyleSheet("") self.ui.document:setEmbeddedStyleSheet(1) self.css = nil self.ui:handleEvent(Event:new("UpdatePos")) end end -- crengine enhanced block rendering feature/flags (see crengine/include/lvrend.h): -- legacy flat book web -- ENHANCED 0x00000001 x x x -- ALLOW_PAGE_BREAK_WHEN_NO_CONTENT 0x00000002 x -- -- COLLAPSE_VERTICAL_MARGINS 0x00000010 x x x -- ALLOW_VERTICAL_NEGATIVE_MARGINS 0x00000020 x x x -- ALLOW_NEGATIVE_COLLAPSED_MARGINS 0x00000040 x -- -- ENSURE_MARGIN_AUTO_ALIGNMENT 0x00000100 x x -- ALLOW_HORIZONTAL_NEGATIVE_MARGINS 0x00000200 x -- ALLOW_HORIZONTAL_BLOCK_OVERFLOW 0x00000400 x -- ALLOW_HORIZONTAL_PAGE_OVERFLOW 0x00000800 x -- -- USE_W3C_BOX_MODEL 0x00001000 x x x -- ALLOW_STYLE_W_H_ABSOLUTE_UNITS 0x00002000 x -- ENSURE_STYLE_WIDTH 0x00004000 x x -- ENSURE_STYLE_HEIGHT 0x00008000 x -- -- WRAP_FLOATS 0x00010000 x x x -- PREPARE_FLOATBOXES 0x00020000 x x x -- FLOAT_FLOATBOXES 0x00040000 x x -- DO_NOT_CLEAR_OWN_FLOATS 0x00100000 x x -- ALLOW_EXACT_FLOATS_FOOTPRINTS 0x00200000 x x -- -- BOX_INLINE_BLOCKS 0x01000000 x x x -- COMPLETE_INCOMPLETE_TABLES 0x02000000 x x x local BLOCK_RENDERING_FLAGS = { 0x00000000, -- legacy block rendering 0x03030031, -- flat mode (with prepared floatBoxes, so inlined, to avoid display hash mismatch) 0x03375131, -- book mode (floating floatBoxes, limited widths support) 0x7FFFFFFF, -- web mode, all features/flags } function ReaderTypeset:setBlockRenderingMode(mode) -- mode starts for now with 0 = legacy, so we may later be able -- to remove it and then start with 1 = flat -- (Ensure we don't crash if we added and removed some options) if mode + 1 > #BLOCK_RENDERING_FLAGS then mode = #BLOCK_RENDERING_FLAGS - 1 end local flags = BLOCK_RENDERING_FLAGS[mode + 1] if not flags then return end self.block_rendering_mode = mode if self.ensure_saner_block_rendering_flags then -- see next function -- Don't enable BOX_INLINE_BLOCKS -- inlineBoxes have been around and allowed on older DOM_VERSION -- for a few weeks - let's disable it: it may break highlights -- made during this time, but may resurrect others made during -- a longer previous period of time. flags = bit.band(flags, bit.bnot(0x01000000)) -- Don't enable COMPLETE_INCOMPLETE_TABLES, as they may add -- many boxing elements around huge amount of text, and break -- some past highlights made on the non-boxed elements. flags = bit.band(flags, bit.bnot(0x02000000)) end self.ui.document:setBlockRenderingFlags(flags) self.ui:handleEvent(Event:new("UpdatePos")) end function ReaderTypeset:ensureSanerBlockRenderingFlags(mode) -- Called by ReaderRolling:onReadSettings() when old -- DOM version requested, before normalized xpointers, -- asking us to unset some of the flags set previously. self.ensure_saner_block_rendering_flags = true self:setBlockRenderingMode(self.block_rendering_mode) end function ReaderTypeset:addToMainMenu(menu_items) -- insert table to main reader menu menu_items.set_render_style = { text = self.css_menu_title, sub_item_table = self:genStyleSheetMenu(), } end function ReaderTypeset:makeDefaultStyleSheet(css, name, description, touchmenu_instance) local text = self.ui.document.is_fb2 and T(_("Set default style for FB2 documents to %1?"), BD.filename(name)) or T(_("Set default style to %1?"), BD.filename(name)) if description then text = text .. "\n\n" .. description end UIManager:show(ConfirmBox:new{ text = text, ok_callback = function() if self.ui.document.is_fb2 then G_reader_settings:saveSetting("copt_fb2_css", css) else G_reader_settings:saveSetting("copt_css", css) end if touchmenu_instance then touchmenu_instance:updateItems() end end, }) end function ReaderTypeset:onSetPageHorizMargins(h_margins, when_applied_callback) self.unscaled_margins = { h_margins[1], self.unscaled_margins[2], h_margins[2], self.unscaled_margins[4] } self.ui:handleEvent(Event:new("SetPageMargins", self.unscaled_margins, when_applied_callback)) end function ReaderTypeset:onSetPageTopMargin(t_margin, when_applied_callback) self.unscaled_margins = { self.unscaled_margins[1], t_margin, self.unscaled_margins[3], self.unscaled_margins[4] } if self.sync_t_b_page_margins then self.unscaled_margins[4] = t_margin -- Let ConfigDialog know so it can update it on screen and have it saved on quit self.configurable.b_page_margin = t_margin end self.ui:handleEvent(Event:new("SetPageMargins", self.unscaled_margins, when_applied_callback)) end function ReaderTypeset:onSetPageBottomMargin(b_margin, when_applied_callback) self.unscaled_margins = { self.unscaled_margins[1], self.unscaled_margins[2], self.unscaled_margins[3], b_margin } if self.sync_t_b_page_margins then self.unscaled_margins[2] = b_margin -- Let ConfigDialog know so it can update it on screen and have it saved on quit self.configurable.t_page_margin = b_margin end self.ui:handleEvent(Event:new("SetPageMargins", self.unscaled_margins, when_applied_callback)) end function ReaderTypeset:onSetPageTopAndBottomMargin(t_b_margins, when_applied_callback) local t_margin, b_margin = t_b_margins[1], t_b_margins[2] self.unscaled_margins = { self.unscaled_margins[1], t_margin, self.unscaled_margins[3], b_margin } if t_margin ~= b_margin then -- Set Sync T/B Margins toggle to off, as user explicitly made them differ self.sync_t_b_page_margins = false self.configurable.sync_t_b_page_margins = 0 end self.ui:handleEvent(Event:new("SetPageMargins", self.unscaled_margins, when_applied_callback)) end function ReaderTypeset:onSyncPageTopBottomMargins(toggle, when_applied_callback) self.sync_t_b_page_margins = not self.sync_t_b_page_margins if self.sync_t_b_page_margins then -- Adjust current top and bottom margins if needed if self.unscaled_margins[2] ~= self.unscaled_margins[4] then -- Taking the rounded mean can change the vertical page height, -- and so the previous lines layout. We could have used the mean -- for the top, and the delta from the mean for the bottom (and -- have them possibly not equal), but as these are unscaled here, -- and later scaled, the end result could still be different. -- So just take the mean and make them equal. local mean_margin = Math.round((self.unscaled_margins[2] + self.unscaled_margins[4]) / 2) self.configurable.t_page_margin = mean_margin self.configurable.b_page_margin = mean_margin self.unscaled_margins = { self.unscaled_margins[1], mean_margin, self.unscaled_margins[3], mean_margin } self.ui:handleEvent(Event:new("SetPageMargins", self.unscaled_margins, when_applied_callback)) when_applied_callback = nil end end if when_applied_callback then when_applied_callback() end end function ReaderTypeset:onSetPageMargins(margins, when_applied_callback) local left = Screen:scaleBySize(margins[1]) local top = Screen:scaleBySize(margins[2]) local right = Screen:scaleBySize(margins[3]) local bottom if self.view.footer.reclaim_height then bottom = Screen:scaleBySize(margins[4]) else bottom = Screen:scaleBySize(margins[4]) + self.view.footer:getHeight() end self.ui.document:setPageMargins(left, top, right, bottom) self.ui:handleEvent(Event:new("UpdatePos")) if when_applied_callback then -- Provided when hide_on_apply, and ConfigDialog temporarily hidden: -- show an InfoMessage with the unscaled & scaled values, -- and call when_applied_callback on dismiss UIManager:show(InfoMessage:new{ text = T(_([[ Margins set to: left: %1 (%2px) right: %3 (%4px) top: %5 (%6px) bottom: %7 (%8px) Tap to dismiss.]]), margins[1], left, margins[3], right, margins[2], top, margins[4], bottom), dismiss_callback = when_applied_callback, }) end end return ReaderTypeset