From 9760c4c9acdf0ea1cf4c57ded8c65eb4390ca171 Mon Sep 17 00:00:00 2001 From: Sayamindu Dasgupta Date: Mon, 20 Jul 2009 19:44:46 +0000 Subject: Added epubview from upstream --- diff --git a/epubview/__init__.py b/epubview/__init__.py new file mode 100644 index 0000000..81a3175 --- /dev/null +++ b/epubview/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2009 One Laptop Per Child +# Author: Sayamindu Dasgupta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject + +gobject.threads_init() + +from epub import _Epub as Epub +from epubview import _View as EpubView +from jobs import _JobFind as JobFind \ No newline at end of file diff --git a/epubview/epub.py b/epubview/epub.py new file mode 100644 index 0000000..81de9d7 --- /dev/null +++ b/epubview/epub.py @@ -0,0 +1,133 @@ +# Copyright 2009 One Laptop Per Child +# Author: Sayamindu Dasgupta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import zipfile +import tempfile +import os, os.path +from lxml import etree +import shutil + +import navmap, epubinfo + + +class _Epub(object): + def __init__(self, filepath): + self._filepath = filepath + self._zobject = None + self._obffile = None + self._obfpath = None + self._ncxpath = None + self._ncxfile = None + self._basepath = None + self._tempdir = tempfile.mkdtemp() + + if not self._verify(): + print 'Warning: This does not seem to be a valid epub file' + + self._get_obf() + self._get_ncx() + + self._ncxfile = self._zobject.open(self._ncxpath) + self._navmap = navmap.NavMap(self._ncxfile, self._basepath) + + self._obffile = self._zobject.open(self._obfpath) + self._info = epubinfo.EpubInfo(self._obffile) + + self._unzip() + + def _unzip(self): + #self._zobject.extractall(path = self._tempdir) # This is broken upto python 2.7 + orig_cwd = os.getcwd() + os.chdir(self._tempdir) + for name in self._zobject.namelist(): + if name.startswith(os.path.sep): # Some weird zip file entries start with a slash, and we don't want to write to the root directory + name = name[1:] + if name.endswith(os.path.sep) or name.endswith('\\'): + os.makedirs(name) + else: + self._zobject.extract(name) + os.chdir(orig_cwd) + + + def _get_obf(self): + containerfile = self._zobject.open('META-INF/container.xml') + + tree = etree.parse(containerfile) + root = tree.getroot() + + for element in root.iterfind('.//{urn:oasis:names:tc:opendocument:xmlns:container}rootfile'): + if element.get('media-type') == 'application/oebps-package+xml': + self._obfpath = element.get('full-path') + + if self._obfpath.rpartition('/')[0]: + self._basepath = self._obfpath.rpartition('/')[0] + '/' + else: + self._basepath = '' + + containerfile.close() + + + def _get_ncx(self): + obffile = self._zobject.open(self._obfpath) + + tree = etree.parse(obffile) + root = tree.getroot() + + for element in root.iterfind('.//{http://www.idpf.org/2007/opf}item'): + if element.get('media-type') == 'application/x-dtbncx+xml' or \ + element.get('id') == 'ncx': + self._ncxpath = self._basepath + element.get('href') + + obffile.close() + + + def _verify(self): + ''' + Method to crudely check to verify that what we + are dealing with is a epub file or not + ''' + if not os.path.exists(self._filepath): + return False + + self._zobject = zipfile.ZipFile(self._filepath) + + if not 'mimetype' in self._zobject.namelist(): + return False + + mtypefile = self._zobject.open('mimetype') + mimetype = mtypefile.readline() + + if mimetype != 'application/epub+zip': + return False + + return True + + def get_toc_model(self): + return self._navmap.get_gtktreestore() + + def get_flattoc(self): + return self._navmap.get_flattoc() + + def get_basedir(self): + return self._tempdir + + def get_info(self): + return self._info + + def close(self): + self._zobject.close() + shutil.rmtree(self._tempdir) diff --git a/epubview/epubinfo.py b/epubview/epubinfo.py new file mode 100644 index 0000000..ddf9ad5 --- /dev/null +++ b/epubview/epubinfo.py @@ -0,0 +1,90 @@ +import os +from lxml import etree + + +class EpubInfo(): #TODO: Cover the entire DC range + def __init__(self, file): + self._tree = etree.parse(file) + self._root = self._tree.getroot() + self._e_metadata = self._root.find('{http://www.idpf.org/2007/opf}metadata') + + self.title = self._get_title() + self.creator = self._get_creator() + self.date = self._get_date() + self.subject = self._get_subject() + self.source = self._get_source() + self.rights = self._get_rights() + self.identifier = self._get_identifier() + self.language = self._get_language() + + + def _get_data(self, tagname): + element = self._e_metadata.find(tagname) + return element.text + + def _get_title(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}title') + except AttributeError: + return None + + return ret + + def _get_creator(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}creator') + except AttributeError: + return None + return ret + + def _get_date(self): + #TODO: iter + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}date') + except AttributeError: + return None + + return ret + + def _get_source(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}source') + except AttributeError: + return None + + return ret + + def _get_rights(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}rights') + except AttributeError: + return None + + return ret + + def _get_identifier(self): + #TODO: iter + element = self._e_metadata.find('.//{http://purl.org/dc/elements/1.1/}identifier') + + if element is not None: + return {'id':element.get('id'), 'value':element.text} + else: + return None + + def _get_language(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}language') + except AttributeError: + return None + + return ret + + def _get_subject(self): + try: + subjectlist = [] + for element in self._e_metadata.iterfind('.//{http://purl.org/dc/elements/1.1/}subject'): + subjectlist.append(element.text) + except AttributeError: + return None + + return subjectlist \ No newline at end of file diff --git a/epubview/epubview.py b/epubview/epubview.py new file mode 100644 index 0000000..f59fb9c --- /dev/null +++ b/epubview/epubview.py @@ -0,0 +1,480 @@ +# Copyright 2009 One Laptop Per Child +# Author: Sayamindu Dasgupta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk, gtk.gdk +import gobject +import widgets + +import os.path +import math +import shutil + +from epub import _Epub +from jobs import _JobPaginator as _Paginator + + +LOADING_HTML = ''' +
+

Loading...

+
+''' + +class _View(gtk.HBox): + __gproperties__ = { + 'has-selection' : (gobject.TYPE_BOOLEAN, 'whether has selection', + 'whether the widget has selection or not', + 0, gobject.PARAM_READABLE), + 'zoom' : (gobject.TYPE_FLOAT, 'the zoom level', + 'the zoom level of the widget', + 0.5, 4.0, 1.0, gobject.PARAM_READWRITE) + } + __gsignals__ = { + 'page-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self): + gobject.threads_init() + gtk.HBox.__init__(self) + + self.connect("destroy", self._destroy_cb) + + self._ready = False + self._paginator = None + self._loaded_page = -1 + #self._old_scrollval = -1 + self._loaded_filename = None + self._pagecount = -1 + self.__going_fwd = True + self.__going_back = False + self.__page_changed = False + self.has_selection = False + self.zoom = 1.0 + self._epub = None + self._findjob = None + self.__in_search = False + self.__search_fwd = True + + self._sw = gtk.ScrolledWindow() + self._view = widgets._WebView() + self._view.load_string(LOADING_HTML, 'text/html', 'utf-8', '/') + settings = self._view.get_settings() + settings.props.default_font_family = 'DejaVu LGC Serif' + settings.props.enable_plugins = False + settings.props.default_encoding = 'utf-8' + self._view.connect('load-finished', self._view_load_finished_cb) + self._view.connect('scroll-event', self._view_scroll_event_cb) + self._view.connect('key-press-event', self._view_keypress_event_cb) + self._view.connect('button-release-event', self._view_buttonrelease_event_cb) + self._view.connect('selection-changed', self._view_selection_changed_cb) + self._view.connect_after('populate-popup', self._view_populate_popup_cb) + + self._sw.add(self._view) + self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) + self._v_vscrollbar = self._sw.get_vscrollbar() + self._v_scrollbar_value_changed_cb_id = self._v_vscrollbar.connect('value-changed', \ + self._v_scrollbar_value_changed_cb) + self._scrollbar = gtk.VScrollbar() + self._scrollbar.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self._scrollbar_change_value_cb_id = self._scrollbar.connect('change-value', \ + self._scrollbar_change_value_cb) + self.pack_start(self._sw, expand = True, fill = True) + self.pack_start(self._scrollbar, expand = False, fill = False) + + self._view.set_flags(gtk.CAN_DEFAULT|gtk.CAN_FOCUS) + + def set_document(self, epubdocumentinstance): + self._epub = epubdocumentinstance + gobject.idle_add(self._paginate) + + def do_get_property(self, property): + if property.name == 'has-selection': + return self.has_selection + elif property.name == 'zoom': + return self.zoom + else: + raise AttributeError, 'unknown property %s' % property.name + + def do_set_property(self, property, value): + if property.name == 'zoom': + self.__set_zoom(value) + else: + raise AttributeError, 'unknown property %s' % property.name + + def get_has_selection(self): + return self.get_property('has-selection') + + def get_zoom(self): + return self.get_property('zoom') + + def set_zoom(self, value): + self.set_property('zoom', value) + + def zoom_in(self): + if self.can_zoom_in(): + self.set_zoom(self.get_zoom() + 0.1) + return True + else: + return False + + def zoom_out(self): + if self.can_zoom_out(): + self.set_zoom(self.get_zoom() - 0.1) + return True + else: + return False + + def can_zoom_in(self): + if self.zoom < 4: + return True + else: + return False + + def can_zoom_out(self): + if self.zoom > 0.5: + return True + else: + return False + + def get_current_page(self): + return self._loaded_page + + def get_current_file(self): + #return self._loaded_filename + if self._paginator: + return self._paginator.get_file_for_pageno(self._loaded_page) + else: + return None + + def get_pagecount(self): + return self._pagecount + + def set_current_page(self, n): + if n < 1 or n > self._pagecount: + return False + self._load_page(n) + return True + + def next_page(self): + if self._loaded_page == self._pagecount: + return False + self._load_next_page() + return True + + def previous_page(self): + if self._loaded_page == 1: + return False + self._load_prev_page() + return True + + def scroll(self, scrolltype, horizontal): + if scrolltype == gtk.SCROLL_PAGE_BACKWARD: + self.__going_back = True + self.__going_fwd = False + if not self._do_page_transition(): + self._view.move_cursor(gtk.MOVEMENT_PAGES, -1) + elif scrolltype == gtk.SCROLL_PAGE_FORWARD: + self.__going_back = False + self.__going_fwd = True + if not self._do_page_transition(): + self._view.move_cursor(gtk.MOVEMENT_PAGES, 1) + else: + print ('Got unsupported scrolltype %s' % str(scrolltype)) + + def copy(self): + self._view.copy_clipboard() + + def find_next(self): + self._view.grab_focus() + self._view.grab_default() + + if self._view.search_text(self._findjob.get_search_text(), \ + self._findjob.get_case_sensitive(), True, False): + return + else: + path = os.path.join(self._epub.get_basedir(), self._findjob.get_next_file()) + self.__in_search = True + self.__search_fwd = True + self._load_file(path) + + def find_previous(self): + self._view.grab_focus() + self._view.grab_default() + + if self._view.search_text(self._findjob.get_search_text(), \ + self._findjob.get_case_sensitive(), False, False): + return + else: + path = os.path.join(self._epub.get_basedir(), self._findjob.get_prev_file()) + self.__in_search = True + self.__search_fwd = False + self._load_file(path) + + def _find_changed(self, job): + self._view.grab_focus() + self._view.grab_default() + self._findjob = job + #self._view.search_text(self._findjob.get_search_text(), \ + # self._findjob.get_case_sensitive(), True, False) + self.find_next() + + def __set_zoom(self, value): + self._view.set_zoom_level(value) + self.zoom = value + + def __set_has_selection(self, value): + if value != self.has_selection: + self.has_selection = value + self.notify('has-selection') + + def _view_populate_popup_cb(self, view, menu): + menu.destroy() #HACK + return + + def _view_selection_changed_cb(self, view): + # FIXME: This does not seem to be implemented in + # webkitgtk yet + print view.has_selection() + + def _view_buttonrelease_event_cb(self, view, event): + # Ugly hack + self.__set_has_selection(view.can_copy_clipboard() \ + | view.can_cut_clipboard()) + + def _view_keypress_event_cb(self, view, event): + name = gtk.gdk.keyval_name(event.keyval) + if name == 'Page_Down' or name == 'Down': + self.__going_back = False + self.__going_fwd = True + elif name == 'Page_Up' or name == 'Up': + self.__going_back = True + self.__going_fwd = False + + self._do_page_transition() + + def _view_scroll_event_cb(self, view, event): + if event.direction == gtk.gdk.SCROLL_DOWN: + self.__going_back = False + self.__going_fwd = True + elif event.direction == gtk.gdk.SCROLL_UP: + self.__going_back = True + self.__going_fwd = False + + self._do_page_transition() + + def _do_page_transition(self): + if self.__going_fwd: + if self._v_vscrollbar.get_value() >= \ + self._v_vscrollbar.props.adjustment.props.upper - \ + self._v_vscrollbar.props.adjustment.props.page_size: + self._load_next_file() + return True + elif self.__going_back: + if self._v_vscrollbar.get_value() == self._v_vscrollbar.props.adjustment.props.lower: + self._load_prev_file() + return True + + return False + + def _view_load_finished_cb(self, v, frame): + filename = self._view.props.uri.replace('file://', '') + if os.path.exists(filename.replace('xhtml', 'xml')): + filename = filename.replace('xhtml', 'xml') # Hack for making javascript work + + if self._loaded_page < 1 or filename == None: + return False + + self._loaded_filename = filename + + remfactor = self._paginator.get_remfactor_for_file(filename) + pages = self._paginator.get_pagecount_for_file(filename) + extra = int(math.ceil(remfactor * self._view.get_page_height()/(pages-remfactor))) + if extra > 0: + self._view.add_bottom_padding(extra) + + if self.__in_search: + self._view.search_text(self._findjob.get_search_text(), \ + self._findjob.get_case_sensitive(), \ + self.__search_fwd, False) + self.__in_search = False + else: + if self.__going_back: + # We need to scroll to the last page + self._scroll_page_end() + else: + self._scroll_page() + + def _scroll_page_end(self): + v_upper = self._v_vscrollbar.props.adjustment.props.upper + v_page_size = self._v_vscrollbar.props.adjustment.props.page_size + self._v_vscrollbar.set_value(v_upper) + + def _scroll_page(self): + pageno = self._loaded_page + + v_upper = self._v_vscrollbar.props.adjustment.props.upper + v_page_size = self._v_vscrollbar.props.adjustment.props.page_size + + scrollfactor = self._paginator.get_scrollfactor_pos_for_pageno(pageno) + self._v_vscrollbar.set_value((v_upper - v_page_size) * scrollfactor) + + def _paginate(self): + filelist = [] + for i in self._epub._navmap.get_flattoc(): + filelist.append(os.path.join(self._epub._tempdir, i[1])) + + self._paginator = _Paginator(filelist) + self._paginator.connect('paginated', self._paginated_cb) + + def _load_next_page(self): + self._load_page(self._loaded_page + 1) + + def _load_prev_page(self): + self._load_page(self._loaded_page - 1) + + def _v_scrollbar_value_changed_cb(self, scrollbar): + if self._loaded_page < 1: + return + scrollval = scrollbar.get_value() + scroll_upper = self._v_vscrollbar.props.adjustment.props.upper + scroll_page_size = self._v_vscrollbar.props.adjustment.props.page_size + + if self.__going_fwd == True and not self._loaded_page == self._pagecount: + if self._paginator.get_file_for_pageno(self._loaded_page) != \ + self._paginator.get_file_for_pageno(self._loaded_page + 1): + return # We don't need this if the next page is in another file + + scrollfactor_next = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page + 1) + if scrollval > 0: + scrollfactor = scrollval/(scroll_upper - scroll_page_size) + else: + scrollfactor = 0 + if scrollfactor >= scrollfactor_next: + self._on_page_changed(self._loaded_page + 1) + elif self.__going_back == True and self._loaded_page > 1: + if self._paginator.get_file_for_pageno(self._loaded_page) != \ + self._paginator.get_file_for_pageno(self._loaded_page - 1): + return + + scrollfactor_cur = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page) + if scrollval > 0: + scrollfactor = scrollval/(scroll_upper - scroll_page_size) + else: + scrollfactor = 0 + + if scrollfactor <= scrollfactor_cur: + self._on_page_changed(self._loaded_page - 1) + + def _on_page_changed(self, pageno): + self.__page_changed = True + self._loaded_page = pageno + self._scrollbar.handler_block(self._scrollbar_change_value_cb_id) + self._scrollbar.set_value(pageno) + self._scrollbar.handler_unblock(self._scrollbar_change_value_cb_id) + self.emit('page-changed') + + def _load_page(self, pageno): + if pageno > self._pagecount or pageno < 1: + #TODO: Cause an exception + return + + self._on_page_changed(pageno) + filename = self._paginator.get_file_for_pageno(pageno) + if filename != self._loaded_filename: + #self._loaded_filename = filename + if filename.endswith('xml'): + dest = filename.replace('xml', 'xhtml') + shutil.copy(filename.replace('file://', ''), dest.replace('file://', '')) + self._view.open(dest) + else: + self._view.open(filename) + else: + self._scroll_page() + + def _load_next_file(self): + cur_file = self._paginator.get_file_for_pageno(self._loaded_page) + pageno = self._loaded_page + while pageno < self._paginator.get_total_pagecount(): + pageno += 1 + if self._paginator.get_file_for_pageno(pageno) != cur_file: + break + + self._load_page(pageno) + + def _load_file(self, path): + #TODO: This is a bit suboptimal - fix it + for pageno in range(1, self.get_pagecount()): + filepath = self._paginator.get_file_for_pageno(pageno) + if filepath.endswith(path): + self._load_page(pageno) + break + + def _load_prev_file(self): + cur_file = self._paginator.get_file_for_pageno(self._loaded_page) + pageno = self._loaded_page + while pageno > 1: + pageno -= 1 + if self._paginator.get_file_for_pageno(pageno) != cur_file: + break + + self._load_page(pageno) + + def _scrollbar_change_value_cb(self, range, scrolltype, value): + if scrolltype == gtk.SCROLL_STEP_FORWARD: + self.__going_fwd = True + self.__going_back = False + if not self._do_page_transition(): + self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, 1) + elif scrolltype == gtk.SCROLL_STEP_BACKWARD: + self.__going_fwd = False + self.__going_back = True + if not self._do_page_transition(): + self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, -1) + elif scrolltype == gtk.SCROLL_JUMP or \ + scrolltype == gtk.SCROLL_PAGE_FORWARD or \ + scrolltype == gtk.SCROLL_PAGE_BACKWARD: + if value > self._scrollbar.props.adjustment.props.upper: + self._load_page(self._pagecount) + else: + self._load_page(round(value)) + else: + print 'Warning: unknown scrolltype %s with value %f' % (str(scrolltype), value) + + self._scrollbar.set_value(self._loaded_page) #FIXME: This should not be needed here + + if self.__page_changed == True: + self.__page_changed = False + return False + else: + return True + + def _paginated_cb(self, object): + self._ready = True + + self._pagecount = self._paginator.get_total_pagecount() + self._scrollbar.set_range(1.0, self._pagecount - 1.0) + self._scrollbar.set_increments(1.0, 1.0) + self._view.grab_focus() + self._view.grab_default() + if self._loaded_page < 1: + self._load_page(1) + + + + def _destroy_cb(self, widget): + self._epub.close() + diff --git a/epubview/jobs.py b/epubview/jobs.py new file mode 100644 index 0000000..de276b3 --- /dev/null +++ b/epubview/jobs.py @@ -0,0 +1,248 @@ +# Copyright 2009 One Laptop Per Child +# Author: Sayamindu Dasgupta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import gobject +import gtk +import widgets +import cairo + +import math +import os.path +import BeautifulSoup + +import epub + +import threading + +PAGE_WIDTH = 135 +PAGE_HEIGHT = 216 + +def _pixel_to_mm(pixel, dpi): + inches = pixel/dpi + return int(inches/0.03937) + +def _mm_to_pixel(mm, dpi): + inches = mm * 0.03937 + return int(inches * dpi) + + + +class SearchThread(threading.Thread): + def __init__(self, obj): + threading.Thread.__init__ (self) + self.obj = obj + self.stopthread = threading.Event() + + def _start_search(self): + for entry in self.obj.flattoc: + if self.stopthread.isSet(): + break + name, file = entry + filepath = os.path.join(self.obj._document.get_basedir(), file) + f = open(filepath) + if self._searchfile(f): + self.obj._matchfilelist.append(file) + f.close() + + gtk.gdk.threads_enter() + self.obj._finished = True + self.obj.emit('updated') + gtk.gdk.threads_leave() + + return False + + def _searchfile(self, fileobj): + soup = BeautifulSoup.BeautifulSoup(fileobj) + body = soup.find('body') + tags = body.findChildren(True) + for tag in tags: + if not tag.string is None: + if tag.string.find(self.obj._text) > -1: + return True + + return False + + def run (self): + self._start_search() + + def stop(self): + self.stopthread.set() + + + +class _JobPaginator(gobject.GObject): + __gsignals__ = { + 'paginated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self, filelist): + gobject.GObject.__init__(self) + + self._filelist = filelist + self._filedict = {} + self._pagemap = {} + + self._bookheight = 0 + self._count = 0 + self._pagecount = 0 + + self._screen = gtk.gdk.screen_get_default() + self._old_fontoptions = self._screen.get_font_options() + options = cairo.FontOptions() + options.set_hint_style(cairo.HINT_STYLE_MEDIUM) + options.set_antialias(cairo.ANTIALIAS_GRAY) + options.set_subpixel_order(cairo.SUBPIXEL_ORDER_DEFAULT) + options.set_hint_metrics(cairo.HINT_METRICS_DEFAULT) + self._screen.set_font_options(options) + + self._temp_win = gtk.Window() + self._temp_view = widgets._WebView() + + settings = self._temp_view.get_settings() + settings.props.default_font_family = 'DejaVu LGC Serif' + settings.props.sans_serif_font_family = 'DejaVu LGC Sans' + settings.props.serif_font_family = 'DejaVu LGC Serif' + settings.props.monospace_font_family = 'DejaVu LGC Sans Mono' + settings.props.enforce_96_dpi = True + #settings.props.auto_shrink_images = False #FIXME: This does not seem to work + settings.props.enable_plugins = False + settings.props.default_font_size = 12 + settings.props.default_monospace_font_size = 10 + settings.props.default_encoding = 'utf-8' + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) + self._dpi = 96 + sw.set_size_request(_mm_to_pixel(PAGE_WIDTH, self._dpi), _mm_to_pixel(PAGE_HEIGHT, self._dpi)) + sw.add(self._temp_view) + self._temp_win.add(sw) + self._temp_view.connect('load-finished', self._page_load_finished_cb) + + self._temp_win.show_all() + self._temp_win.unrealize() + + self._temp_view.open(self._filelist[self._count]) + + def _page_load_finished_cb(self, v, frame): + f = v.get_main_frame() + pageheight = v.get_page_height() + + if pageheight <= _mm_to_pixel(PAGE_HEIGHT, self._dpi): + pages = 1 + else: + pages = pageheight/float(_mm_to_pixel(PAGE_HEIGHT, self._dpi)) + for i in range(1, int(math.ceil(pages) + 1)): + if pages - i < 0: + pagelen = (pages - math.floor(pages))/pages + else: + pagelen = 1/pages + self._pagemap[float(self._pagecount + i)] = (f.props.uri, (i-1)/math.ceil(pages), pagelen) + + self._pagecount += math.ceil(pages) + self._filedict[f.props.uri.replace('file://', '')] = (math.ceil(pages), math.ceil(pages) - pages) + self._bookheight += pageheight + + if self._count+1 >= len(self._filelist): + self._temp_win.destroy() + self._screen.set_font_options(self._old_fontoptions) + self.emit('paginated') + else: + self._count += 1 + self._temp_view.open(self._filelist[self._count]) + + + def get_file_for_pageno(self, pageno): + return self._pagemap[pageno][0] + + def get_scrollfactor_pos_for_pageno(self, pageno): + return self._pagemap[pageno][1] + + def get_scrollfactor_len_for_pageno(self, pageno): + return self._pagemap[pageno][2] + + def get_pagecount_for_file(self, file): + return self._filedict[file][0] + + def get_remfactor_for_file(self, file): + return self._filedict[file][1] + + def get_total_pagecount(self): + return self._pagecount + + def get_total_height(self): + return self._bookheight + + +class _JobFind(gobject.GObject): + __gsignals__ = { + 'updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self, document, start_page, n_pages, text, case_sensitive=False): + gobject.GObject.__init__(self) + gtk.threads_init() + + self._finished = False + self._document = document + self._start_page = start_page + self._n_pages = n_pages + self._text = text + self._case_sensitive = case_sensitive + self.flattoc = self._document.get_flattoc() + self._matchfilelist = [] + self._current_file_index = 0 + self.threads = [] + + s_thread = SearchThread(self) + self.threads.append(s_thread) + s_thread.start() + + def cancel(self): + for s_thread in self.threads: + s_thread.stop() + + def is_finished(self): + return self._finished + + def get_next_file(self): + self._current_file_index += 1 + try: + path = self._matchfilelist[self._current_file_index] + except IndexError: + self._current_file_index = 0 + path = self._matchfilelist[self._current_file_index] + + return path + + def get_prev_file(self): + self._current_file_index -= 1 + try: + path = self._matchfilelist[self._current_file_index] + except IndexError: + self._current_file_index = -1 + path = self._matchfilelist[self._current_file_index] + + return path + + def get_search_text(self): + return self._text + + def get_case_sensitive(self): + return self._case_sensitive diff --git a/epubview/modules/webkit.so b/epubview/modules/webkit.so new file mode 100755 index 0000000..f8dce2a --- /dev/null +++ b/epubview/modules/webkit.so Binary files differ diff --git a/epubview/navmap.py b/epubview/navmap.py new file mode 100644 index 0000000..0ba0b8f --- /dev/null +++ b/epubview/navmap.py @@ -0,0 +1,64 @@ +from lxml import etree +import gtk + +class NavPoint(object): + def __init__(self, label, contentsrc, children = []): + self._label = label + self._contentsrc = contentsrc + self._children = children + + def get_label(self): + return self._label + + def get_contentsrc(self): + return self._contentsrc + + def get_children(self): + return self._children + + +class NavMap(object): + def __init__(self, file, basepath): + self._basepath = basepath + self._tree = etree.parse(file) + self._root = self._tree.getroot() + self._gtktreestore = gtk.TreeStore(str, str) + self._flattoc = [] + + self._populate_toc() + + def _populate_toc(self): + navmap = self._root.find('{http://www.daisy.org/z3986/2005/ncx/}navMap') + for navpoint in navmap.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint'): + self._process_navpoint(navpoint) + + def _gettitle(self, navpoint): + text = navpoint.find('./{http://www.daisy.org/z3986/2005/ncx/}navLabel/{http://www.daisy.org/z3986/2005/ncx/}text') + return text.text + + def _getcontent(self, navpoint): + text = navpoint.find('./{http://www.daisy.org/z3986/2005/ncx/}content/') + return self._basepath + text.get('src') + + def _process_navpoint(self, navpoint, parent = None): + title = self._gettitle(navpoint) + content = self._getcontent(navpoint) + + iter = self._gtktreestore.append(parent, [title, content]) + self._flattoc.append((title, content)) + + childnavpointlist = list(navpoint.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint')) + + if len(childnavpointlist): + for childnavpoint in childnavpointlist: + self._process_navpoint(childnavpoint, parent = iter) + else: + return + + def get_gtktreestore(self): + return self._gtktreestore + + def get_flattoc(self): + return self._flattoc + +#t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx') \ No newline at end of file diff --git a/epubview/widgets.py b/epubview/widgets.py new file mode 100644 index 0000000..19d2dc9 --- /dev/null +++ b/epubview/widgets.py @@ -0,0 +1,27 @@ +try: + import webkit +except ImportError: + import os, sys + sys.path.append(os.path.join(os.environ['SUGAR_BUNDLE_PATH'], 'epubview', 'modules')) + import webkit + +import gtk + + +class _WebView(webkit.WebView): + def __init__(self): + webkit.WebView.__init__(self) + + def get_page_height(self): + #TODO: Need to check status of page load + js = 'oldtitle=document.title;document.title=document.body.clientHeight;' + self.execute_script(js) + ret = self.get_main_frame().get_title() + js = 'document.title=oldtitle;' + self.execute_script(js) + return int(ret) + + def add_bottom_padding(self, incr): + js = ('var newdiv = document.createElement("div");newdiv.style.height = "%dpx";document.body.appendChild(newdiv);' % incr) + self.execute_script(js) + -- cgit v0.9.1