From 5e3c554dd780a3f062e0b1bd8002eda3e1926e20 Mon Sep 17 00:00:00 2001 From: Jellby Date: Sat, 31 Oct 2020 10:40:36 +0100 Subject: [PATCH] Hide non-linear fragments Add option to hide (skip) non-linear fragments, only working in 1-page mode. Tweaks mostly to footer, toc and skim code to make it clear(er) which pages belong to linear or non-linear fragments. --- base | 2 +- .../apps/reader/modules/readerbookmark.lua | 21 ++ frontend/apps/reader/modules/readerfooter.lua | 109 ++++++++-- frontend/apps/reader/modules/readergoto.lua | 47 ++++- frontend/apps/reader/modules/readerpaging.lua | 4 +- .../apps/reader/modules/readerrolling.lua | 106 +++++++++- frontend/apps/reader/modules/readertoc.lua | 89 ++++++-- frontend/apps/reader/skimtowidget.lua | 1 + frontend/document/credocument.lua | 194 +++++++++++++++++- frontend/document/document.lua | 42 ++++ frontend/ui/elements/reader_menu_order.lua | 1 + frontend/ui/widget/progresswidget.lua | 44 +++- 12 files changed, 600 insertions(+), 60 deletions(-) diff --git a/base b/base index dd34e3497..514ac803f 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit dd34e3497ba30bdfe300d4dd069fefc432263814 +Subproject commit 514ac803f17b8fd2db9c85e32fd4118ab15ed85b diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 2fa3f08b7..44c5f6ced 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -257,6 +257,13 @@ function ReaderBookmark:onShowBookmark() page = self.ui.pagemap:getXPointerPageLabel(page, true) else page = self.ui.document:getPageFromXPointer(page) + if self.ui.document:hasHiddenFlows() then + local flow = self.ui.document:getPageFlow(page) + page = self.ui.document:getPageNumberInFlow(page) + if flow > 0 then + page = T("[%1]%2", page, flow) + end + end end end if v.text == nil or v.text == "" then @@ -491,6 +498,13 @@ function ReaderBookmark:updateBookmark(item) for i=1, #self.bookmarks do if item.datetime == self.bookmarks[i].datetime and item.page == self.bookmarks[i].page then local page = self.ui.document:getPageFromXPointer(item.updated_highlight.pos0) + if self.ui.document:hasHiddenFlows() then + local flow = self.ui.document:getPageFlow(page) + page = self.ui.document:getPageNumberInFlow(page) + if flow > 0 then + page = T("[%1]%2", page, flow) + end + end local new_text = item.updated_highlight.text self.bookmarks[i].page = item.updated_highlight.pos0 self.bookmarks[i].pos0 = item.updated_highlight.pos0 @@ -517,6 +531,13 @@ function ReaderBookmark:renameBookmark(item, from_highlight) local page = item.page if not self.ui.document.info.has_pages then page = self.ui.document:getPageFromXPointer(page) + if self.ui.document:hasHiddenFlows() then + local flow = self.ui.document:getPageFlow(page) + page = self.ui.document:getPageNumberInFlow(page) + if flow > 0 then + page = T("[%1]%2", page, flow) + end + end end item.text = T(_("Page %1 %2 @ %3"), page, item.notes, item.datetime) end diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 3837388bb..8a3dfcd0f 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -191,7 +191,19 @@ local footerTextGeneratorMap = { return ("%s / %s"):format(footer.ui.pagemap:getCurrentPageLabel(true), footer.ui.pagemap:getLastPageLabel(true)) end - return ("%d / %d"):format(footer.pageno, footer.pages) + if footer.ui.document:hasHiddenFlows() then + -- i.e., if we are hiding non-linear fragments and there's anything to hide, + local flow = footer.ui.document:getPageFlow(footer.pageno) + local page = footer.ui.document:getPageNumberInFlow(footer.pageno) + local pages = footer.ui.document:getTotalPagesInFlow(flow) + if flow == 0 then + return ("%d // %d"):format(page, pages) + else + return ("[%d / %d]%d"):format(page, pages, flow) + end + else + return ("%d / %d"):format(footer.pageno, footer.pages) + end elseif footer.position then return ("%d / %d"):format(footer.position, footer.doc_height) end @@ -200,32 +212,36 @@ local footerTextGeneratorMap = { local symbol_type = footer.settings.item_prefix or "icons" local prefix = symbol_prefix[symbol_type].pages_left local left = footer.ui.toc:getChapterPagesLeft(footer.pageno) - return prefix .. " " .. (left and left or footer.pages - footer.pageno) + return prefix .. " " .. (left or footer.ui.document:getTotalPagesLeft(footer.pageno)) end, percentage = function(footer) local symbol_type = footer.settings.item_prefix or "icons" local prefix = symbol_prefix[symbol_type].percentage local digits = footer.settings.progress_pct_format or "0" - local string_percentage - if not prefix then - string_percentage = "%." .. digits .. "f%%" - else - string_percentage = prefix .. " %." .. digits .. "f%%" + local string_percentage = "%." .. digits .. "f%%" + if footer.ui.document:hasHiddenFlows() then + local flow = footer.ui.document:getPageFlow(footer.pageno) + if flow ~= 0 then + string_percentage = "[" .. string_percentage .. "]" + end + end + if prefix then + string_percentage = prefix .. " " .. string_percentage end return string_percentage:format(footer.progress_bar.percentage * 100) end, book_time_to_read = function(footer) local symbol_type = footer.settings.item_prefix or "icons" local prefix = symbol_prefix[symbol_type].book_time_to_read - local current_page = footer.ui:getCurrentPage() - return footer:getDataFromStatistics(prefix .. " ", footer.pages - current_page) + local left = footer.ui.document:getTotalPagesLeft(footer.pageno) + return footer:getDataFromStatistics(prefix .. " ", left) end, chapter_time_to_read = function(footer) local symbol_type = footer.settings.item_prefix or "icons" local prefix = symbol_prefix[symbol_type].chapter_time_to_read local left = footer.ui.toc:getChapterPagesLeft(footer.pageno) return footer:getDataFromStatistics( - prefix .. " ", (left and left or footer.pages - footer.pageno)) + prefix .. " ", (left or footer.ui.document:getTotalPagesLeft(footer.pageno))) end, mem_usage = function(footer) local symbol_type = footer.settings.item_prefix or "icons" @@ -1724,18 +1740,44 @@ function ReaderFooter:setTocMarkers(reset) if self.settings.disable_progress_bar or self.settings.progress_style_thin then return end if reset then self.progress_bar.ticks = nil - self.pages = self.view.document:getPageCount() + self.pages = self.ui.document:getPageCount() end if self.settings.toc_markers then self.progress_bar.tick_width = Screen:scaleBySize(self.settings.toc_markers_width) if self.progress_bar.ticks ~= nil then -- already computed return end - self.progress_bar.ticks = {} - if self.ui.toc then - self.progress_bar.ticks = self.ui.toc:getTocTicksFlattened() + if self.ui.document:hasHiddenFlows() and self.pageno then + local flow = self.ui.document:getPageFlow(self.pageno) + self.progress_bar.ticks = {} + if self.ui.toc then + -- filter the ticks to show only those in the current flow + for n, pageno in ipairs(self.ui.toc:getTocTicksFlattened()) do + if self.ui.document:getPageFlow(pageno) == flow then + table.insert(self.progress_bar.ticks, self.ui.document:getPageNumberInFlow(pageno)) + end + end + end + self.progress_bar.last = self.ui.document:getTotalPagesInFlow(flow) + else + if self.ui.toc then + self.progress_bar.ticks = self.ui.toc:getTocTicksFlattened() + end + if self.view.view_mode == "page" then + self.progress_bar.last = self.pages or self.ui.document:getPageCount() + else + -- in scroll mode, convert pages to positions + if self.ui.toc then + self.progress_bar.ticks = {} + for n, pageno in ipairs(self.ui.toc:getTocTicksFlattened()) do + local idx = self.ui.toc:getTocIndexByPage(pageno) + local pos = self.ui.document:getPosFromXPointer(self.ui.toc.toc[idx].xpointer) + table.insert(self.progress_bar.ticks, pos) + end + end + self.progress_bar.last = self.doc_height or self.ui.document.info.doc_height + end end - self.progress_bar.last = self.pages or self.view.document:getPageCount() else self.progress_bar.ticks = nil end @@ -1770,7 +1812,14 @@ end function ReaderFooter:updateFooterPage(force_repaint, force_recompute) if type(self.pageno) ~= "number" then return end - self.progress_bar.percentage = self.pageno / self.pages + if self.ui.document:hasHiddenFlows() then + local flow = self.ui.document:getPageFlow(self.pageno) + local page = self.ui.document:getPageNumberInFlow(self.pageno) + local pages = self.ui.document:getTotalPagesInFlow(flow) + self.progress_bar.percentage = page / pages + else + self.progress_bar.percentage = self.pageno / self.pages + end self:updateFooterText(force_repaint, force_recompute) end @@ -1882,19 +1931,39 @@ function ReaderFooter:_updateFooterText(force_repaint, force_recompute) end end +function ReaderFooter:onTocReset() + self:setTocMarkers(true) + if self.view.view_mode == "page" then + self:updateFooterPage() + else + self:updateFooterPos() + end +end + function ReaderFooter:onPageUpdate(pageno) + local toc_markers_update = false + if self.ui.document:hasHiddenFlows() then + local flow = self.pageno and self.ui.document:getPageFlow(self.pageno) + local new_flow = pageno and self.ui.document:getPageFlow(pageno) + if pageno and new_flow ~= flow then + toc_markers_update = true + end + end self.pageno = pageno - self.pages = self.view.document:getPageCount() + self.pages = self.ui.document:getPageCount() + if toc_markers_update then + self:setTocMarkers(true) + end self.ui.doc_settings:saveSetting("doc_pages", self.pages) -- for Book information self:updateFooterPage() end function ReaderFooter:onPosUpdate(pos, pageno) self.position = pos - self.doc_height = self.view.document.info.doc_height + self.doc_height = self.ui.document.info.doc_height if pageno then self.pageno = pageno - self.pages = self.view.document:getPageCount() + self.pages = self.ui.document:getPageCount() self.ui.doc_settings:saveSetting("doc_pages", self.pages) -- for Book information end self:updateFooterPos() @@ -2065,7 +2134,7 @@ function ReaderFooter:refreshFooter(refresh, signal) -- We *do* need to ensure we at least re-compute the footer layout, though, especially when going from visible to invisible... self:onUpdateFooter(refresh and not signal, refresh and signal) if signal then - self.ui:handleEvent(Event:new("SetPageBottomMargin", self.view.document.configurable.b_page_margin)) + self.ui:handleEvent(Event:new("SetPageBottomMargin", self.ui.document.configurable.b_page_margin)) end end diff --git a/frontend/apps/reader/modules/readergoto.lua b/frontend/apps/reader/modules/readergoto.lua index 9cc208150..a02aaa773 100644 --- a/frontend/apps/reader/modules/readergoto.lua +++ b/frontend/apps/reader/modules/readergoto.lua @@ -62,6 +62,12 @@ function ReaderGoto:onShowGotoDialog() self.goto_dialog = InputDialog:new{ title = dialog_title, input_hint = input_hint, + description = self.document:hasHiddenFlows() and + _([[ +x for an absolute page number +[x] for a page number in the main (linear) flow +[x]y for a page number in the non-linear fragment y]]) + or nil, buttons = { { { @@ -134,20 +140,51 @@ function ReaderGoto:gotoPage() end end self:close() + elseif self.ui.document:hasHiddenFlows() then + -- if there are hidden flows, we accept the syntax [x]y + -- for page number x in flow number y (y defaults to 0 if not present) + local flow + number, flow = string.match(page_number, "^ *%[(%d+)%](%d*) *$") + flow = tonumber(flow) or 0 + number = tonumber(number) + if number then + if self.ui.document.flows[flow] ~= nil then + if number < 1 or number > self.ui.document:getTotalPagesInFlow(flow) then + return + end + local page = 0 + -- in flow 0 (linear), we count pages skipping non-linear flows, + -- in a non-linear flow the target page is immediate + if flow == 0 then + for i=1, number do + page = self.ui.document:getNextPage(page) + end + else + page = self.ui.document:getFirstPageInFlow(flow) + number - 1 + end + if page > 0 then + self.ui:handleEvent(Event:new("GotoPage", page)) + self:close() + end + end + end end end function ReaderGoto:onGoToBeginning() - self.ui.link:addCurrentLocationToStack() - self.ui:handleEvent(Event:new("GotoPage", 1)) + local new_page = self.ui.document:getNextPage(0) + if new_page then + self.ui.link:addCurrentLocationToStack() + self.ui:handleEvent(Event:new("GotoPage", new_page)) + end return true end function ReaderGoto:onGoToEnd() - local endpage = self.document:getPageCount() - if endpage then + local new_page = self.ui.document:getPrevPage(0) + if new_page then self.ui.link:addCurrentLocationToStack() - self.ui:handleEvent(Event:new("GotoPage", endpage)) + self.ui:handleEvent(Event:new("GotoPage", new_page)) end return true end diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index 35e613dae..9296c1ce0 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -1034,7 +1034,7 @@ end -- mode, and other zoom modes than Fit page function ReaderPaging:onGotoNextChapter() local pageno = self.current_page - local new_page = self.ui.toc:getNextChapter(pageno, 0) + local new_page = self.ui.toc:getNextChapter(pageno) if new_page then self.ui.link:addCurrentLocationToStack() self:onGotoPage(new_page) @@ -1044,7 +1044,7 @@ end function ReaderPaging:onGotoPrevChapter() local pageno = self.current_page - local new_page = self.ui.toc:getPreviousChapter(pageno, 0) + local new_page = self.ui.toc:getPreviousChapter(pageno) if new_page then self.ui.link:addCurrentLocationToStack() self:onGotoPage(new_page) diff --git a/frontend/apps/reader/modules/readerrolling.lua b/frontend/apps/reader/modules/readerrolling.lua index 16d3f9752..ad1b13f54 100644 --- a/frontend/apps/reader/modules/readerrolling.lua +++ b/frontend/apps/reader/modules/readerrolling.lua @@ -55,7 +55,8 @@ local ReaderRolling = InputContainer:new{ -- With visible_pages=2, in 2-pages mode, ensure the first -- page is always odd or even (odd is logical to avoid a -- same page when turning first 2-pages set of document) - odd_or_even_first_page = 1 -- 1 = odd, 2 = even, nil or others = free + odd_or_even_first_page = 1, -- 1 = odd, 2 = even, nil or others = free + hide_nonlinear_flows = nil, } function ReaderRolling:init() @@ -206,7 +207,7 @@ function ReaderRolling:onReadSettings(config) self.setupXpointer = function() self.xpointer = self.ui.document:getXPointer() if self.view.view_mode == "page" then - self.ui:handleEvent(Event:new("PageUpdate", 1)) + self.ui:handleEvent(Event:new("PageUpdate", self.ui.document:getNextPage(0))) end end end @@ -230,6 +231,12 @@ function ReaderRolling:onReadSettings(config) G_reader_settings:readSetting("copt_visible_pages") or 1 self.ui.document:setVisiblePageCount(self.visible_pages) + self.hide_nonlinear_flows = config:readSetting("hide_nonlinear_flows") + if self.hide_nonlinear_flows == nil then + self.hide_nonlinear_flows = G_reader_settings:isTrue("hide_nonlinear_flows") + end + self.ui.document:setHideNonlinearFlows(self.hide_nonlinear_flows) + -- Set a callback to allow showing load and rendering progress -- (this callback will be cleaned up by cre.cpp closeDocument(), -- no need to handle it in :onCloseDocument() here.) @@ -302,10 +309,14 @@ function ReaderRolling:onSaveSettings() self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable) self.ui.doc_settings:saveSetting("inverse_reading_order", self.inverse_reading_order) self.ui.doc_settings:saveSetting("visible_pages", self.visible_pages) + self.ui.doc_settings:saveSetting("hide_nonlinear_flows", self.hide_nonlinear_flows) end function ReaderRolling:onReaderReady() self:setupTouchZones() + if self.hide_nonlinear_flows then + self.ui.document:cacheFlows() + end self.setupXpointer() end @@ -457,6 +468,31 @@ You can set how many lines are shown.]]) help_text = _([[When page overlap is enabled, some lines from the previous pages are shown on the next page.]]), sub_item_table = page_overlap_menu, } + if self.ui.document:hasNonLinearFlows() then + local hide_nonlinear_text = _("When this option is enabled, if a document contains non-linear fragments, they will be hidden from the normal page flow, but they are still accessible through links, Toc or Go to. This currently works only in single-page, non-scrolling mode.") + menu_items.hide_nonlinear_flows = { + text = _("Hide non-linear fragments"), + enabled_func = function() + return self.view.view_mode == "page" and self.ui.document:getVisiblePageCount() == 1 + end, + checked_func = function() return self.hide_nonlinear_flows end, + callback = function() + self:onToggleHideNonlinear() + end, + hold_callback = function() + UIManager:show(ConfirmBox:new{ + text = T( + hide_nonlinear_text .. _("\n\nSet default hide non-linear fragments to %1?"), + self.hide_nonlinear_flows and _("enabled") or _("disabled") + ), + ok_callback = function() + G_reader_settings:saveSetting("hide_nonlinear_flows", self.hide_nonlinear_flows) + end, + }) + end, + help_text = hide_nonlinear_text, + } + end end function ReaderRolling:getLastPercent() @@ -526,8 +562,18 @@ end function ReaderRolling:onGotoNextChapter() local visible_page_count = self.ui.document:getVisiblePageCount() local pageno = self.current_page + (visible_page_count > 1 and 1 or 0) - local new_page = self.ui.toc:getNextChapter(pageno, 0) - if new_page then + local new_page + if self.ui.document:hasHiddenFlows() then + -- Find next chapter start + new_page = self.ui.document:getNextPage(pageno) + while new_page > 0 do + if self.ui.toc:isChapterStart(new_page) then break end + new_page = self.ui.document:getNextPage(new_page) + end + else + new_page = self.ui.toc:getNextChapter(pageno) or 0 + end + if new_page > 0 then self.ui.link:addCurrentLocationToStack() self:onGotoPage(new_page) end @@ -536,8 +582,18 @@ end function ReaderRolling:onGotoPrevChapter() local pageno = self.current_page - local new_page = self.ui.toc:getPreviousChapter(pageno, 0) - if new_page then + local new_page + if self.ui.document:hasHiddenFlows() then + -- Find previous chapter start + new_page = self.ui.document:getPrevPage(pageno) + while new_page > 0 do + if self.ui.toc:isChapterStart(new_page) then break end + new_page = self.ui.document:getPrevPage(new_page) + end + else + new_page = self.ui.toc:getPreviousChapter(pageno) or 0 + end + if new_page > 0 then self.ui.link:addCurrentLocationToStack() self:onGotoPage(new_page) end @@ -721,7 +777,23 @@ function ReaderRolling:onGotoViewRel(diff) else diff = math.floor(diff) end - self:_gotoPage(self.current_page + diff*page_count) + local new_page = self.current_page + if self.ui.document:hasHiddenFlows() then + local test_page + for i=1, math.abs(diff*page_count) do + if diff > 0 then + test_page = self.ui.document:getNextPage(new_page) + else + test_page = self.ui.document:getPrevPage(new_page) + end + if test_page > 0 then + new_page = test_page + end + end + else + new_page = new_page + diff*page_count + end + self:_gotoPage(new_page) if diff > 0 and old_page == self.current_page then self.ui:handleEvent(Event:new("EndOfBook")) end @@ -783,12 +855,13 @@ function ReaderRolling:updatePos() local new_height = self.ui.document.info.doc_height local new_page = self.ui.document.info.number_of_pages if self.old_doc_height ~= new_height or self.old_page ~= new_page then + if self.hide_nonlinear_flows then + self.ui.document:cacheFlows() + end self:_gotoXPointer(self.xpointer) self.old_doc_height = new_height self.old_page = new_page self.ui:handleEvent(Event:new("UpdateToc")) - self.view.footer:setTocMarkers(true) - self.view.footer:onUpdateFooter() end self:updateTopStatusBarMarkers() UIManager:setDirty(self.view.dialog, "partial") @@ -808,7 +881,6 @@ function ReaderRolling:onChangeViewMode() self.old_doc_height = self.ui.document.info.doc_height self.old_page = self.ui.document.info.number_of_pages self.ui:handleEvent(Event:new("UpdateToc")) - self.view.footer:setTocMarkers(true) if self.xpointer then self:_gotoXPointer(self.xpointer) -- Ensure a whole screen refresh is always enqueued @@ -1310,6 +1382,20 @@ Note that %1 (out of %2) xpaths from your bookmarks and highlights have been nor }) end +function ReaderRolling:onToggleHideNonlinear() + self.hide_nonlinear_flows = not self.hide_nonlinear_flows + self.ui.document:setHideNonlinearFlows(self.hide_nonlinear_flows) + -- The document may change due to forced pagebreaks between flows being + -- added or removed, so we need to find our location + self:onUpdatePos() + -- Even if the document doesn't change, we must ensure that the + -- flow and call caches are cleared, to get the right page numbers, + -- which may have changed, and the correct flow structure. Also, + -- the footer needs updating, and TOC markers may come or go. + self.ui.document:cacheFlows() + self.ui:handleEvent(Event:new("UpdateToc")) +end + -- Duplicated in ReaderPaging function ReaderRolling:onToggleReadingOrder() self.inverse_reading_order = not self.inverse_reading_order diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua index a27ef20f3..8cb075d2e 100644 --- a/frontend/apps/reader/modules/readertoc.lua +++ b/frontend/apps/reader/modules/readertoc.lua @@ -62,6 +62,7 @@ end function ReaderToc:onUpdateToc() self:resetToc() + self.ui:handleEvent(Event:new("TocReset")) --- @note: Let this propagate, plugins/statistics uses it to react to changes in document pagination --return true @@ -168,25 +169,35 @@ function ReaderToc:validateAndFixToc() nb_bogus = nb_bogus + 1 -- See how many pages we'd need fixing on either side local nb_prev = 0 + local nb_prev_main = 0 for j = i-1, first, -1 do local ppage = toc[j].fixed_page or toc[j].page if ppage <= page then break else nb_prev = nb_prev + 1 + if self.ui.document:getPageFlow(ppage) == 0 then + nb_prev_main = nb_prev_main + 1 + end end end - local nb_next = 1 - for j = i+1, last do + local nb_next = 0 + local nb_next_main = 0 + for j = i, last do local npage = toc[j].fixed_page or toc[j].page if npage >= cur_page then break else nb_next = nb_next + 1 + if self.ui.document:getPageFlow(npage) == 0 then + nb_next_main = nb_next_main + 1 + end end end logger.dbg("BOGUS TOC:", i, page, "<", i-1, cur_page, "-", nb_prev, nb_next) - if nb_prev <= nb_next then -- less changes when fixing previous pages + -- Note: by comparing only the entries that belong to the main (linear) flow + -- we give priority to moving non-linear bogus entries + if nb_prev_main <= nb_next_main then -- less changes when fixing previous pages local fixed_page if i-nb_prev-1 >= 1 then fixed_page = toc[i-nb_prev-1].fixed_page or toc[i-nb_prev-1].page @@ -436,21 +447,46 @@ function ReaderToc:isChapterEnd(cur_pageno) end function ReaderToc:getChapterPagesLeft(pageno) - --if self:isChapterEnd(pageno) then return 0 end - local next_chapter = self:getNextChapter(pageno) - if next_chapter then - next_chapter = next_chapter - pageno - 1 + if self.ui.document:hasHiddenFlows() then + -- Count pages until new chapter + local pages_left = 0 + local test_page = self.ui.document:getNextPage(pageno) + while test_page > 0 do + pages_left = pages_left + 1 + if self:isChapterStart(test_page) then + return pages_left - 1 + end + test_page = self.ui.document:getNextPage(test_page) + end + else + local next_chapter = self:getNextChapter(pageno) + if next_chapter then + next_chapter = next_chapter - pageno - 1 + end + return next_chapter end - return next_chapter end function ReaderToc:getChapterPagesDone(pageno) if self:isChapterStart(pageno) then return 0 end - local previous_chapter = self:getPreviousChapter(pageno) - if previous_chapter then - previous_chapter = pageno - previous_chapter + if self.ui.document:hasHiddenFlows() then + -- Count pages until chapter start + local pages_done = 0 + local test_page = self.ui.document:getPrevPage(pageno) + while test_page > 0 do + pages_done = pages_done + 1 + if self:isChapterStart(test_page) then + return pages_done + end + test_page = self.ui.document:getPrevPage(test_page) + end + else + local previous_chapter = self:getPreviousChapter(pageno) + if previous_chapter then + previous_chapter = pageno - previous_chapter + end + return previous_chapter end - return previous_chapter end function ReaderToc:updateCurrentNode() @@ -489,7 +525,34 @@ function ReaderToc:onShowToc() for _,v in ipairs(self.toc) do v.text = self.toc_indent:rep(v.depth-1)..self:cleanUpTocTitle(v.title) v.mandatory = v.page - if v.orig_page then -- bogus page fixed: show original page number + if self.ui.document:hasHiddenFlows() then + local flow = self.ui.document:getPageFlow(v.page) + if v.orig_page then -- bogus page fixed: show original page number + -- This is an ugly piece of code, which can result in an ugly TOC, + -- but it shouldn't be needed very often, only when bogus page numbers + -- are fixed, and then showing everything gets complicated + local orig_flow = self.ui.document:getPageFlow(v.orig_page) + if flow == 0 and orig_flow == flow then + v.mandatory = T("(%1) %2", self.ui.document:getPageNumberInFlow(v.orig_page), self.ui.document:getPageNumberInFlow(v.page)) + elseif flow == 0 and orig_flow ~= flow then + v.mandatory = T("[%1]%2", self.ui.document:getPageNumberInFlow(v.orig_page), self.ui.document:getPageFlow(v.orig_page)) + elseif flow > 0 and orig_flow == flow then + v.mandatory = T("[(%1) %2]%3", self.ui.document:getPageNumberInFlow(v.orig_page), + self.ui.document:getPageNumberInFlow(v.page), self.ui.document:getPageFlow(v.page)) + else + v.mandatory = T("([%1]%2) [%3]%4", self.ui.document:getPageNumberInFlow(v.orig_page), self.ui.document:getPageFlow(v.orig_page), + self.ui.document:getPageNumberInFlow(v.page), self.ui.document:getPageFlow(v.page)) + end + else + -- Plain numbers for the linear entries, + -- for non-linear entries we use the same syntax as in the Go to dialog + if flow == 0 then + v.mandatory = self.ui.document:getPageNumberInFlow(v.page) + else + v.mandatory = T("[%1]%2", self.ui.document:getPageNumberInFlow(v.page), self.ui.document:getPageFlow(v.page)) + end + end + elseif v.orig_page then -- bogus page fixed: show original page number v.mandatory = T("(%1) %2", v.orig_page, v.page) end if self.ui.pagemap and self.ui.pagemap:wantsPageLabels() then diff --git a/frontend/apps/reader/skimtowidget.lua b/frontend/apps/reader/skimtowidget.lua index 428b950de..bc16ad35b 100644 --- a/frontend/apps/reader/skimtowidget.lua +++ b/frontend/apps/reader/skimtowidget.lua @@ -92,6 +92,7 @@ function SkimToWidget:init() ticks = self.ticks_flattened, tick_width = Size.line.medium, last = self.page_count, + alt = self.ui.document.flows, } self.skimto_progress = FrameContainer:new{ padding = Size.padding.button, diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 2fc02e920..5b6ae0745 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -58,6 +58,11 @@ local CreDocument = Document:new{ default_css = "./data/cr3.css", provider = "crengine", provider_name = "Cool Reader Engine", + + hide_nonlinear_flows = false, + flows = {}, + page_in_flow = {}, + last_linear_page = nil, } -- NuPogodi, 20.05.12: inspect the zipfile content @@ -283,10 +288,188 @@ function CreDocument:updateColorRendering() end end +function CreDocument:setHideNonlinearFlows(hide_nonlinear_flows) + if hide_nonlinear_flows ~= self.hide_nonlinear_flows then + self.hide_nonlinear_flows = hide_nonlinear_flows + self._document:setIntProperty("crengine.doc.nonlinear.pagebreak.force", self.hide_nonlinear_flows and 1 or 0) + end +end + function CreDocument:getPageCount() return self._document:getPages() end +-- Whether the document has any non-linear flow to care about +function CreDocument:hasNonLinearFlows() + return self._document:hasNonLinearFlows() +end + +-- Whether non-linear flows (if any) will be hidden +function CreDocument:hasHiddenFlows() + return self.flows[1] ~= nil +end + +-- Get the next/prev page number, skipping non-linear flows, +-- i.e. the next/prev page that is either in the current +-- flow or in the linear flow (flow 0) +-- If "page" is 0, these give the initial and final linear pages +function CreDocument:getNextPage(page) + if self:hasHiddenFlows() then + if page < 0 or page >= self:getPageCount() then + return 0 + elseif page == 0 then + return self:getFirstPageInFlow(0) + end + local flow = self:getPageFlow(page) + local start_page = page + 1 + local end_page = self:getLastLinearPage() + local test_page = start_page + -- max to ensure at least one iteration + -- (in case the current flow goes after all linear pages) + while test_page <= math.max(end_page, start_page) do + local test_page_flow = self:getPageFlow(test_page) + if test_page_flow == flow or test_page_flow == 0 then + -- same flow as current, or linear flow, this is a "good" page + return test_page + elseif test_page_flow > 0 then + -- some other non-linear flow, skip all pages in this flow + test_page = test_page + self:getTotalPagesInFlow(test_page_flow) + else + -- went beyond the last page + break + end + end + return 0 + else + return Document.getNextPage(self, page) + end +end + +function CreDocument:getPrevPage(page) + if self:hasHiddenFlows() then + if page < 0 or page > self:getPageCount() then + return 0 + elseif page == 0 then + return self:getLastLinearPage() + end + local flow = self:getPageFlow(page) + local start_page = page - 1 + local end_page = self:getFirstPageInFlow(0) + local test_page = start_page + -- min to ensure at least one iteration + -- (in case the current flow goes before all linear pages) + while test_page >= math.min(end_page, start_page) do + local test_page_flow = self:getPageFlow(test_page) + if test_page_flow == flow or test_page_flow == 0 then + -- same flow as current, or linear flow, this is a "good" page + return test_page + elseif test_page_flow > 0 then + -- some other non-linear flow, skip all pages in this flow + test_page = self:getFirstPageInFlow(test_page_flow) - 1 + else + -- went beyond the first page + break + end + end + return 0 + else + return Document.getPrevPage(self, page) + end +end + +function CreDocument:getPageFlow(page) + -- Only report non-linear pages if "hide_nonlinear_flows" is enabled, and in 1-page mode, + -- otherwise all pages are linear (flow 0) + if self.hide_nonlinear_flows and self._view_mode == self.PAGE_VIEW_MODE and self:getVisiblePageCount() == 1 then + return self._document:getPageFlow(page) + else + return 0 + end +end + +function CreDocument:getLastLinearPage() + return self.last_linear_page +end + +function CreDocument:getFirstPageInFlow(flow) + return self.flows[flow][1] +end + +function CreDocument:getTotalPagesInFlow(flow) + return self.flows[flow][2] +end + +function CreDocument:getPageNumberInFlow(page) + if self:hasHiddenFlows() then + return self.page_in_flow[page] + else + return page + end +end + +function CreDocument:cacheFlows() + -- Build the cache tables "flows" and "page_in_flow", if there are + -- any non-linear flows in the source document. Also set the value + -- of "last_linear_page", to possibly speed up counting in documents + -- with many non-linear pages at the end. + -- flows[i] contains {ini, num}, where ini is the first page in flow i, + -- and num is the total number of pages in the flow. + -- page_in_flow[i] contains the number of page i with its flow. + -- + -- So, flows[0][1] is the first page in the linear flow, + -- and page_in_flow[flows[0][1]] must be 1, because it is the first + self.flows = {} + self.page_in_flow = {} + if self:hasNonLinearFlows() and self.hide_nonlinear_flows then + for i=1,self:getPageCount() do + local flow = self:getPageFlow(i) + if self.flows[flow] ~= nil then + self.flows[flow][2] = self.flows[flow][2]+1 + else + self.flows[flow] = {i, 1} + end + self.page_in_flow[i] = self.flows[flow][2] + if flow == 0 then + self.last_linear_page = i + end + end + else + self.last_linear_page = self:getPageCount() + self.flows[0] = {1, self.last_linear_page} + end +end + +function CreDocument:getTotalPagesLeft(page) + if self:hasHiddenFlows() then + local pages_left + local last_linear = self:getLastLinearPage() + if page > last_linear then + -- If beyond the last linear page, count only the pages in the current flow + local flow = self:getPageFlow(page) + pages_left = self:getTotalPagesInFlow(flow) - self:getPageNumberInFlow(page) + else + -- Otherwise, count all pages until the last linear, + -- except the flows that start (and end) between + -- the current page and the last linear + pages_left = last_linear - page + for flow, tab in ipairs(self.flows) do + -- tab[1] is the initial page of the flow + -- tab[2] is the total number of pages in the flow + if tab[1] > last_linear then + break + end + -- strict >, to make sure we include pages in the current flow + if tab[1] > page then + pages_left = pages_left - tab[2] + end + end + end + return pages_left + else + return Document.getTotalPagesLeft(self, page) + end +end + function CreDocument:getCoverPageImage() -- no need to render document in order to get cover image if not self:loadDocument() then @@ -578,7 +761,7 @@ function CreDocument:gotoPos(pos) end function CreDocument:gotoPage(page) - logger.dbg("CreDocument: goto page", page) + logger.dbg("CreDocument: goto page", page, "flow", self:getPageFlow(page)) self._document:gotoPage(page) end @@ -766,6 +949,9 @@ function CreDocument:setViewMode(new_mode) self._view_mode = self.PAGE_VIEW_MODE end self._document:setViewMode(self._view_mode) + if self.hide_nonlinear_flows then + self:cacheFlows() + end end end @@ -1272,6 +1458,7 @@ function CreDocument:setupCallCache() elseif name:sub(1,6) == "enable" then add_reset = true elseif name == "zoomFont" then add_reset = true -- not used by koreader elseif name == "resetCallCache" then add_reset = true + elseif name == "cacheFlows" then add_reset = true -- These may have crengine do native highlight or unhighlight -- (we could keep the original buffer and use a scratch buffer while @@ -1313,6 +1500,11 @@ function CreDocument:setupCallCache() elseif name == "getCacheFilePath" then no_wrap = true elseif name == "getStatistics" then no_wrap = true elseif name == "getNormalizedXPointer" then no_wrap = true + elseif name == "getNextPage" then no_wrap = true + elseif name == "getPrevPage" then no_wrap = true + elseif name == "getPageFlow" then no_wrap = true + elseif name == "getPageNumberInFlow" then no_wrap = true + elseif name == "getTotalPagesLeft" then no_wrap = true -- Some get* have different results by page/pos elseif name == "getLinkFromPosition" then cache_by_tag = true diff --git a/frontend/document/document.lua b/frontend/document/document.lua index a8a61b428..7b5224478 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -188,6 +188,48 @@ function Document:getPageCount() return self.info.number_of_pages end +-- Some functions that look quite silly, but they can be +-- overridden for document types that support separate flows +-- (e.g. CreDocument) +function Document:hasNonLinearFlows() + return false +end + +function Document:hasHiddenFlows() + return false +end + +function Document:getNextPage(page) + local new_page = page + 1 + return (new_page > 0 and new_page < self.info.number_of_pages) and new_page or 0 +end + +function Document:getPrevPage(page) + if page == 0 then return self.info.number_of_pages end + local new_page = page - 1 + return (new_page > 0 and new_page < self.info.number_of_pages) and new_page or 0 +end + +function Document:getTotalPagesLeft(page) + return self.info.number_of_pages - page +end + +function Document:getPageFlow(page) + return 0 +end + +function Document:getFirstPageInFlow(flow) + return 1 +end + +function Document:getTotalPagesInFlow(flow) + return self.info.number_of_pages +end + +function Document:getPageNumberInFlow(page) + return page +end + -- calculates page dimensions function Document:getPageDimensions(pageno, zoom, rotation) local native_dimen = self:getNativePageDimensions(pageno):copy() diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 2dae3044f..2d43deb3e 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -16,6 +16,7 @@ local order = { "toggle_bookmark", "bookmark_browsing_mode", "page_map", + "hide_nonlinear_flows", "----------------------------", "go_to", "skim_to", diff --git a/frontend/ui/widget/progresswidget.lua b/frontend/ui/widget/progresswidget.lua index 85dbfa91f..c5dc60089 100644 --- a/frontend/ui/widget/progresswidget.lua +++ b/frontend/ui/widget/progresswidget.lua @@ -11,7 +11,8 @@ Configurable attributes: * bordersize * bordercolor * bgcolor - * rectcolor -- infill color + * altcolor -- alternate backrgound color for "alt" pages + * rectdim -- dim amount for infill * ticks (list) -- default to nil, use this if you want to insert markers * tick_width * last -- maximum tick, used with ticks @@ -42,13 +43,15 @@ local ProgressWidget = Widget:new{ bordersize = Screen:scaleBySize(1), bordercolor = Blitbuffer.COLOR_BLACK, bgcolor = Blitbuffer.COLOR_WHITE, - rectcolor = Blitbuffer.COLOR_DIM_GRAY, + altcolor = Blitbuffer.COLOR_LIGHT_GRAY, + rectdim = 2/3, percentage = nil, ticks = nil, tick_width = Screen:scaleBySize(3), last = nil, fill_from_right = false, allow_mirroring = true, + alt = nil, -- table with alternate pages to mark with different color (in the form {{ini1, len1}, {ini2, len2}, ...}) _mirroredUI = BD.mirroredUILayout(), _orig_margin_v = nil, _orig_bordersize = nil, @@ -73,27 +76,52 @@ function ProgressWidget:paintTo(bb, x, y) bb:paintBorder(x, y, my_size.w, my_size.h, self.bordersize, self.bordercolor, self.radius) + -- background for alternate pages (e.g. non-linear flows) + if self.alt and self.alt[1] ~= nil then + local bar_width = (my_size.w-2*self.margin_h) + local y_pos = y + self.margin_v + self.bordersize + local bar_height = my_size.h-2*(self.margin_v+self.bordersize) + for i=1, #self.alt do + local tick_x = bar_width*((self.alt[i][1]-1)/self.last) + local width = bar_width*(self.alt[i][2]/self.last) + width = math.ceil(tick_x + width) + tick_x = math.floor(tick_x) + width = width - tick_x + if self._mirroredUI then + tick_x = bar_width - tick_x - width + end + bb:paintRect( + x + self.margin_h + tick_x, + y_pos, + width, + bar_height, + self.altcolor) + end + end -- paint percentage infill + -- note that "lightenRect" is misleading, it actualy darkens stuff if self.percentage >= 0 and self.percentage <= 1 then if self.fill_from_right or (self._mirroredUI and not self.fill_from_right) then - bb:paintRect(x+self.margin_h + math.ceil((my_size.w-2*self.margin_h)*(1-self.percentage)), + bb:lightenRect(x+self.margin_h + math.ceil((my_size.w-2*self.margin_h)*(1-self.percentage)), math.ceil(y+self.margin_v+self.bordersize), math.ceil((my_size.w-2*self.margin_h)*self.percentage), my_size.h-2*(self.margin_v+self.bordersize), - self.rectcolor) + self.rectdim) else - bb:paintRect(x+self.margin_h, + bb:lightenRect(x+self.margin_h, math.ceil(y+self.margin_v+self.bordersize), math.ceil((my_size.w-2*self.margin_h)*self.percentage), - my_size.h-2*(self.margin_v+self.bordersize), self.rectcolor) + my_size.h-2*(self.margin_v+self.bordersize), + self.rectdim) end end + -- ticks if self.ticks and self.last and self.last > 0 then local bar_width = (my_size.w-2*self.margin_h) local y_pos = y + self.margin_v + self.bordersize local bar_height = my_size.h-2*(self.margin_v+self.bordersize) - for i=1, #self.ticks do - local tick_x = bar_width*(self.ticks[i]/self.last) + for i, tick in ipairs(self.ticks) do + local tick_x = bar_width*(tick/self.last) if self._mirroredUI then tick_x = bar_width - tick_x end