2016-12-25 20:13:30 +00:00
--[[--
ReaderView module handles all the screen painting for document browsing .
] ]
2014-11-17 13:44:13 +00:00
local AlphaContainer = require ( " ui/widget/container/alphacontainer " )
2017-05-22 14:21:23 +00:00
local Blitbuffer = require ( " ffi/blitbuffer " )
local ConfirmBox = require ( " ui/widget/confirmbox " )
2016-10-14 18:11:35 +00:00
local Device = require ( " device " )
2013-10-18 20:38:07 +00:00
local Geom = require ( " ui/geometry " )
local Event = require ( " ui/event " )
2017-05-22 14:21:23 +00:00
local ImageWidget = require ( " ui/widget/imagewidget " )
2019-08-05 16:38:10 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
2017-05-22 14:21:23 +00:00
local OverlapGroup = require ( " ui/widget/overlapgroup " )
local ReaderDogear = require ( " apps/reader/modules/readerdogear " )
local ReaderFlipping = require ( " apps/reader/modules/readerflipping " )
local ReaderFooter = require ( " apps/reader/modules/readerfooter " )
local UIManager = require ( " ui/uimanager " )
2016-04-19 06:50:36 +00:00
local dbg = require ( " dbg " )
2016-12-29 08:10:38 +00:00
local logger = require ( " logger " )
2014-10-15 12:31:24 +00:00
local _ = require ( " gettext " )
2017-05-22 14:21:23 +00:00
local Screen = Device.screen
local T = require ( " ffi/util " ) . template
2013-10-18 20:38:07 +00:00
2016-12-25 20:13:30 +00:00
local ReaderView = OverlapGroup : extend {
2014-03-13 13:52:43 +00:00
document = nil ,
-- single page state
state = {
page = nil ,
pos = 0 ,
zoom = 1.0 ,
rotation = 0 ,
gamma = 1.0 ,
offset = nil ,
bbox = nil ,
} ,
2014-10-22 13:34:11 +00:00
outer_page_color = Blitbuffer.gray ( DOUTER_PAGE_COLOR / 15 ) ,
2017-11-20 20:58:58 +00:00
-- highlight with "lighten" or "underscore" or "invert"
2014-03-13 13:52:43 +00:00
highlight = {
2014-10-22 13:34:11 +00:00
lighten_factor = 0.2 ,
2014-03-13 13:52:43 +00:00
temp_drawer = " invert " ,
temp = { } ,
saved_drawer = " lighten " ,
saved = { } ,
} ,
highlight_visible = true ,
-- PDF/DjVu continuous paging
page_scroll = nil ,
2014-10-22 13:34:11 +00:00
page_bgcolor = Blitbuffer.gray ( DBACKGROUND_COLOR / 15 ) ,
2014-03-13 13:52:43 +00:00
page_states = { } ,
scroll_mode = " vertical " ,
2016-11-05 15:12:19 +00:00
-- properties of the gap drawn between each page in scroll mode:
2014-03-13 13:52:43 +00:00
page_gap = {
2016-11-05 15:12:19 +00:00
-- width in pixels (when scrolling horizontally)
width = Screen : scaleBySize ( G_reader_settings : readSetting ( " page_gap_width " ) or 8 ) ,
-- height in pixels (when scrolling vertically)
height = Screen : scaleBySize ( G_reader_settings : readSetting ( " page_gap_height " ) or 8 ) ,
-- color (0 = white, 8 = gray, 15 = black)
color = Blitbuffer.gray ( ( G_reader_settings : readSetting ( " page_gap_color " ) or 8 ) / 15 ) ,
2014-03-13 13:52:43 +00:00
} ,
-- DjVu page rendering mode (used in djvu.c:drawPage())
render_mode = DRENDER_MODE , -- default to COLOR
-- Crengine view mode
view_mode = DCREREADER_VIEW_MODE , -- default to page mode
hinting = true ,
-- visible area within current viewing page
visible_area = Geom : new { x = 0 , y = 0 } ,
-- dimen for current viewing page
page_area = Geom : new { } ,
-- dimen for area to dim
2014-11-13 04:37:10 +00:00
dim_area = nil ,
2014-03-13 13:52:43 +00:00
-- has footer
footer_visible = nil ,
-- has dogear
dogear_visible = false ,
-- in flipping state
flipping_visible = false ,
2014-04-02 20:59:17 +00:00
2014-03-13 13:52:43 +00:00
-- auto save settings after turning pages
2014-04-02 20:59:17 +00:00
auto_save_paging_count = 0 ,
2016-07-13 06:53:23 +00:00
autoSaveSettings = function ( ) end
2012-05-18 22:50:26 +00:00
}
2013-02-24 11:49:23 +00:00
function ReaderView : init ( )
2016-12-25 20:13:30 +00:00
self.view_modules = { }
2014-04-30 15:19:24 +00:00
-- fix recalculate from close document pageno
self.state . page = nil
2014-11-13 04:37:10 +00:00
-- fix inherited dim_area for following opened documents
self : resetDimArea ( )
2015-03-12 06:35:43 +00:00
self : addWidgets ( )
2016-02-17 06:36:40 +00:00
self.emitHintPageEvent = function ( )
self.ui : handleEvent ( Event : new ( " HintPage " , self.hinting ) )
end
2013-02-24 11:49:23 +00:00
end
2014-11-13 04:37:10 +00:00
function ReaderView : resetDimArea ( )
self.dim_area = Geom : new { w = 0 , h = 0 }
end
2015-03-12 06:35:43 +00:00
function ReaderView : addWidgets ( )
2014-03-13 13:52:43 +00:00
self.dogear = ReaderDogear : new {
view = self ,
ui = self.ui ,
}
self.footer = ReaderFooter : new {
view = self ,
ui = self.ui ,
}
self.flipping = ReaderFlipping : new {
view = self ,
ui = self.ui ,
}
2014-11-17 13:44:13 +00:00
self.arrow = AlphaContainer : new {
alpha = 0.6 ,
ImageWidget : new {
file = " resources/icons/appbar.control.expand.png " ,
}
}
2014-03-13 13:52:43 +00:00
self [ 1 ] = self.dogear
self [ 2 ] = self.footer
self [ 3 ] = self.flipping
2013-02-23 18:25:57 +00:00
end
2016-12-25 20:13:30 +00:00
--[[--
Register a view UI widget module for document browsing .
@ tparam string name module name , registered widget can be accessed by readerui.view . view_modules [ name ] .
@ tparam ui.widget . widget.Widget widget paintable widget , i.e . has a paintTo method .
@ usage
local ImageWidget = require ( " ui/widget/imagewidget " )
local dummy_image = ImageWidget : new {
file = " resources/icons/appbar.control.expand.png " ,
}
-- the image will be painted on all book pages
readerui.view : registerViewModule ( ' dummy_image ' , dummy_image )
] ]
function ReaderView : registerViewModule ( name , widget )
if not widget.paintTo then
print ( name .. " view module does not have paintTo method! " )
return
end
widget.view = self
widget.ui = self.ui
self.view_modules [ name ] = widget
end
2015-03-12 06:35:43 +00:00
function ReaderView : resetLayout ( )
2016-12-25 20:13:30 +00:00
for _ , widget in ipairs ( self ) do
2016-03-08 06:42:46 +00:00
widget : resetLayout ( )
2015-03-12 06:35:43 +00:00
end
2016-12-25 20:13:30 +00:00
for _ , m in pairs ( self.view_modules ) do
if m.resetLayout then m : resetLayout ( ) end
end
2015-03-12 06:35:43 +00:00
end
2012-05-18 22:50:26 +00:00
function ReaderView : paintTo ( bb , x , y )
2016-08-12 09:47:35 +00:00
dbg : v ( " readerview painting " , self.visible_area , " to " , x , y )
2014-03-13 13:52:43 +00:00
if self.page_scroll then
self : drawPageBackground ( bb , x , y )
else
self : drawPageSurround ( bb , x , y )
end
-- draw page content
if self.ui . document.info . has_pages then
if self.page_scroll then
self : drawScrollPages ( bb , x , y )
else
self : drawSinglePage ( bb , x , y )
end
else
if self.view_mode == " page " then
self : drawPageView ( bb , x , y )
elseif self.view_mode == " scroll " then
self : drawScrollView ( bb , x , y )
end
end
-- dim last read area
2014-11-17 13:44:13 +00:00
if self.dim_area . w ~= 0 and self.dim_area . h ~= 0 then
if self.page_overlap_style == " dim " then
bb : dimRect (
self.dim_area . x , self.dim_area . y ,
self.dim_area . w , self.dim_area . h
)
elseif self.page_overlap_style == " arrow " then
self.arrow : paintTo ( bb , 0 , self.dim_area . h )
end
2014-03-13 13:52:43 +00:00
end
-- draw saved highlight
if self.highlight_visible then
self : drawSavedHighlight ( bb , x , y )
end
-- draw temporary highlight
if self.highlight . temp then
self : drawTempHighlight ( bb , x , y )
end
-- paint dogear
if self.dogear_visible then
self.dogear : paintTo ( bb , x , y )
end
-- paint footer
if self.footer_visible then
self.footer : paintTo ( bb , x , y )
end
-- paint flipping
if self.flipping_visible then
self.flipping : paintTo ( bb , x , y )
end
2016-12-25 20:13:30 +00:00
for _ , m in pairs ( self.view_modules ) do
m : paintTo ( bb , x , y )
end
2014-03-13 13:52:43 +00:00
-- stop activity indicator
self.ui : handleEvent ( Event : new ( " StopActivityIndicator " ) )
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
-- Most pages should not require dithering
self.dialog . dithered = nil
-- For KOpt, let the user choose.
if self.ui . document.info . has_pages then
2019-02-08 17:31:40 +00:00
-- Also enforce dithering in PicDocument
if self.ui . document.is_pic or self.document . configurable.hw_dithering == 1 then
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
self.dialog . dithered = true
end
else
-- Whereas for CRe,
-- If we're attempting to show a large enough amount of image data, request dithering (without triggering another repaint ;)).
local img_count , img_coverage = self.ui . document : getDrawnImagesStatistics ( )
-- With some nil guards because this may not be implemented in every engine ;).
if img_count and img_count > 0 and img_coverage and img_coverage >= 0.075 then
self.dialog . dithered = true
end
end
2012-05-18 22:50:26 +00:00
end
2013-04-23 22:59:52 +00:00
--[[
Given coordinates on the screen return position in original page
] ] --
function ReaderView : screenToPageTransform ( pos )
2014-03-13 13:52:43 +00:00
if self.ui . document.info . has_pages then
if self.page_scroll then
return self : getScrollPagePosition ( pos )
else
return self : getSinglePagePosition ( pos )
end
else
pos.page = self.ui . document : getCurrentPage ( )
2016-02-15 09:33:48 +00:00
-- local last_y = self.ui.document:getCurrentPos()
2016-12-29 08:10:38 +00:00
logger.dbg ( " document has no pages at " , pos )
2014-03-13 13:52:43 +00:00
return pos
end
2013-04-23 22:59:52 +00:00
end
--[[
Given rectangle in original page return rectangle on the screen
] ] --
function ReaderView : pageToScreenTransform ( page , rect )
2014-03-13 13:52:43 +00:00
if self.ui . document.info . has_pages then
if self.page_scroll then
return self : getScrollPageRect ( page , rect )
else
return self : getSinglePageRect ( rect )
end
else
return rect
end
2013-04-23 22:59:52 +00:00
end
2014-07-02 08:38:09 +00:00
--[[
Get page area on screen for a given page number
--]]
function ReaderView : getScreenPageArea ( page )
if self.ui . document.info . has_pages then
local area = Geom : new { x = 0 , y = 0 }
if self.page_scroll then
for _ , state in ipairs ( self.page_states ) do
if page ~= state.page then
area.y = area.y + state.visible_area . h + state.offset . y
area.y = area.y + self.page_gap . height
else
area.x = state.offset . x
area.w = state.visible_area . w
area.h = state.visible_area . h
return area
end
end
else
area.x = self.state . offset.x
area.y = self.state . offset.y
area.w = self.visible_area . w
area.h = self.visible_area . h
return area
end
else
2014-11-25 03:02:33 +00:00
return self.dimen
2014-07-02 08:38:09 +00:00
end
end
2013-03-10 06:23:26 +00:00
function ReaderView : drawPageBackground ( bb , x , y )
2014-03-13 13:52:43 +00:00
bb : paintRect ( x , y , self.dimen . w , self.dimen . h , self.page_bgcolor )
2013-03-10 06:23:26 +00:00
end
function ReaderView : drawPageSurround ( bb , x , y )
2014-03-13 13:52:43 +00:00
if self.dimen . h > self.visible_area . h then
bb : paintRect ( x , y , self.dimen . w , self.state . offset.y , self.outer_page_color )
bb : paintRect ( x , y + self.dimen . h - self.state . offset.y - 1 ,
self.dimen . w , self.state . offset.y + 1 , self.outer_page_color )
end
if self.dimen . w > self.visible_area . w then
bb : paintRect ( x , y , self.state . offset.x , self.dimen . h , self.outer_page_color )
bb : paintRect ( x + self.dimen . w - self.state . offset.x - 1 , y ,
self.state . offset.x + 1 , self.dimen . h , self.outer_page_color )
end
2013-03-10 06:23:26 +00:00
end
function ReaderView : drawScrollPages ( bb , x , y )
2014-03-13 13:52:43 +00:00
local pos = Geom : new { x = x , y = y }
for page , state in ipairs ( self.page_states ) do
self.ui . document : drawPage (
bb ,
pos.x + state.offset . x ,
pos.y + state.offset . y ,
state.visible_area ,
state.page ,
state.zoom ,
state.rotation ,
state.gamma ,
self.render_mode )
pos.y = pos.y + state.visible_area . h
-- draw page gap if not the last part
if page ~= # self.page_states then
self : drawPageGap ( bb , pos.x , pos.y )
pos.y = pos.y + self.page_gap . height
end
end
2016-02-17 06:36:40 +00:00
UIManager : nextTick ( self.emitHintPageEvent )
2013-03-10 06:23:26 +00:00
end
2013-06-15 15:13:19 +00:00
function ReaderView : getCurrentPageList ( )
2014-03-13 13:52:43 +00:00
local pages = { }
if self.ui . document.info . has_pages then
if self.page_scroll then
for _ , state in ipairs ( self.page_states ) do
table.insert ( pages , state.page )
end
else
table.insert ( pages , self.state . page )
end
end
return pages
2013-06-15 15:13:19 +00:00
end
2013-04-23 22:59:52 +00:00
function ReaderView : getScrollPagePosition ( pos )
2016-02-15 09:33:48 +00:00
local x_p , y_p
2014-03-13 13:52:43 +00:00
local x_s , y_s = pos.x , pos.y
for _ , state in ipairs ( self.page_states ) do
if y_s < state.visible_area . h + state.offset . y then
y_p = ( state.visible_area . y + y_s - state.offset . y ) / state.zoom
x_p = ( state.visible_area . x + x_s - state.offset . x ) / state.zoom
return {
x = x_p ,
y = y_p ,
page = state.page ,
zoom = state.zoom ,
rotation = state.rotation ,
}
else
y_s = y_s - state.visible_area . h - self.page_gap . height
end
end
2013-04-23 22:59:52 +00:00
end
function ReaderView : getScrollPageRect ( page , rect_p )
2014-03-13 13:52:43 +00:00
local rect_s = Geom : new { }
for _ , state in ipairs ( self.page_states ) do
local trans_p = Geom : new ( rect_p ) : copy ( )
trans_p : transformByScale ( state.zoom , state.zoom )
2014-10-21 09:24:19 +00:00
if page == state.page and state.visible_area : intersectWith ( trans_p ) then
2014-03-13 13:52:43 +00:00
rect_s.x = rect_s.x + state.offset . x + trans_p.x - state.visible_area . x
rect_s.y = rect_s.y + state.offset . y + trans_p.y - state.visible_area . y
rect_s.w = trans_p.w
rect_s.h = trans_p.h
return rect_s
end
rect_s.y = rect_s.y + state.visible_area . h + self.page_gap . height
end
2013-04-23 22:59:52 +00:00
end
2013-03-10 06:23:26 +00:00
function ReaderView : drawPageGap ( bb , x , y )
2014-03-13 13:52:43 +00:00
if self.scroll_mode == " vertical " then
bb : paintRect ( x , y , self.dimen . w , self.page_gap . height , self.page_gap . color )
elseif self.scroll_mode == " horizontal " then
bb : paintRect ( x , y , self.page_gap . width , self.dimen . h , self.page_gap . color )
end
2013-03-10 06:23:26 +00:00
end
function ReaderView : drawSinglePage ( bb , x , y )
2014-03-13 13:52:43 +00:00
self.ui . document : drawPage (
bb ,
x + self.state . offset.x ,
y + self.state . offset.y ,
self.visible_area ,
self.state . page ,
self.state . zoom ,
self.state . rotation ,
self.state . gamma ,
self.render_mode )
2016-02-17 06:36:40 +00:00
UIManager : nextTick ( self.emitHintPageEvent )
2013-03-10 06:23:26 +00:00
end
2013-04-23 22:59:52 +00:00
function ReaderView : getSinglePagePosition ( pos )
2014-03-13 13:52:43 +00:00
local x_s , y_s = pos.x , pos.y
return {
x = ( self.visible_area . x + x_s - self.state . offset.x ) / self.state . zoom ,
y = ( self.visible_area . y + y_s - self.state . offset.y ) / self.state . zoom ,
page = self.state . page ,
zoom = self.state . zoom ,
rotation = self.state . rotation ,
}
2013-04-23 22:59:52 +00:00
end
function ReaderView : getSinglePageRect ( rect_p )
2014-03-13 13:52:43 +00:00
local rect_s = Geom : new { }
local trans_p = Geom : new ( rect_p ) : copy ( )
trans_p : transformByScale ( self.state . zoom , self.state . zoom )
2014-10-21 09:24:19 +00:00
if self.visible_area : intersectWith ( trans_p ) then
2014-03-13 13:52:43 +00:00
rect_s.x = self.state . offset.x + trans_p.x - self.visible_area . x
rect_s.y = self.state . offset.y + trans_p.y - self.visible_area . y
rect_s.w = trans_p.w
rect_s.h = trans_p.h
return rect_s
end
2013-04-23 22:59:52 +00:00
end
2013-03-10 06:23:26 +00:00
function ReaderView : drawPageView ( bb , x , y )
2014-03-13 13:52:43 +00:00
self.ui . document : drawCurrentViewByPage (
bb ,
x + self.state . offset.x ,
y + self.state . offset.y ,
self.visible_area ,
self.state . page )
2013-03-10 06:23:26 +00:00
end
function ReaderView : drawScrollView ( bb , x , y )
2014-03-13 13:52:43 +00:00
self.ui . document : drawCurrentViewByPos (
bb ,
x + self.state . offset.x ,
y + self.state . offset.y ,
self.visible_area ,
self.state . pos )
2013-03-10 06:23:26 +00:00
end
2013-06-15 15:13:19 +00:00
function ReaderView : drawTempHighlight ( bb , x , y )
2014-03-13 13:52:43 +00:00
for page , boxes in pairs ( self.highlight . temp ) do
for i = 1 , # boxes do
local rect = self : pageToScreenTransform ( page , boxes [ i ] )
if rect then
self : drawHighlightRect ( bb , x , y , rect , self.highlight . temp_drawer )
end
end
end
2013-06-15 15:13:19 +00:00
end
function ReaderView : drawSavedHighlight ( bb , x , y )
2014-03-13 13:52:43 +00:00
if self.ui . document.info . has_pages then
self : drawPageSavedHighlight ( bb , x , y )
else
self : drawXPointerSavedHighlight ( bb , x , y )
end
2014-01-17 19:05:17 +00:00
end
function ReaderView : drawPageSavedHighlight ( bb , x , y )
2014-03-13 13:52:43 +00:00
local pages = self : getCurrentPageList ( )
for _ , page in pairs ( pages ) do
local items = self.highlight . saved [ page ]
if not items then items = { } end
for i = 1 , # items do
local item = items [ i ]
local pos0 , pos1 = item.pos0 , item.pos1
local boxes = self.ui . document : getPageBoxesFromPositions ( page , pos0 , pos1 )
if boxes then
for _ , box in pairs ( boxes ) do
local rect = self : pageToScreenTransform ( page , box )
if rect then
self : drawHighlightRect ( bb , x , y , rect , item.drawer or self.highlight . saved_drawer )
end
end -- end for each box
end -- end if boxes
end -- end for each highlight
end -- end for each page
2013-06-15 15:13:19 +00:00
end
2014-01-17 19:05:17 +00:00
function ReaderView : drawXPointerSavedHighlight ( bb , x , y )
2018-10-08 16:57:59 +00:00
-- Getting screen boxes is done for each tap on screen (changing pages,
-- showing menu...). We might want to cache these boxes per page (and
-- clear that cache when page layout change or highlights are added
-- or removed).
2019-03-13 12:05:50 +00:00
local cur_view_top , cur_view_bottom
2014-03-13 13:52:43 +00:00
for page , _ in pairs ( self.highlight . saved ) do
local items = self.highlight . saved [ page ]
if not items then items = { } end
for j = 1 , # items do
local item = items [ j ]
local pos0 , pos1 = item.pos0 , item.pos1
2017-11-20 20:58:58 +00:00
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check this item is on current page
2019-03-13 12:05:50 +00:00
if not cur_view_top then
-- Even in page mode, it's safer to use pos and ui.dimen.h
-- than pages' xpointers pos, even if ui.dimen.h is a bit
-- larger than pages' heights
cur_view_top = self.ui . document : getCurrentPos ( )
if self.view_mode == " page " and self.ui . document : getVisiblePageCount ( ) > 1 then
cur_view_bottom = cur_view_top + 2 * self.ui . dimen.h
else
cur_view_bottom = cur_view_top + self.ui . dimen.h
2019-02-26 06:16:43 +00:00
end
end
2019-03-13 12:05:50 +00:00
local spos0 = self.ui . document : getPosFromXPointer ( pos0 )
local spos1 = self.ui . document : getPosFromXPointer ( pos1 )
local start_pos = math.min ( spos0 , spos1 )
local end_pos = math.max ( spos0 , spos1 )
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
2018-10-08 16:57:59 +00:00
local boxes = self.ui . document : getScreenBoxesFromPositions ( pos0 , pos1 , true ) -- get_segments=true
2017-11-20 20:58:58 +00:00
if boxes then
for _ , box in pairs ( boxes ) do
local rect = self : pageToScreenTransform ( page , box )
if rect then
self : drawHighlightRect ( bb , x , y , rect , item.drawer or self.highlight . saved_drawer )
end
end -- end for each box
end -- end if boxes
end
2014-03-13 13:52:43 +00:00
end -- end for each highlight
end -- end for all saved highlight
2014-01-17 19:05:17 +00:00
end
2016-06-27 16:43:23 +00:00
function ReaderView : drawHighlightRect ( bb , _x , _y , rect , drawer )
2014-03-13 13:52:43 +00:00
local x , y , w , h = rect.x , rect.y , rect.w , rect.h
2014-04-02 20:59:17 +00:00
2014-03-13 13:52:43 +00:00
if drawer == " underscore " then
self.highlight . line_width = self.highlight . line_width or 2
2019-03-14 19:58:45 +00:00
self.highlight . line_color = self.highlight . line_color or Blitbuffer.COLOR_GRAY
2014-03-13 13:52:43 +00:00
bb : paintRect ( x , y + h - 1 , w ,
self.highlight . line_width ,
self.highlight . line_color )
elseif drawer == " lighten " then
2014-10-22 13:34:11 +00:00
bb : lightenRect ( x , y , w , h , self.highlight . lighten_factor )
2014-03-13 13:52:43 +00:00
elseif drawer == " invert " then
bb : invertRect ( x , y , w , h )
end
2013-04-23 22:59:52 +00:00
end
2013-03-10 06:23:26 +00:00
function ReaderView : getPageArea ( page , zoom , rotation )
2014-03-13 13:52:43 +00:00
if self.use_bbox then
return self.ui . document : getUsedBBoxDimensions ( page , zoom , rotation )
else
return self.ui . document : getPageDimensions ( page , zoom , rotation )
end
2013-03-10 06:23:26 +00:00
end
2012-12-08 06:05:10 +00:00
--[[
This method is supposed to be only used by ReaderPaging
--]]
2012-05-18 22:50:26 +00:00
function ReaderView : recalculate ( )
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
-- Start by resetting the dithering flag early, so it doesn't carry over from the previous page.
self.dialog . dithered = nil
2014-03-13 13:52:43 +00:00
if self.ui . document.info . has_pages and self.state . page then
self.page_area = self : getPageArea (
self.state . page ,
self.state . zoom ,
self.state . rotation )
-- reset our size
self.visible_area : setSizeTo ( self.dimen )
2014-08-06 14:06:34 +00:00
if self.ui . document.configurable . writing_direction == 0 then
-- starts from left top of page_area
self.visible_area . x = self.page_area . x
self.visible_area . y = self.page_area . y
else
-- start from right top of page_area
self.visible_area . x = self.page_area . x + self.page_area . w - self.visible_area . w
self.visible_area . y = self.page_area . y
end
2014-03-13 13:52:43 +00:00
-- and recalculate it according to page size
self.visible_area : offsetWithin ( self.page_area , 0 , 0 )
-- clear dim area
self.dim_area . w = 0
self.dim_area . h = 0
self.ui : handleEvent (
Event : new ( " ViewRecalculate " , self.visible_area , self.page_area ) )
else
self.visible_area : setSizeTo ( self.dimen )
end
self.state . offset = Geom : new { x = 0 , y = 0 }
if self.dimen . h > self.visible_area . h then
self.state . offset.y = ( self.dimen . h - self.visible_area . h ) / 2
end
if self.dimen . w > self.visible_area . w then
self.state . offset.x = ( self.dimen . w - self.visible_area . w ) / 2
end
-- flag a repaint so self:paintTo will be called
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
-- NOTE: This is also unfortunately called during panning, essentially making sure we'll never be using "fast" for pans ;).
2014-11-30 00:12:00 +00:00
UIManager : setDirty ( self.dialog , " partial " )
2012-05-18 22:50:26 +00:00
end
function ReaderView : PanningUpdate ( dx , dy )
2016-12-29 08:10:38 +00:00
logger.dbg ( " pan by " , dx , dy )
2014-03-13 13:52:43 +00:00
local old = self.visible_area : copy ( )
self.visible_area : offsetWithin ( self.page_area , dx , dy )
if self.visible_area ~= old then
-- flag a repaint
2019-02-11 02:28:46 +00:00
UIManager : setDirty ( self.dialog , " partial " )
2016-12-29 08:10:38 +00:00
logger.dbg ( " on pan: page_area " , self.page_area )
logger.dbg ( " on pan: visible_area " , self.visible_area )
2014-03-13 13:52:43 +00:00
self.ui : handleEvent (
Event : new ( " ViewRecalculate " , self.visible_area , self.page_area ) )
end
return true
2012-05-18 22:50:26 +00:00
end
2014-01-02 03:08:06 +00:00
function ReaderView : PanningStart ( x , y )
2016-12-29 08:10:38 +00:00
logger.dbg ( " panning start " , x , y )
2014-03-13 13:52:43 +00:00
if not self.panning_visible_area then
self.panning_visible_area = self.visible_area : copy ( )
end
self.visible_area = self.panning_visible_area : copy ( )
self.visible_area : offsetWithin ( self.page_area , x , y )
self.ui : handleEvent ( Event : new ( " ViewRecalculate " , self.visible_area , self.page_area ) )
2019-02-11 02:28:46 +00:00
UIManager : setDirty ( self.dialog , " partial " )
2014-01-02 03:08:06 +00:00
end
function ReaderView : PanningStop ( )
2014-03-13 13:52:43 +00:00
self.panning_visible_area = nil
2014-01-02 03:08:06 +00:00
end
function ReaderView : SetZoomCenter ( x , y )
2014-03-13 13:52:43 +00:00
local old = self.visible_area : copy ( )
self.visible_area : centerWithin ( self.page_area , x , y )
if self.visible_area ~= old then
self.ui : handleEvent ( Event : new ( " ViewRecalculate " , self.visible_area , self.page_area ) )
2014-11-30 00:12:00 +00:00
UIManager : setDirty ( self.dialog , " partial " )
2014-03-13 13:52:43 +00:00
end
2014-01-02 03:08:06 +00:00
end
2016-03-27 08:26:37 +00:00
function ReaderView : getViewContext ( )
if self.page_scroll then
return self.page_states
else
return {
{
page = self.state . page ,
pos = self.state . pos ,
zoom = self.state . zoom ,
rotation = self.state . rotation ,
gamma = self.state . gamma ,
offset = self.state . offset : copy ( ) ,
bbox = self.state . bbox ,
} ,
self.visible_area : copy ( ) ,
self.page_area : copy ( ) ,
}
end
end
function ReaderView : restoreViewContext ( ctx )
if self.page_scroll then
self.page_states = ctx
else
self.state = ctx [ 1 ]
self.visible_area = ctx [ 2 ]
self.page_area = ctx [ 3 ]
end
end
2018-12-28 03:32:42 +00:00
-- NOTE: This is just a shim for koptoptions, because we want to be able to pass an optional second argument to SetScreenMode...
-- This is also used as a sink for gsensor input events, because we can only send a single event per input,
-- and we need to cover both CRe & KOpt...
function ReaderView : onSwapScreenMode ( new_mode , rotation )
-- Don't do anything if an explicit rotation was requested, but it hasn't actually changed,
-- because we may be sending this event *right before* a ChangeScreenMode in CRe (gyro)
if rotation ~= nil and rotation ~= true and rotation == Screen : getRotationMode ( ) then
return true
end
-- CRe
self.ui : handleEvent ( Event : new ( " ChangeScreenMode " , new_mode , rotation or true ) )
-- KOpt (On CRe, since it's redundant (RR:onChangeScreenMode already sends one), this'll get discarded early)
self.ui : handleEvent ( Event : new ( " SetScreenMode " , new_mode , rotation or true ) )
end
function ReaderView : onSetScreenMode ( new_mode , rotation , noskip )
-- Don't do anything if an explicit rotation was requested, but it hasn't actually changed,
-- because we may be sending this event *right after* a ChangeScreenMode in CRe (gsensor)
-- We only want to let the onReadSettings one go through, otherwise the testsuite blows up...
if noskip == nil and rotation ~= nil and rotation ~= true and rotation == Screen : getRotationMode ( ) then
return true
end
2014-03-13 13:52:43 +00:00
if new_mode == " landscape " or new_mode == " portrait " then
self.screen_mode = new_mode
2018-12-28 03:32:42 +00:00
-- NOTE: Hacky hack! If rotation is "true", that's actually an "interactive" flag for setScreenMode
2019-08-23 17:53:53 +00:00
--- @fixme That's because we can't store nils in a table, which is what Event:new attempts to do ;).
-- c.f., <https://stackoverflow.com/q/7183998/> & <http://lua-users.org/wiki/VarargTheSecondClassCitizen>
2018-12-28 03:32:42 +00:00
-- With a fixed Event implementation, we'd instead stick "interactive" in a third argument,
-- which we could happily pass while still keeping rotation nil ;).
if rotation ~= nil and rotation ~= true then
2014-03-13 13:52:43 +00:00
Screen : setRotationMode ( rotation )
else
2018-12-28 03:32:42 +00:00
Screen : setScreenMode ( new_mode , rotation )
2014-03-13 13:52:43 +00:00
end
2014-12-05 21:25:32 +00:00
UIManager : setDirty ( self.dialog , " full " )
2016-12-04 06:57:57 +00:00
local new_screen_size = Screen : getSize ( )
self.ui : handleEvent ( Event : new ( " SetDimensions " , new_screen_size ) )
self.ui : onScreenResize ( new_screen_size )
2014-03-13 13:52:43 +00:00
self.ui : handleEvent ( Event : new ( " InitScrollPageStates " ) )
end
2018-12-28 03:32:42 +00:00
self.cur_rotation_mode = Screen : getRotationMode ( )
2014-03-13 13:52:43 +00:00
return true
2013-02-02 08:46:06 +00:00
end
2012-06-12 16:14:23 +00:00
function ReaderView : onSetDimensions ( dimensions )
2014-03-13 13:52:43 +00:00
self : resetLayout ( )
self.dimen = dimensions
-- recalculate view
self : recalculate ( )
2013-02-23 18:25:57 +00:00
end
function ReaderView : onRestoreDimensions ( dimensions )
2014-03-13 13:52:43 +00:00
self : resetLayout ( )
self.dimen = dimensions
-- recalculate view
self : recalculate ( )
2012-06-12 16:14:23 +00:00
end
2013-02-23 18:25:57 +00:00
function ReaderView : onSetFullScreen ( full_screen )
2014-03-13 13:52:43 +00:00
self.footer_visible = not full_screen
self.ui : handleEvent ( Event : new ( " SetDimensions " , Screen : getSize ( ) ) )
2013-02-23 18:25:57 +00:00
end
2016-03-27 22:39:47 +00:00
function ReaderView : onSetScrollMode ( page_scroll )
2019-08-05 16:38:10 +00:00
if self.ui . document.info . has_pages and page_scroll and self.ui . zooming.paged_modes [ self.zoom_mode ] then
UIManager : show ( InfoMessage : new {
2019-08-22 15:11:47 +00:00
text = _ ( [ [
Continuous view ( scroll mode ) works best with zoom to page width or zoom to content width .
2019-08-05 16:38:10 +00:00
In combination with zoom to fit page , page height , content height or content , continuous view can cause unexpected shifts when turning pages . ] ] ) ,
timeout = 5 ,
} )
end
2014-03-13 13:52:43 +00:00
self.page_scroll = page_scroll
self : recalculate ( )
self.ui : handleEvent ( Event : new ( " InitScrollPageStates " ) )
2013-03-10 06:23:26 +00:00
end
2012-10-09 22:26:01 +00:00
function ReaderView : onReadSettings ( config )
2014-07-17 17:22:54 +00:00
local screen_mode
2014-03-13 13:52:43 +00:00
self.render_mode = config : readSetting ( " render_mode " ) or 0
2014-07-17 17:22:54 +00:00
if self.ui . document.info . has_pages then
screen_mode = config : readSetting ( " screen_mode " ) or G_reader_settings : readSetting ( " kopt_screen_mode " ) or " portrait "
else
screen_mode = config : readSetting ( " screen_mode " ) or G_reader_settings : readSetting ( " copt_screen_mode " ) or " portrait "
end
2014-03-13 13:52:43 +00:00
if screen_mode then
Screen : setScreenMode ( screen_mode )
2018-12-28 03:32:42 +00:00
self : onSetScreenMode ( screen_mode , config : readSetting ( " rotation_mode " ) , true )
2014-03-13 13:52:43 +00:00
end
self.state . gamma = config : readSetting ( " gamma " ) or DGLOBALGAMMA
local full_screen = config : readSetting ( " kopt_full_screen " ) or self.document . configurable.full_screen
2016-07-24 00:57:29 +00:00
if full_screen == 0 then
2016-04-28 07:02:15 +00:00
self.footer_visible = false
end
2014-03-13 13:52:43 +00:00
self : resetLayout ( )
local page_scroll = config : readSetting ( " kopt_page_scroll " ) or self.document . configurable.page_scroll
self.page_scroll = page_scroll == 1 and true or false
self.highlight . saved = config : readSetting ( " highlight " ) or { }
2017-05-22 14:21:23 +00:00
self.page_overlap_style = config : readSetting ( " page_overlap_style " ) or G_reader_settings : readSetting ( " page_overlap_style " ) or " dim "
2012-10-09 22:26:01 +00:00
end
2012-05-18 22:50:26 +00:00
function ReaderView : onPageUpdate ( new_page_no )
2014-03-13 13:52:43 +00:00
self.state . page = new_page_no
self : recalculate ( )
self.highlight . temp = { }
2016-07-13 06:53:23 +00:00
UIManager : nextTick ( self.autoSaveSettings )
2012-05-18 22:50:26 +00:00
end
2012-06-05 07:23:36 +00:00
function ReaderView : onPosUpdate ( new_pos )
2014-03-13 13:52:43 +00:00
self.state . pos = new_pos
self : recalculate ( )
self.highlight . temp = { }
2016-07-13 06:53:23 +00:00
UIManager : nextTick ( self.autoSaveSettings )
2012-06-05 07:23:36 +00:00
end
2012-11-26 07:30:24 +00:00
function ReaderView : onZoomUpdate ( zoom )
2014-03-13 13:52:43 +00:00
self.state . zoom = zoom
self : recalculate ( )
self.highlight . temp = { }
2012-05-18 22:50:26 +00:00
end
2012-12-02 09:09:32 +00:00
function ReaderView : onBBoxUpdate ( bbox )
2014-03-13 13:52:43 +00:00
self.use_bbox = bbox and true or false
2012-12-02 09:09:32 +00:00
end
2012-05-18 22:50:26 +00:00
function ReaderView : onRotationUpdate ( rotation )
2014-03-13 13:52:43 +00:00
self.state . rotation = rotation
self : recalculate ( )
2012-05-18 22:50:26 +00:00
end
2013-02-20 06:32:51 +00:00
function ReaderView : onGammaUpdate ( gamma )
2014-03-13 13:52:43 +00:00
self.state . gamma = gamma
if self.page_scroll then
self.ui : handleEvent ( Event : new ( " UpdateScrollPageGamma " , gamma ) )
end
2013-01-05 14:28:14 +00:00
end
2019-08-30 11:47:51 +00:00
function ReaderView : onFontSizeUpdate ( font_size )
self.ui : handleEvent ( Event : new ( " ReZoom " , font_size ) )
2013-04-20 08:17:38 +00:00
end
2013-04-14 07:16:42 +00:00
function ReaderView : onDefectSizeUpdate ( )
2014-03-13 13:52:43 +00:00
self.ui : handleEvent ( Event : new ( " ReZoom " ) )
2013-04-14 07:16:42 +00:00
end
function ReaderView : onPageCrop ( )
2014-03-13 13:52:43 +00:00
self.ui : handleEvent ( Event : new ( " ReZoom " ) )
2013-04-14 07:16:42 +00:00
end
function ReaderView : onMarginUpdate ( )
2014-03-13 13:52:43 +00:00
self.ui : handleEvent ( Event : new ( " ReZoom " ) )
2013-04-14 07:16:42 +00:00
end
2013-01-07 12:05:48 +00:00
function ReaderView : onSetViewMode ( new_mode )
2017-10-02 22:31:14 +00:00
if new_mode ~= self.view_mode then
self.view_mode = new_mode
self.ui . document : setViewMode ( new_mode )
self.ui : handleEvent ( Event : new ( " ChangeViewMode " ) )
end
2014-03-13 13:52:43 +00:00
return true
2013-01-05 14:28:14 +00:00
end
2013-12-27 15:18:16 +00:00
function ReaderView : onSaveSettings ( )
2014-03-13 13:52:43 +00:00
self.ui . doc_settings : saveSetting ( " render_mode " , self.render_mode )
self.ui . doc_settings : saveSetting ( " screen_mode " , self.screen_mode )
self.ui . doc_settings : saveSetting ( " rotation_mode " , self.cur_rotation_mode )
self.ui . doc_settings : saveSetting ( " gamma " , self.state . gamma )
2014-04-02 20:59:17 +00:00
self.ui . doc_settings : saveSetting ( " highlight " , self.highlight . saved )
2014-11-17 13:44:13 +00:00
self.ui . doc_settings : saveSetting ( " page_overlap_style " , self.page_overlap_style )
2012-10-09 22:26:01 +00:00
end
2013-10-18 20:38:07 +00:00
2014-10-15 12:31:24 +00:00
function ReaderView : getRenderModeMenuTable ( )
local view = self
local function make_mode ( text , mode )
return {
text = text ,
checked_func = function ( ) return view.render_mode == mode end ,
callback = function ( ) view.render_mode = mode end ,
}
end
return {
2019-08-24 07:25:38 +00:00
-- @translators Selects which layers of the DjVu image should be rendered. Valid rendering modes are color, black, mask, foreground, and background. See http://djvu.sourceforge.net/ and https://en.wikipedia.org/wiki/DjVu for more information about the format.
2014-10-15 12:31:24 +00:00
text = _ ( " DjVu render mode " ) ,
sub_item_table = {
make_mode ( _ ( " COLOUR (works for both colour and b&w pages) " ) , 0 ) ,
make_mode ( _ ( " BLACK & WHITE (for b&w pages only, much faster) " ) , 1 ) ,
make_mode ( _ ( " COLOUR ONLY (slightly faster than COLOUR) " ) , 2 ) ,
make_mode ( _ ( " MASK ONLY (for b&w pages only) " ) , 3 ) ,
make_mode ( _ ( " COLOUR BACKGROUND (show only background) " ) , 4 ) ,
make_mode ( _ ( " COLOUR FOREGROUND (show only foreground) " ) , 5 ) ,
}
}
end
2014-11-17 13:44:13 +00:00
local page_overlap_styles = {
arrow = _ ( " Arrow " ) ,
dim = _ ( " Gray out " ) ,
}
2019-02-03 10:17:27 +00:00
function ReaderView : genOverlapStyleMenu ( overlap_enabled_func )
2014-11-17 13:44:13 +00:00
local view = self
local get_overlap_style = function ( style )
return {
text = page_overlap_styles [ style ] ,
2019-02-03 10:17:27 +00:00
enabled_func = overlap_enabled_func ,
2014-11-17 13:44:13 +00:00
checked_func = function ( )
return view.page_overlap_style == style
end ,
callback = function ( )
view.page_overlap_style = style
2017-05-22 14:21:23 +00:00
end ,
hold_callback = function ( )
UIManager : show ( ConfirmBox : new {
text = T (
_ ( " Set default overlap style to %1? " ) ,
style
) ,
ok_callback = function ( )
view.page_overlap_style = style
G_reader_settings : saveSetting ( " page_overlap_style " , style )
end ,
} )
end ,
2014-11-17 13:44:13 +00:00
}
end
return {
get_overlap_style ( " arrow " ) ,
get_overlap_style ( " dim " ) ,
}
end
2016-02-17 06:36:40 +00:00
function ReaderView : onCloseDocument ( )
self.hinting = false
-- stop any in fly HintPage event
UIManager : unschedule ( self.emitHintPageEvent )
end
2016-07-13 06:53:23 +00:00
function ReaderView : onReaderReady ( )
if DAUTO_SAVE_PAGING_COUNT ~= nil then
if DAUTO_SAVE_PAGING_COUNT <= 0 then
self.autoSaveSettings = function ( )
self.ui : saveSettings ( )
end
else
self.autoSaveSettings = function ( )
if self.auto_save_paging_count == DAUTO_SAVE_PAGING_COUNT then
self.ui : saveSettings ( )
self.auto_save_paging_count = 0
else
self.auto_save_paging_count = self.auto_save_paging_count + 1
end
end
end
end
end
2013-10-18 20:38:07 +00:00
return ReaderView