diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index d8f627e06..1b2a2b55c 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -1,14 +1,23 @@ require "cache" require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" +require "document/koptinterface" DjvuDocument = Document:new{ _document = false, -- libdjvulibre manages its own additional cache, default value is hard written in c module. djvulibre_cache_size = nil, - dc_null = DrawContext.new() + dc_null = DrawContext.new(), + screen_size = Screen:getSize(), + screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, + configurable = Configurable, + koptinterface = KoptInterface, } function DjvuDocument:init() + self.configurable:loadDefaults() if not validDjvuFile(self.file) then self.error_message = "Not a valid DjVu file" return @@ -22,6 +31,7 @@ function DjvuDocument:init() end self.is_open = true self.info.has_pages = true + self.info.configurable = true self:_readMetadata() end @@ -38,7 +48,8 @@ end function DjvuDocument:getUsedBBox(pageno) -- djvu does not support usedbbox, so fake it. local used = {} - used.x, used.y, used.w, used.h = 0.01, 0.01, -0.01, -0.01 + local native_dim = self:getNativePageDimensions(pageno) + used.x0, used.y0, used.x1, used.y1 = 0, 0, native_dim.w, native_dim.h return used end @@ -52,4 +63,28 @@ function DjvuDocument:invertTextYAxel(pageno, text_table) return text_table end +function DjvuDocument:getPageDimensions(pageno, zoom, rotation) + if self.configurable.text_wrap == 1 then + return self.koptinterface:getPageDimensions(self, pageno, zoom, rotation) + else + return Document.getPageDimensions(self, pageno, zoom, rotation) + end +end + +function DjvuDocument:renderPage(pageno, rect, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, render_mode) + else + return Document.renderPage(self, pageno, rect, zoom, rotation, render_mode) + end +end + +function DjvuDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + else + Document.drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + end +end + DocumentRegistry:addProvider("djvu", "application/djvu", DjvuDocument) diff --git a/frontend/document/document.lua b/frontend/document/document.lua index 715dc34d9..5c1bfa60b 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -247,5 +247,4 @@ end require "document/pdfdocument" require "document/djvudocument" -require "document/koptdocument" require "document/credocument" diff --git a/frontend/document/koptdocument.lua b/frontend/document/koptdocument.lua deleted file mode 100644 index dcd265699..000000000 --- a/frontend/document/koptdocument.lua +++ /dev/null @@ -1,213 +0,0 @@ -require "cache" -require "ui/geometry" -require "ui/screen" -require "ui/device" - --- Any document processed by K2pdfopt is called a koptdocument -KoptDocument = Document:new{ - _document = false, - -- muPDF manages its own additional cache - mupdf_cache_size = 5 * 1024 * 1024, - djvulibre_cache_size = nil, - dc_null = DrawContext.new(), - screen_size = Screen:getSize(), - screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, - configurable = { - font_size = 1.0, - page_margin = 0.06, - line_spacing = 1.2, - word_spacing = 0.15, - quality = 1.0, - text_wrap = 1, - defect_size = 1.0, - trim_page = 0, - detect_indent = 1, - multi_threads = 0, - auto_straighten = 0, - justification = -1, - max_columns = 2, - contrast = 1.0, - screen_rotation = 0, - } -} - -function KoptDocument:init() - self.file_type = string.lower(string.match(self.file, ".+%.([^.]+)") or "") - if self.file_type == "pdf" then - local ok - ok, self._document = pcall(pdf.openDocument, self.file, self.mupdf_cache_size) - if not ok then - self.error_message = self.doc -- will contain error message - return - end - self.is_open = true - self.info.has_pages = true - if self._document:needsPassword() then - self.is_locked = true - else - self:_readMetadata() - end - - elseif self.file_type == "djvu" then - if not validDjvuFile(self.file) then - self.error_message = "Not a valid DjVu file" - return - end - - local ok - ok, self._document = pcall(djvu.openDocument, self.file, self.djvulibre_cache_size) - if not ok then - self.error_message = self.doc -- will contain error message - return - end - self.is_open = true - self.info.has_pages = true - self:_readMetadata() - end -end - -function KoptDocument:unlock(password) - if not self._document:authenticatePassword(password) then - self._document:close() - return false, "wrong password" - end - self.is_locked = false - return self:_readMetadata() -end - --- check DjVu magic string to validate -function validDjvuFile(filename) - f = io.open(filename, "r") - if not f then return false end - local magic = f:read(8) - f:close() - if not magic or magic ~= "AT&TFORM" then return false end - return true -end - -function KoptDocument:getUsedBBox(pageno) - if self.file_type == "pdf" then - local hash = "pgubbox|"..self.file.."|"..pageno - local cached = Cache:check(hash) - if cached then - return cached.ubbox - end - local page = self._document:openPage(pageno) - local used = {} - used.x0, used.y0, used.x1, used.y1 = page:getUsedBBox() - local pwidth, pheight = page:getSize(self.dc_null) - if used.x1 == 0 then used.x1 = pwidth end - if used.y1 == 0 then used.y1 = pheight end - -- clamp to page BBox - if used.x0 < 0 then used.x0 = 0 end; - if used.y0 < 0 then used.y0 = 0 end; - if used.x1 > pwidth then used.x1 = pwidth end - if used.y1 > pheight then used.y1 = pheight end - --@TODO give size for cacheitem? 02.12 2012 (houqp) - Cache:insert(hash, CacheItem:new{ - ubbox = used, - }) - page:close() - DEBUG("UsedBBox", used) - return used - elseif self.file_type == "djvu" then - -- djvu does not support usedbbox, so fake it. - local used = {} - local native_dim = self:getNativePageDimensions(pageno) - used.x0, used.y0, used.x1, used.y1 = 0, 0, native_dim.w, native_dim.h - return used - end -end - --- get reflow context -function KoptDocument:getKOPTContext(pageno) - local kc = KOPTContext.new() - kc:setTrim(self.configurable.trim_page) - kc:setWrap(self.configurable.text_wrap) - kc:setIndent(self.configurable.detect_indent) - kc:setRotate(self.configurable.screen_rotation) - kc:setColumns(self.configurable.max_columns) - kc:setDeviceDim(self.screen_size.w, self.screen_size.h) - kc:setDeviceDPI(self.screen_dpi) - kc:setStraighten(self.configurable.auto_straighten) - kc:setJustification(self.configurable.justification) - kc:setZoom(self.configurable.font_size) - kc:setMargin(self.configurable.page_margin) - kc:setQuality(self.configurable.quality) - kc:setContrast(self.configurable.contrast) - kc:setDefectSize(self.configurable.defect_size) - kc:setLineSpacing(self.configurable.line_spacing) - kc:setWordSpacing(self.configurable.word_spacing) - local bbox = self:getUsedBBox(pageno) - kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1) - return kc -end - --- calculates page dimensions -function KoptDocument:getPageDimensions(pageno, zoom, rotation) - -- check cached page size - local hash = "kctx|"..self.file.."|"..pageno - local cached = Cache:check(hash) - if not cached then - local kc = self:getKOPTContext(pageno) - local page = self._document:openPage(pageno) - -- reflow page - page:reflow(kc, 0) - page:close() - local fullwidth, fullheight = kc:getPageDim() - DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) - local page_size = Geom:new{ w = fullwidth, h = fullheight } - -- cache reflowed page size and kc - Cache:insert(hash, CacheItem:new{ kctx = kc }) - return page_size - end - DEBUG("Found cached koptcontex on page", pageno, cached) - local fullwidth, fullheight = cached.kctx:getPageDim() - local page_size = Geom:new{ w = fullwidth, h = fullheight } - return page_size -end - -function KoptDocument:renderPage(pageno, rect, zoom, rotation, render_mode) - self.render_mode = render_mode - local hash = "renderpg|"..self.file.."|"..pageno.."|"..zoom.."|"..rotation - local page_size = self:getPageDimensions(pageno, zoom, rotation) - -- this will be the size we actually render - local size = page_size - -- we prefer to render the full page, if it fits into cache - if not Cache:willAccept(size.w * size.h / 2) then - -- whole page won't fit into cache - DEBUG("rendering only part of the page") - -- TODO: figure out how to better segment the page - if not rect then - DEBUG("aborting, since we do not have a specification for that part") - -- required part not given, so abort - return - end - -- only render required part - hash = "renderpg|"..self.file.."|"..pageno.."|"..zoom.."|"..rotation.."|"..tostring(rect) - size = rect - end - - -- prepare cache item with contained blitbuffer - local tile = CacheItem:new{ - size = size.w * size.h / 2 + 64, -- estimation - excerpt = size, - pageno = pageno, - bb = Blitbuffer.new(size.w, size.h) - } - - -- draw to blitbuffer - local kc_hash = "kctx|"..self.file.."|"..pageno - local page = self._document:openPage(pageno) - local cached = Cache:check(kc_hash) - if cached then - page:rfdraw(cached.kctx, tile.bb) - page:close() - Cache:insert(hash, tile) - return tile - end - DEBUG("Error: cannot render page before reflowing.") -end - -DocumentRegistry:addProvider("pdf", "application/pdf", KoptDocument) -DocumentRegistry:addProvider("djvu", "application/djvu", KoptDocument) diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua new file mode 100644 index 000000000..a45951b3f --- /dev/null +++ b/frontend/document/koptinterface.lua @@ -0,0 +1,113 @@ +require "cache" +require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" + +KoptInterface = {} + +-- get reflow context +function KoptInterface:getKOPTContext(doc, pageno) + local kc = KOPTContext.new() + kc:setTrim(doc.configurable.trim_page) + kc:setWrap(doc.configurable.text_wrap) + kc:setIndent(doc.configurable.detect_indent) + kc:setRotate(doc.configurable.screen_rotation) + kc:setColumns(doc.configurable.max_columns) + kc:setDeviceDim(doc.screen_size.w, doc.screen_size.h) + kc:setDeviceDPI(doc.screen_dpi) + kc:setStraighten(doc.configurable.auto_straighten) + kc:setJustification(doc.configurable.justification) + kc:setZoom(doc.configurable.font_size) + kc:setMargin(doc.configurable.page_margin) + kc:setQuality(doc.configurable.quality) + kc:setContrast(doc.configurable.contrast) + kc:setDefectSize(doc.configurable.defect_size) + kc:setLineSpacing(doc.configurable.line_spacing) + kc:setWordSpacing(doc.configurable.word_spacing) + local bbox = doc:getUsedBBox(pageno) + kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1) + return kc +end + +-- calculates page dimensions +function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) + -- check cached page size + local hash = "kctx|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local cached = Cache:check(hash) + if not cached then + local kc = self:getKOPTContext(doc, pageno) + local page = doc._document:openPage(pageno) + -- reflow page + page:reflow(kc, 0) + page:close() + local fullwidth, fullheight = kc:getPageDim() + DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) + local page_size = Geom:new{ w = fullwidth, h = fullheight } + -- cache reflowed page size and kc + Cache:insert(hash, CacheItem:new{ kctx = kc }) + return page_size + end + --DEBUG("Found cached koptcontex on page", pageno, cached) + local fullwidth, fullheight = cached.kctx:getPageDim() + local page_size = Geom:new{ w = fullwidth, h = fullheight } + return page_size +end + +function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode) + doc.render_mode = render_mode + local hash = "renderpg|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local page_size = self:getPageDimensions(doc, pageno, zoom, rotation) + -- this will be the size we actually render + local size = page_size + -- we prefer to render the full page, if it fits into cache + if not Cache:willAccept(size.w * size.h / 2) then + -- whole page won't fit into cache + DEBUG("rendering only part of the page") + -- TODO: figure out how to better segment the page + if not rect then + DEBUG("aborting, since we do not have a specification for that part") + -- required part not given, so abort + return + end + -- only render required part + hash = "renderpg|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|').."|"..tostring(rect) + size = rect + end + + local cached = Cache:check(hash) + if cached then return cached end + + -- prepare cache item with contained blitbuffer + local tile = CacheItem:new{ + size = size.w * size.h / 2 + 64, -- estimation + excerpt = size, + pageno = pageno, + bb = Blitbuffer.new(size.w, size.h) + } + + -- draw to blitbuffer + local kc_hash = "kctx|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local page = doc._document:openPage(pageno) + local cached = Cache:check(kc_hash) + if cached then + page:rfdraw(cached.kctx, tile.bb) + page:close() + DEBUG("cached hash", hash) + if not Cache:check(hash) then + Cache:insert(hash, tile) + end + return tile + end + DEBUG("Error: cannot render page before reflowing.") +end + +function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode) + local tile = self:renderPage(doc, pageno, rect, zoom, rotation, render_mode) + DEBUG("now painting", tile, rect) + target:blitFrom(tile.bb, + x, y, + rect.x - tile.excerpt.x, + rect.y - tile.excerpt.y, + rect.w, rect.h) +end diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index 17d2eb4df..85709c5a6 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -1,14 +1,23 @@ require "cache" require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" +require "document/koptinterface" PdfDocument = Document:new{ _document = false, -- muPDF manages its own additional cache mupdf_cache_size = 5 * 1024 * 1024, - dc_null = DrawContext.new() + dc_null = DrawContext.new(), + screen_size = Screen:getSize(), + screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, + configurable = Configurable, + koptinterface = KoptInterface, } function PdfDocument:init() + self.configurable:loadDefaults() local ok ok, self._document = pcall(pdf.openDocument, self.file, self.mupdf_cache_size) if not ok then @@ -17,6 +26,7 @@ function PdfDocument:init() end self.is_open = true self.info.has_pages = true + self.info.configurable = true if self._document:needsPassword() then self.is_locked = true else @@ -50,4 +60,28 @@ function PdfDocument:getUsedBBox(pageno) return used end +function PdfDocument:getPageDimensions(pageno, zoom, rotation) + if self.configurable.text_wrap == 1 then + return self.koptinterface:getPageDimensions(self, pageno, zoom, rotation) + else + return Document.getPageDimensions(self, pageno, zoom, rotation) + end +end + +function PdfDocument:renderPage(pageno, rect, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, render_mode) + else + return Document.renderPage(self, pageno, rect, zoom, rotation, render_mode) + end +end + +function PdfDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + else + Document.drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + end +end + DocumentRegistry:addProvider("pdf", "application/pdf", PdfDocument) diff --git a/frontend/ui/config.lua b/frontend/ui/config.lua new file mode 100644 index 000000000..69495c770 --- /dev/null +++ b/frontend/ui/config.lua @@ -0,0 +1,337 @@ +require "ui/widget" +require "ui/focusmanager" +require "ui/infomessage" +require "ui/font" + +FixedTextWidget = TextWidget:new{} +function FixedTextWidget:getSize() + local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true) + if not tsize then + return Geom:new{} + end + self._length = tsize.x + self._height = self.face.size + return Geom:new{ + w = self._length, + h = self._height, + } +end + +function FixedTextWidget:paintTo(bb, x, y) + renderUtf8Text(bb, x, y+self._height, self.face, self.text, true) +end + +MenuBarItem = InputContainer:new{} +function MenuBarItem:init() + self.dimen = self[1]:getSize() + -- we need this table per-instance, so we declare it here + if Device:isTouchDevice() then + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + }, + doc = "Select Menu Item", + }, + } + else + self.active_key_events = { + Select = { {"Press"}, doc = "chose selected item" }, + } + end +end + +function MenuBarItem:onTapSelect() + for _, item in pairs(self.items) do + item[1].invert = false + end + self[1].invert = true + self.config:onShowOptions(self.options) + UIManager.repaint_all = true + return true +end + +OptionTextItem = InputContainer:new{} +function OptionTextItem:init() + local text_widget = self[1] + self.dimen = text_widget:getSize() + self[1] = UnderlineContainer:new{ + text_widget, + padding = self.padding, + color = self.color, + } + -- we need this table per-instance, so we declare it here + if Device:isTouchDevice() then + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + }, + doc = "Select Option Item", + }, + } + else + self.active_key_events = { + Select = { {"Press"}, doc = "chose selected item" }, + } + end +end + +function OptionTextItem:onTapSelect() + for _, item in pairs(self.items) do + item[1].color = 0 + end + self[1].color = 15 + local option_value = nil + if type(self.values) == "table" then + option_value = self.values[self.current_item] + self.config:onConfigChoice(self.name, option_value) + end + UIManager.repaint_all = true + return true +end + +ConfigIcons = HorizontalGroup:new{} +function ConfigIcons:init() + for c = 1, #self.icons do + table.insert(self, self.spacing) + table.insert(self, self.icons[c]) + end + table.insert(self, self.spacing) +end + +ConfigOption = CenterContainer:new{dimen = Geom:new{ w = Screen:getWidth(), h = math.floor(150*Screen:getWidth()/600)}} +function ConfigOption:init() + local default_name_font_size = math.floor(20*Screen:getWidth()/600) + local default_item_font_size = math.floor(20*Screen:getWidth()/600) + local default_items_spacing = math.floor(30*Screen:getWidth()/600) + local default_option_height = math.floor(30*Screen:getWidth()/600) + local vertical_group = VerticalGroup:new{} + for c = 1, #self.options do + if self.options[c].show ~= false then + local name_align = self.options[c].name_align_right and self.options[c].name_align_right or 0.33 + local item_align = self.options[c].item_align_center and self.options[c].item_align_center or 0.66 + local name_font_face = self.options[c].name_font_face and self.options[c].name_font_face or "tfont" + local name_font_size = self.options[c].name_font_size and self.options[c].name_font_size or default_name_font_size + local item_font_face = self.options[c].item_font_face and self.options[c].item_font_face or "cfont" + local item_font_size = self.options[c].item_font_size and self.options[c].item_font_size or default_item_font_size + local option_height = self.options[c].height and self.options[c].height or default_option_height + local items_spacing = HorizontalSpan:new{ width = self.options[c].spacing and self.options[c].spacing or default_items_spacing} + + local horizontal_group = HorizontalGroup:new{} + if self.options[c].name_text then + local option_name_container = RightContainer:new{ + dimen = Geom:new{ w = Screen:getWidth()*name_align, h = option_height}, + } + local option_name = TextWidget:new{ + text = self.options[c].name_text, + face = Font:getFace(name_font_face, name_font_size), + } + table.insert(option_name_container, option_name) + table.insert(horizontal_group, option_name_container) + end + + if self.options[c].widget == "ProgressWidget" then + local widget_container = CenterContainer:new{ + dimen = Geom:new{w = Screen:getWidth()*self.options[c].widget_align_center, h = option_height} + } + local widget = ProgressWidget:new{ + width = self.options[c].width, + height = self.options[c].height, + percentage = self.options[c].percentage, + } + table.insert(widget_container, widget) + table.insert(horizontal_group, widget_container) + end + + local option_items_container = CenterContainer:new{ + dimen = Geom:new{w = Screen:getWidth()*item_align, h = option_height} + } + local option_items_group = HorizontalGroup:new{} + local option_items_fixed = false + local option_items = {} + if type(self.options[c].item_font_size) == "table" then + option_items_group.align = "bottom" + option_items_fixed = true + end + -- make current index according to configurable table + local current_item = nil + if self.options[c].name then + local val = self.config.configurable[self.options[c].name] + local min_diff = math.abs(val - self.options[c].values[1]) + local diff = nil + for index, val_ in pairs(self.options[c].values) do + if val == val_ then + current_item = index + break + end + diff = math.abs(val - val_) + if diff <= min_diff then + min_diff = diff + current_item = index + end + end + end + + for d = 1, #self.options[c].item_text do + local option_item = nil + if option_items_fixed then + option_item = OptionTextItem:new{ + FixedTextWidget:new{ + text = self.options[c].item_text[d], + face = Font:getFace(item_font_face, item_font_size[d]), + }, + padding = 3, + color = d == current_item and 15 or 0, + } + else + option_item = OptionTextItem:new{ + TextWidget:new{ + text = self.options[c].item_text[d], + face = Font:getFace(item_font_face, item_font_size), + }, + padding = -3, + color = d == current_item and 15 or 0, + } + end + option_items[d] = option_item + option_item.items = option_items + option_item.name = self.options[c].name + option_item.values = self.options[c].values + option_item.current_item = d + option_item.config = self.config + table.insert(option_items_group, option_item) + if d ~= #self.options[c].item_text then + table.insert(option_items_group, items_spacing) + end + end + table.insert(option_items_container, option_items_group) + table.insert(horizontal_group, option_items_container) + table.insert(vertical_group, horizontal_group) + end -- if + end -- for + self[1] = vertical_group +end + +ConfigPanel = VerticalGroup:new{} +function ConfigPanel:init() + local default_option = ConfigOption:new{ + options = self.config_options.default_options + } + local menu_bar = FrameContainer:new{ + background = 0, + } + local menu_items = {} + local icons_width = 0 + local icons_height = 0 + for c = 1, #self.config_options do + local menu_icon = ImageWidget:new{ + file = self.config_options[c].icon + } + local icon_dimen = menu_icon:getSize() + icons_width = icons_width + icon_dimen.w + icons_height = icon_dimen.h > icons_height and icon_dimen.h or icons_height + + menu_items[c] = MenuBarItem:new{ + menu_icon, + options = ConfigOption:new{ + options = self.config_options[c].options, + config = self.config_dialog, + }, + config = self.config_dialog, + items = menu_items, + } + end + menu_bar[1] = ConfigIcons:new{ + icons = menu_items, + spacing = HorizontalSpan:new{ + width = (Screen:getWidth() - icons_width) / (#menu_items+1) + } + } + menu_bar.dimen = Geom:new{ w = Screen:getWidth(), h = icons_height} + + self[1] = default_option + self[2] = menu_bar +end + +--[[ +Widget that displays config menu +--]] +ConfigDialog = InputContainer:new{ + --is_borderless = false, +} + +function ConfigDialog:init() + ------------------------------------------ + -- start to set up widget layout --------- + ------------------------------------------ + self.config_panel = ConfigPanel:new{ + config_options = self.config_options, + config_dialog = self, + } + + local config_panel_size = self.config_panel:getSize() + + self.menu_dimen = Geom:new{ + x = (Screen:getWidth() - config_panel_size.w)/2, + y = Screen:getHeight() - config_panel_size.h, + w = config_panel_size.w, + h = config_panel_size.h, + } + + self[1] = BottomContainer:new{ + dimen = Screen:getSize(), + FrameContainer:new{ + dimen = self.config_panel:getSize(), + background = 0, + self.config_panel, + } + } + ------------------------------------------ + -- start to set up input event callback -- + ------------------------------------------ + if Device:isTouchDevice() then + self.ges_events.TapCloseMenu = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } + } + } + else + -- set up keyboard events + self.key_events.Close = { {"Back"}, doc = "close config menu" } + -- we won't catch presses to "Right" + self.key_events.FocusRight = nil + end + self.key_events.Select = { {"Press"}, doc = "select current menu item"} + + UIManager.repaint_all = true +end + +function ConfigDialog:onShowOptions(options) + self.config_panel[1] = options + UIManager.repaint_all = true + return true +end + +function ConfigDialog:onCloseMenu() + UIManager:close(self) + if self.close_callback then + self.close_callback() + end + return true +end + +function ConfigDialog:onTapCloseMenu(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.menu_dimen) then + self:onCloseMenu() + --self.ui:handleEvent(Event:new("GotoPageRel", 0)) + return true + end +end diff --git a/frontend/ui/graphics.lua b/frontend/ui/graphics.lua index 6a35fd8fb..d864b5e80 100644 --- a/frontend/ui/graphics.lua +++ b/frontend/ui/graphics.lua @@ -70,9 +70,9 @@ function blitbuffer.progressBar(bb, x, y, w, h, if load_m_h*2 > h then load_m_h = h/2 end - blitbuffer.paintBorder(fb.bb, x, y, w, h, 2, 15) - fb.bb:paintRect(x+load_m_w, y+load_m_h, - (w-2*load_m_w)*load_percent, (h-2*load_m_h), c) + bb:paintBorder(x, y, w, h, 2, 15) + bb:paintRect(x+load_m_w, y+load_m_h, + (w-2*load_m_w)*load_percent, (h-2*load_m_h), c) end diff --git a/frontend/ui/reader/readerconfig.lua b/frontend/ui/reader/readerconfig.lua new file mode 100644 index 000000000..b13ce0dc3 --- /dev/null +++ b/frontend/ui/reader/readerconfig.lua @@ -0,0 +1,270 @@ +require "ui/config" + +KOPTOptions = { + default_options = { + { + widget = "ProgressWidget", + widget_align_center = 0.8, + width = Screen:getWidth()*0.7, + height = 5, + percentage = 0.0, + item_text = {"Goto"}, + item_align_center = 0.2, + item_font_face = "tfont", + item_font_size = 20, + } + }, + { + icon = "resources/icons/appbar.transform.rotate.right.large.png", + options = { + { + name="screen_rotation", + name_text = "Screen Rotation", + item_text = {"portrait", "landscape"}, + values = {0, 90}, + default_value = 0, + } + } + }, + { + icon = "resources/icons/appbar.crop.large.png", + options = { + { + name="trim_page", + name_text = "Page Crop", + item_text = {"auto", "manual"}, + values = {1, 0}, + default_value = 1, + } + } + }, + { + icon = "resources/icons/appbar.column.two.large.png", + options = { + { + name = "text_wrap", + name_text = "Reflow", + item_text = {"on","off"}, + values = {1, 0}, + default_value = 0, + show = true + }, + { + name = "max_columns", + name_text = "Columns", + item_text = {"1","2","3","4"}, + values = {1,2,3,4}, + default_value = 2, + show = false + }, + { + name = "page_margin", + name_text = "Page Margin", + item_text = {"small", "medium", "large"}, + values = {0.02, 0.06, 0.10}, + default_value = 0.06, + }, + { + name = "line_spacing", + name_text = "Line Spacing", + item_text = {"small", "medium", "large"}, + values = {1.0, 1.2, 1.4}, + default_value = 1.2, + }, + { + name = "word_spacing", + name_text = "Word Spacing", + item_text = {"small", "medium", "large"}, + values = {0.05, 0.15, 0.375}, + default_value = 0.15, + }, + { + name = "justification", + name_text = "Justification", + item_text = {"auto","left","center","right","full"}, + values = {-1,0,1,2,3}, + default_value = -1, + }, + } + }, + { + icon = "resources/icons/appbar.text.size.large.png", + options = { + { + name = "font_size", + item_text = {"Aa","Aa","Aa","Aa","Aa","Aa","Aa","Aa","Aa","Aa"}, + item_align_center = 1.0, + spacing = Screen:getWidth()*0.03, + item_font_size = {20,24,28,32,36,38,40,42,46,50}, + values = {0.2, 0.3, 0.4, 0.6, 0.8, 1.0, 1.2, 1.6, 2.2, 2.8}, + default_value = 1.0, + }, + } + }, + { + icon = "resources/icons/appbar.grade.b.large.png", + options = { + { + name = "contrast", + name_text = "Contrast", + name_align_right = 0.2, + item_text = {"lightest", "lighter", "default", "darker", "darkest"}, + item_font_size = math.floor(18*Screen:getWidth()/600), + item_align_center = 0.8, + values = {2.0, 1.5, 1.0, 0.5, 0.2}, + default_value = 1.0, + } + } + }, + { + icon = "resources/icons/appbar.settings.large.png", + options = { + { + name = "quality", + name_text = "Render Quality", + item_text = {"low", "default", "high"}, + values={0.5, 0.8, 1.0}, + default_value = 1.0, + }, + { + name = "auto_straighten", + name_text = "Auto Straighten", + item_text = {"0 deg", "5 deg", "10 deg"}, + values = {0, 5, 10}, + default_value = 0, + }, + { + name = "detect_indent", + name_text = "Indentation", + item_text = {"enable","disable"}, + values = {1, 0}, + default_value = 1, + show = false, + }, + { + name = "defect_size", + name_text = "Defect Size", + item_text = {"small","medium","large"}, + values = {0.5, 1.0, 2.0}, + default_value = 1.0, + show = false, + }, + } + }, +} + +Configurable = {} + +function Configurable:hash(sep) + local hash = "" + local excluded = {multi_threads = true,} + for key,value in pairs(self) do + if type(value) == "number" and not excluded[key] then + hash = hash..sep..value + end + end + return hash +end + +function Configurable:loadDefaults() + for i=1,#KOPTOptions do + local options = KOPTOptions[i].options + for j=1,#KOPTOptions[i].options do + local key = KOPTOptions[i].options[j].name + self[key] = KOPTOptions[i].options[j].default_value + end + end +end + +function Configurable:loadSettings(settings, prefix) + for key,value in pairs(self) do + if type(value) == "number" then + saved_value = settings:readSetting(prefix..key) + self[key] = (saved_value == nil) and self[key] or saved_value + --Debug("Configurable:loadSettings", "key", key, "saved value", saved_value,"Configurable.key", self[key]) + end + end + --Debug("loaded config:", dump(Configurable)) +end + +function Configurable:saveSettings(settings, prefix) + for key,value in pairs(self) do + if type(value) == "number" then + settings:saveSetting(prefix..key, value) + end + end +end + +ReaderConfig = InputContainer:new{ + dimen = Geom:new{ + x = 0, + y = 7*Screen:getHeight()/8, + w = Screen:getWidth(), + h = Screen:getHeight()/8, + } +} + +function ReaderConfig:init() + if Device:isTouchDevice() then + self.ges_events = { + TapShowConfigMenu = { + GestureRange:new{ + ges = "tap", + range = self.dimen:copy(), + } + } + } + else + self.key_events = { + ShowConfigMenu = { { "AA" }, doc = "show config dialog" }, + } + end +end + +function ReaderConfig:onShowConfigMenu() + local config_dialog = ConfigDialog:new{ + dimen = self.dimen:copy(), + ui = self.ui, + configurable = self.configurable, + config_options = KOPTOptions, + } + + function config_dialog:onConfigChoice(option_name, option_value) + self.configurable[option_name] = option_value + if option_name == "text_wrap" then + self.ui:handleEvent(Event:new("RedrawCurrentPage")) + end + end + + local dialog_container = CenterContainer:new{ + config_dialog, + dimen = self.dimen:copy(), + } + config_dialog.close_callback = function () + UIManager:close(menu_container) + end + + self.dialog_container = dialog_container + + UIManager:show(config_dialog) + + return true +end + +function ReaderConfig:onTapShowConfigMenu() + self:onShowConfigMenu() + return true +end + +function ReaderConfig:onSetDimensions(dimen) + -- update gesture listenning range according to new screen orientation + self:init() +end + +function ReaderConfig:onReadSettings(config) + self.configurable:loadSettings(config, 'kopt_') +end + +function ReaderConfig:onCloseDocument() + self.configurable:saveSettings(self.ui.doc_settings, 'kopt_') +end diff --git a/frontend/ui/reader/readermenu.lua b/frontend/ui/reader/readermenu.lua index d2ea485a4..29376ea3b 100644 --- a/frontend/ui/reader/readermenu.lua +++ b/frontend/ui/reader/readermenu.lua @@ -15,7 +15,7 @@ function ReaderMenu:init() range = Geom:new{ x = 0, y = 0, w = Screen:getWidth(), - h = Screen:getHeight()/2 + h = Screen:getHeight()/4, } } }, diff --git a/frontend/ui/reader/readerpaging.lua b/frontend/ui/reader/readerpaging.lua index f06739123..f9da309e7 100644 --- a/frontend/ui/reader/readerpaging.lua +++ b/frontend/ui/reader/readerpaging.lua @@ -14,10 +14,10 @@ function ReaderPaging:init() GestureRange:new{ ges = "tap", range = Geom:new{ - x = Screen:getWidth()/2, - y = Screen:getHeight()/2, - w = Screen:getWidth(), - h = Screen:getHeight() + x = Screen:getWidth()/4, + y = Screen:getHeight()/4, + w = 3*Screen:getWidth()/4, + h = 5*Screen:getHeight()/8, } } }, @@ -26,9 +26,9 @@ function ReaderPaging:init() ges = "tap", range = Geom:new{ x = 0, - y = Screen:getHeight()/2, - w = Screen:getWidth()/2, - h = Screen:getHeight()/2, + y = Screen:getHeight()/4, + w = Screen:getWidth()/4, + h = 5*Screen:getHeight()/8, } } } @@ -227,5 +227,6 @@ function ReaderPaging:onGotoPageRel(diff) return true end - - +function ReaderPaging:onRedrawCurrentPage() + self.ui:handleEvent(Event:new("PageUpdate", self.current_page)) +end diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index d8bb9dcfb..220725677 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -9,6 +9,7 @@ require "ui/reader/readertoc" require "ui/reader/readerbookmark" require "ui/reader/readerfont" require "ui/reader/readermenu" +require "ui/reader/readerconfig" --[[ This is an abstraction for a reader interface @@ -127,6 +128,17 @@ function ReaderUI:init() } table.insert(self, font_menu) end + if self.document.info.configurable then + -- configurable controller + local config_dialog = ReaderConfig:new{ + configurable = self.document.configurable, + options = self.document.options, + dialog = self.dialog, + view = self[1], + ui = self + } + table.insert(self, config_dialog) + end --DEBUG(self.doc_settings) -- we only read settings after all the widgets are initialized self:handleEvent(Event:new("ReadSettings", self.doc_settings)) diff --git a/frontend/ui/widget.lua b/frontend/ui/widget.lua index 30a29d879..bf503f79f 100644 --- a/frontend/ui/widget.lua +++ b/frontend/ui/widget.lua @@ -113,7 +113,21 @@ function WidgetContainer:free() end end +--[[ +BottomContainer contains its content (1 widget) at the bottom of its own dimensions +]] +BottomContainer = WidgetContainer:new() +function BottomContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + self[1]:paintTo(bb, + x + (self.dimen.w - contentSize.w)/2, + y + (self.dimen.h - contentSize.h)) +end --[[ CenterContainer centers its content (1 widget) within its own dimensions @@ -137,6 +151,22 @@ function CenterContainer:paintTo(bb, x, y) self[1]:paintTo(bb, x_pos, y_pos) end +--[[ +RightContainer aligns its content (1 widget) at the right of its own dimensions +]] +RightContainer = WidgetContainer:new() + +function RightContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + self[1]:paintTo(bb, + x + (self.dimen.w - contentSize.w), + y + (self.dimen.h - contentSize.h)/2) +end + --[[ A FrameContainer is some graphics content (1 widget) that is surrounded by a frame ]] @@ -332,6 +362,7 @@ end ImageWidget shows an image from a file ]] ImageWidget = Widget:new{ + invert = nil, file = nil, _bb = nil } @@ -349,12 +380,15 @@ function ImageWidget:getSize() if not self._bb then self:_render() end - return { w = self._bb:getWidth(), h = self._bb:getHeight() } + return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() } end function ImageWidget:paintTo(bb, x, y) local size = self:getSize() bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h) + if self.invert then + bb:invertRect(x, y, size.w, size.h) + end end function ImageWidget:free() @@ -364,6 +398,24 @@ function ImageWidget:free() end end +--[[ +ProgressWidget shows a progress bar +]] +ProgressWidget = Widget:new{ + width = nil, + height = nil, + pecentage = nil, +} + +function ProgressWidget:getSize() + return { w = self.width, h = self.height } +end + +function ProgressWidget:paintTo(bb, x, y) + local size = self:getSize() + bb:progressBar(x, y, self.width, self.height, size.w, size.h, 2, 2, self.percentage, 15) +end + --[[ A Layout widget that puts objects besides each others ]] @@ -645,8 +697,8 @@ end function InputContainer:onGesture(ev) for name, gsseq in pairs(self.ges_events) do for _, gs_range in ipairs(gsseq) do + --DEBUG("gs_range", gs_range) if gs_range:match(ev) then - --DEBUG(gs_range) local eventname = gsseq.event or name return self:handleEvent(Event:new(eventname, gsseq.args, ev)) end diff --git a/resources/icons/appbar.checkmark.thick.png b/resources/icons/appbar.checkmark.thick.png new file mode 100644 index 000000000..1daf67824 Binary files /dev/null and b/resources/icons/appbar.checkmark.thick.png differ diff --git a/resources/icons/appbar.checkmark.thick.unchecked.png b/resources/icons/appbar.checkmark.thick.unchecked.png new file mode 100644 index 000000000..02625bf22 Binary files /dev/null and b/resources/icons/appbar.checkmark.thick.unchecked.png differ diff --git a/resources/icons/appbar.chevron.left.png b/resources/icons/appbar.chevron.left.png new file mode 100644 index 000000000..7c96681b0 Binary files /dev/null and b/resources/icons/appbar.chevron.left.png differ diff --git a/resources/icons/appbar.chevron.right.png b/resources/icons/appbar.chevron.right.png new file mode 100644 index 000000000..8e213a67c Binary files /dev/null and b/resources/icons/appbar.chevron.right.png differ diff --git a/resources/icons/appbar.column.two.large.png b/resources/icons/appbar.column.two.large.png new file mode 100644 index 000000000..6fc0150b7 Binary files /dev/null and b/resources/icons/appbar.column.two.large.png differ diff --git a/resources/icons/appbar.crop.large.png b/resources/icons/appbar.crop.large.png new file mode 100644 index 000000000..e2d342d24 Binary files /dev/null and b/resources/icons/appbar.crop.large.png differ diff --git a/resources/icons/appbar.grade.b.large.png b/resources/icons/appbar.grade.b.large.png new file mode 100644 index 000000000..b7e62c28f Binary files /dev/null and b/resources/icons/appbar.grade.b.large.png differ diff --git a/resources/icons/appbar.settings.large.png b/resources/icons/appbar.settings.large.png new file mode 100644 index 000000000..f43f732f6 Binary files /dev/null and b/resources/icons/appbar.settings.large.png differ diff --git a/resources/icons/appbar.text.size.large.png b/resources/icons/appbar.text.size.large.png new file mode 100644 index 000000000..a1dc6b305 Binary files /dev/null and b/resources/icons/appbar.text.size.large.png differ diff --git a/resources/icons/appbar.transform.rotate.right.large.png b/resources/icons/appbar.transform.rotate.right.large.png new file mode 100644 index 000000000..141c71e96 Binary files /dev/null and b/resources/icons/appbar.transform.rotate.right.large.png differ diff --git a/resources/icons/src/appbar.checkmark.thick.unchecked.xaml b/resources/icons/src/appbar.checkmark.thick.unchecked.xaml new file mode 100644 index 000000000..9f16e9023 --- /dev/null +++ b/resources/icons/src/appbar.checkmark.thick.unchecked.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.checkmark.thick.xaml b/resources/icons/src/appbar.checkmark.thick.xaml new file mode 100644 index 000000000..4f4dd3e02 --- /dev/null +++ b/resources/icons/src/appbar.checkmark.thick.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.chevron.left.xaml b/resources/icons/src/appbar.chevron.left.xaml new file mode 100644 index 000000000..84d295396 --- /dev/null +++ b/resources/icons/src/appbar.chevron.left.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.chevron.right.xaml b/resources/icons/src/appbar.chevron.right.xaml new file mode 100644 index 000000000..482e1aa94 --- /dev/null +++ b/resources/icons/src/appbar.chevron.right.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.column.one.xaml b/resources/icons/src/appbar.column.one.xaml new file mode 100644 index 000000000..a0c661538 --- /dev/null +++ b/resources/icons/src/appbar.column.one.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.column.three.xaml b/resources/icons/src/appbar.column.three.xaml new file mode 100644 index 000000000..8ba3afd46 --- /dev/null +++ b/resources/icons/src/appbar.column.three.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.column.two.xaml b/resources/icons/src/appbar.column.two.xaml new file mode 100644 index 000000000..ee5bd4ecb --- /dev/null +++ b/resources/icons/src/appbar.column.two.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.crop.xaml b/resources/icons/src/appbar.crop.xaml new file mode 100644 index 000000000..76ed90bc1 --- /dev/null +++ b/resources/icons/src/appbar.crop.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.grade.b.xaml b/resources/icons/src/appbar.grade.b.xaml new file mode 100644 index 000000000..45e0a2b02 --- /dev/null +++ b/resources/icons/src/appbar.grade.b.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.settings.xaml b/resources/icons/src/appbar.settings.xaml new file mode 100644 index 000000000..be1618ed8 --- /dev/null +++ b/resources/icons/src/appbar.settings.xaml @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.text.size.xaml b/resources/icons/src/appbar.text.size.xaml new file mode 100644 index 000000000..a8b5f6378 --- /dev/null +++ b/resources/icons/src/appbar.text.size.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/appbar.transform.rotate.right.xaml b/resources/icons/src/appbar.transform.rotate.right.xaml new file mode 100644 index 000000000..343073b23 --- /dev/null +++ b/resources/icons/src/appbar.transform.rotate.right.xaml @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/src/license.txt b/resources/icons/src/license.txt new file mode 100644 index 000000000..a35a4c656 --- /dev/null +++ b/resources/icons/src/license.txt @@ -0,0 +1,44 @@ +Read the license: +http://creativecommons.org/licenses/by/3.0/ * + +Basicaly, use it anyway you want but include this license file in the source if your project is open source. Nothing is needed in the front facing project (UNLESS you are using any of the icons listed below). Commercial use is not only allowed but encouraged. This pack was made to promote consistency in applications. + +Creator +- Austin Andrews (@templarian) + +Contributor** +- Oren Nachman + - appbar.chevron.down + - appbar.chevron.up + - appbar.chevron.left + - appbar.chevron.right + +Attribution*** +- Kris Vandermotten (@kvandermotten) + - appbar.medical.pulse +- Constantin Kichinsky (@kichinsky) + - appbar.currency.rubles + - appbar.currency.grivna +- Massimo Savazzi (@msavazzi) + - List of missing exported icons +- Proletkult Graphik, from The Noun Project + - appbar.draw.pen (inspired) +- Olivier Guin, from The Noun Project + - appbar.draw.marker +- Gibran Bisio, from The Noun Project + - appbar.draw.bucket +Andrew Forrester, from The Noun Project + - appbar.fingerprint + +** Developers and designers that emailed Templarian the source .design icons to be added into the package. PNGs also accepted, but may take longer to be added. +*** Icons I've copied so closely you want to attribute them and are also under the CC license. + +Contact +- http://templarian.com/ +- admin[@]templarian[.]com + +* Does not apply to copyrighted logos +- Skype +- Facebook +- Twitter +- etc... \ No newline at end of file