From d7b3778a6915e56d680413c2ea2fd70b251f84ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 13:09:48 -0600 Subject: [PATCH 01/10] Save column widths in device views on device disconnect. --- src/calibre/gui2/__init__.py | 1 + src/calibre/gui2/main.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 2e965fb295..fa03c3a775 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -274,6 +274,7 @@ class GetMetadata(QObject): self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi) class TableView(QTableView): + def __init__(self, parent): QTableView.__init__(self, parent) self.read_settings() diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 66b880f0b2..43ccfe2073 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -742,6 +742,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ########################## Connect to device ############################## + def save_device_view_settings(self): + model = self.location_view.model() + self.memory_view.write_settings() + for x in range(model.rowCount()): + if x > 1: + if model.location_for_row(x) == 'carda': + self.card_a_view.write_settings() + elif model.location_for_row(x) == 'cardb': + self.carb_b_view.write_settings() + def device_detected(self, connected): ''' Called when a device is connected to the computer. @@ -757,6 +767,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.device_connected = True self._sync_menu.enable_device_actions(True, self.device_manager.device.card_prefix()) else: + self.save_device_view_settings() self.device_connected = False self._sync_menu.enable_device_actions(False) self.location_view.model().update_devices() @@ -765,7 +776,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.device_info = ' ' if self.current_view() != self.library_view: self.status_bar.reset_info() - self.location_selected('library') + self.location_view.setCurrentIndex(self.location_view.model().index(0)) def info_read(self, job): ''' @@ -807,6 +818,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.card_b_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA) for view in (self.memory_view, self.card_a_view, self.card_b_view): view.sortByColumn(3, Qt.DescendingOrder) + view.read_settings() if not view.restore_column_widths(): view.resizeColumnsToContents() view.resizeRowsToContents() @@ -1662,7 +1674,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): dynamic.set('sort_column', self.library_view.model().sorted_on) self.library_view.write_settings() if self.device_connected: - self.memory_view.write_settings() + self.save_device_view_settings() def restart(self): self.quit(restart=True) From c6853892d08cac0b4cc99f84fb73d97645fa0ce9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 13:10:36 -0600 Subject: [PATCH 02/10] IGN:Add an entry to the FAQ about how to use content server with Kindle --- src/calibre/manual/faq.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 809c1a58d2..13b139bd05 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -108,7 +108,7 @@ will appear in the next release of |app|. Can I use both |app| and the SONY software to manage my reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Yes, you can use both, provided you don not run them at the same time. That is, you should use the following sequence: +Yes, you can use both, provided you do not run them at the same time. That is, you should use the following sequence: Connect reader->Use one of the programs->Disconnect reader. Reconnect reader->Use the other program->disconnect reader. The underlying reason is that the Reader uses a single file to keep track @@ -122,7 +122,7 @@ other via the computers hard disk. If you do need to reset your metadata due to problems caused by using both at the same time, then just delete the media.xml file on the Reader using -your PC's file explorer and it'll be recreated after disconnection. +your PC's file explorer and it will be recreated after disconnection. Can I use the collections feature of the SONY reader? @@ -149,6 +149,21 @@ How do I use |app| with my Android phone? First install the WordPlayer e-book reading app from the Android Marketplace onto you phone. Then simply plug your phone into the computer with a USB cable. |app| should automatically detect the phone and then you can transfer books to it by clicking the Send to Device button. |app| does not have support for every single androind device out there, so if you would like to have support for your device added, follow the instructions above for getting your device supported in |app|. +Can I access my |app| books using the web browser in my Kindle or other reading device? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +|app| has a *Content Server* that exports the books in |app| as a web page. You can turn it on under +Preferences->Content Server. Then just point the web browser on your device to the computer running +the Content Server and you will be able to browse your book collection. For example, if the computer running +the server has IP address 63.45.128.5, in the browser, you would type:: + + http://63.45.128.5:8080 + +Some devices, like the Kindle, do not allow you to access port 8080 (the default port on which the content +server runs. In that case, change the port in the |app| Preferences to 80. (On some operating systems, +you may not be able to run the server on a port number less than 1024 because of security settings. In +this case the simplest solution is to adjust your router to forward requests on port 80 to port 8080). + I get the error message "Failed to start content server: Port 8080 not free on '0.0.0.0'"? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ceace20197ce938116c93a6081b1c03572177542 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 13:12:06 -0600 Subject: [PATCH 03/10] IGN:Cleanup of ebook viewer code --- resources/viewer/bookmarks.js | 71 +++++++++++ resources/viewer/hyphenation.js | 26 ++++ resources/viewer/referencing.js | 62 ++++++++++ setup/installer/osx/freeze.py | 2 +- src/calibre/gui2/viewer/documentview.py | 31 +++-- src/calibre/gui2/viewer/js.py | 156 ------------------------ 6 files changed, 183 insertions(+), 165 deletions(-) create mode 100644 resources/viewer/bookmarks.js create mode 100644 resources/viewer/hyphenation.js create mode 100644 resources/viewer/referencing.js delete mode 100644 src/calibre/gui2/viewer/js.py diff --git a/resources/viewer/bookmarks.js b/resources/viewer/bookmarks.js new file mode 100644 index 0000000000..c6f59414ab --- /dev/null +++ b/resources/viewer/bookmarks.js @@ -0,0 +1,71 @@ +/* + * bookmarks management + * Copyright 2008 Kovid Goyal + * License: GNU GPL v3 + */ + +function selector_in_parent(elem) { + var num = elem.prevAll().length; + var sel = " > *:eq("+num+") "; + return sel; +} + +function selector(elem) { + var obj = elem; + var sel = ""; + while (obj[0] != document) { + sel = selector_in_parent(obj) + sel; + obj = obj.parent(); + } + return sel; +} + +function find_closest_enclosing_block(top) { + var START = top-1000; + var STOP = top; + var matches = []; + var elem, temp; + var width = 1000; + + for (y = START; y < STOP; y += 20) { + for ( x = 0; x < width; x += 20) { + elem = document.elementFromPoint(x, y); + try { + elem = $(elem); + temp = elem.offset().top + matches.push(elem); + if (Math.abs(temp - START) < 25) { y = STOP; break} + } catch(error) {} + } + } + + var miny = Math.abs(matches[0].offset().top - START), min_elem = matches[0]; + + for (i = 1; i < matches.length; i++) { + elem = matches[i]; + temp = Math.abs(elem.offset().top - START); + if ( temp < miny ) { miny = temp; min_elem = elem; } + } + return min_elem; +} + +function calculate_bookmark(y) { + var elem = find_closest_enclosing_block(y); + var sel = selector(elem); + var ratio = (y - elem.offset().top)/elem.height(); + if (ratio > 1) { ratio = 1; } + if (ratio < 0) { ratio = 0; } + return sel + "|" + ratio; +} + +function animated_scrolling_done() { + window.py_bridge.animated_scroll_done(); +} + +function scroll_to_bookmark(bookmark) { + bm = bookmark.split("|"); + var ratio = 0.7 * parseFloat(bm[1]); + $.scrollTo($(bm[0]), 1000, + {over:ratio, onAfter:function(){window.py_bridge.animated_scroll_done()}}); +} + diff --git a/resources/viewer/hyphenation.js b/resources/viewer/hyphenation.js new file mode 100644 index 0000000000..a0ba6d1f34 --- /dev/null +++ b/resources/viewer/hyphenation.js @@ -0,0 +1,26 @@ +/* + * bookmarks management + * Copyright 2008 Kovid Goyal + * License: GNU GPL v3 + */ + +function init_hyphenate() { + window.py_bridge.init_hyphenate(); +} + +document.addEventListener("DOMContentLoaded", init_hyphenate, false); + +function do_hyphenation(lang) { + Hyphenator.config( + { + 'minwordlength' : 6, + //'hyphenchar' : '|', + 'displaytogglebox' : false, + 'remoteloading' : false, + 'onerrorhandler' : function (e) { + window.py_bridge.debug(e); + } + }); + Hyphenator.hyphenate(document.body, lang); +} + diff --git a/resources/viewer/referencing.js b/resources/viewer/referencing.js new file mode 100644 index 0000000000..6de1a06be9 --- /dev/null +++ b/resources/viewer/referencing.js @@ -0,0 +1,62 @@ +/* + * reference management + * Copyright 2008 Kovid Goyal + * License: GNU GPL v3 + */ + + + +var reference_old_bgcol = "transparent"; +var reference_prefix = "1."; + +function show_reference_panel(ref) { + panel = $("#calibre_reference_panel"); + if (panel.length < 1) { + $(document.body).append('
Paragraph

None

') + panel = $("#calibre_reference_panel"); + } + $("> p", panel).text(ref); + panel.css({top:(window.pageYOffset+20)+"px"}); + panel.fadeIn(500); +} + +function toggle_reference(e) { + p = $(this); + if (e.type == "mouseenter") { + reference_old_bgcol = p.css("background-color"); + p.css({backgroundColor:"beige"}); + var i = 0; + var paras = $("p"); + for (j = 0; j < paras.length; j++,i++) { + if (paras[j] == p[0]) break; + } + show_reference_panel(reference_prefix+(i+1) ); + } else { + p.css({backgroundColor:reference_old_bgcol}); + panel = $("#calibre_reference_panel").hide(); + } + return false; +} + +function enter_reference_mode() { + $("p").bind("mouseenter mouseleave", toggle_reference); +} + +function leave_reference_mode() { + $("p").unbind("mouseenter mouseleave", toggle_reference); +} + +function goto_reference(ref) { + var tokens = ref.split("."); + if (tokens.length != 2) {alert("Invalid reference: "+ref); return;} + var num = parseInt(tokens[1]); + if (isNaN(num)) {alert("Invalid reference: "+ref); return;} + num -= 1; + if (num < 0) {alert("Invalid reference: "+ref); return;} + var p = $("p"); + if (num >= p.length) {alert("Reference not found: "+ref); return;} + $.scrollTo($(p[num]), 1000, + {onAfter:function(){window.py_bridge.animated_scroll_done()}}); +} + + diff --git a/setup/installer/osx/freeze.py b/setup/installer/osx/freeze.py index f30a037703..281432fcf3 100644 --- a/setup/installer/osx/freeze.py +++ b/setup/installer/osx/freeze.py @@ -386,7 +386,7 @@ def main(): { 'optimize' : 2, 'dist_dir' : 'build/py2app', - 'argv_emulation' : False, + 'argv_emulation' : True, 'iconfile' : icon, 'frameworks': ['libusb.dylib', 'libunrar.dylib'], 'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml', diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index aeaf8e2c4f..8e86604603 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -15,11 +15,12 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings from calibre.utils.config import Config, StringConfig from calibre.utils.localization import get_language from calibre.gui2.viewer.config_ui import Ui_Dialog -from calibre.gui2.viewer.js import bookmarks, referencing, hyphenation from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import iswindows from calibre import prints, guess_type +bookmarks = referencing = hyphenation = jquery = jquery_scrollTo = hyphenator = None + def load_builtin_fonts(): base = P('fonts/liberation/*.ttf') for f in glob.glob(base): @@ -192,15 +193,24 @@ class Document(QWebPage): self.hyphenate_default_lang = opts.hyphenate_default_lang def load_javascript_libraries(self): + global bookmarks, referencing, hyphenation, jquery, jquery_scrollTo, hyphenator self.mainFrame().addToJavaScriptWindowObject("py_bridge", self) - jquery = open(P('content_server/jquery.js'), 'rb').read() - jquery_scrollTo = open(P('viewer/jquery_scrollTo.js'), 'rb').read() - hyphenator = open(P('viewer/hyphenate/Hyphenator.js'), - 'rb').read().decode('utf-8') + if jquery is None: + jquery = P('content_server/jquery.js', data=True) + if jquery_scrollTo is None: + jquery_scrollTo = P('viewer/jquery_scrollTo.js', data=True) + if hyphenator is None: + hyphenator = P('viewer/hyphenate/Hyphenator.js', data=True).decode('utf-8') self.javascript(jquery) self.javascript(jquery_scrollTo) + if bookmarks is None: + bookmarks = P('viewer/bookmarks.js', data=True) self.javascript(bookmarks) + if referencing is None: + referencing = P('viewer/referencing.js', data=True) self.javascript(referencing) + if hyphenation is None: + hyphenation = P('viewer/hyphenation.js', data=True) self.javascript(hyphenation) default_lang = self.hyphenate_default_lang lang = self.current_language @@ -333,6 +343,7 @@ class Document(QWebPage): def width(self): return self.mainFrame().contentsSize().width() # offsetWidth gives inaccurate results + class EntityDeclarationProcessor(object): def __init__(self, html): @@ -508,6 +519,7 @@ class DocumentView(QWebView): @classmethod def test_line(cls, img, y): + 'Test if line contains pixels of exactly the same color' start = img.pixel(0, y) for i in range(1, img.width()): if img.pixel(i, y) != start: @@ -517,6 +529,7 @@ class DocumentView(QWebView): def find_next_blank_line(self, overlap): img = QImage(self.width(), overlap, QImage.Format_ARGB32) painter = QPainter(img) + # Render a region of width x overlap pixels atthe bottom of the current viewport self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap)) painter.end() for i in range(overlap-1, -1, -1): @@ -542,18 +555,20 @@ class DocumentView(QWebView): self.manager.scrolled(self.scroll_fraction) def next_page(self): - delta_y = self.document.window_height - 25 + window_height = self.document.window_height + delta_y = window_height - 25 if self.document.at_bottom: if self.manager is not None: self.manager.next_document() else: opos = self.document.ypos lower_limit = opos + delta_y - max_y = self.document.height - self.document.window_height + max_y = self.document.height - window_height lower_limit = min(max_y, lower_limit) if lower_limit > opos: self.document.scroll_to(self.document.xpos, lower_limit) - self.find_next_blank_line( self.height() - (self.document.ypos-opos) ) + actually_scrolled = self.document.ypos - opos + self.find_next_blank_line(window_height - actually_scrolled) if self.manager is not None: self.manager.scrolled(self.scroll_fraction) diff --git a/src/calibre/gui2/viewer/js.py b/src/calibre/gui2/viewer/js.py deleted file mode 100644 index 68768829bb..0000000000 --- a/src/calibre/gui2/viewer/js.py +++ /dev/null @@ -1,156 +0,0 @@ -bookmarks = ''' - -function selector_in_parent(elem) { - var num = elem.prevAll().length; - var sel = " > *:eq("+num+") "; - return sel; -} - -function selector(elem) { - var obj = elem; - var sel = ""; - while (obj[0] != document) { - sel = selector_in_parent(obj) + sel; - obj = obj.parent(); - } - return sel; -} - -function find_closest_enclosing_block(top) { - var START = top-1000; - var STOP = top; - var matches = []; - var elem, temp; - var width = 1000; - - for (y = START; y < STOP; y += 20) { - for ( x = 0; x < width; x += 20) { - elem = document.elementFromPoint(x, y); - try { - elem = $(elem); - temp = elem.offset().top - matches.push(elem); - if (Math.abs(temp - START) < 25) { y = STOP; break} - } catch(error) {} - } - } - - var miny = Math.abs(matches[0].offset().top - START), min_elem = matches[0]; - - for (i = 1; i < matches.length; i++) { - elem = matches[i]; - temp = Math.abs(elem.offset().top - START); - if ( temp < miny ) { miny = temp; min_elem = elem; } - } - return min_elem; -} - -function calculate_bookmark(y) { - var elem = find_closest_enclosing_block(y); - var sel = selector(elem); - var ratio = (y - elem.offset().top)/elem.height(); - if (ratio > 1) { ratio = 1; } - if (ratio < 0) { ratio = 0; } - return sel + "|" + ratio; -} - -function animated_scrolling_done() { - window.py_bridge.animated_scroll_done(); -} - -function scroll_to_bookmark(bookmark) { - bm = bookmark.split("|"); - var ratio = 0.7 * parseFloat(bm[1]); - $.scrollTo($(bm[0]), 1000, - {over:ratio, onAfter:function(){window.py_bridge.animated_scroll_done()}}); -} - -''' - -referencing = ''' -var reference_old_bgcol = "transparent"; -var reference_prefix = "1."; - -function show_reference_panel(ref) { - panel = $("#calibre_reference_panel"); - if (panel.length < 1) { - $(document.body).append('
Paragraph

None

') - panel = $("#calibre_reference_panel"); - } - $("> p", panel).text(ref); - panel.css({top:(window.pageYOffset+20)+"px"}); - panel.fadeIn(500); -} - -function toggle_reference(e) { - p = $(this); - if (e.type == "mouseenter") { - reference_old_bgcol = p.css("background-color"); - p.css({backgroundColor:"beige"}); - var i = 0; - var paras = $("p"); - for (j = 0; j < paras.length; j++,i++) { - if (paras[j] == p[0]) break; - } - show_reference_panel(reference_prefix+(i+1) ); - } else { - p.css({backgroundColor:reference_old_bgcol}); - panel = $("#calibre_reference_panel").hide(); - } - return false; -} - -function enter_reference_mode() { - $("p").bind("mouseenter mouseleave", toggle_reference); -} - -function leave_reference_mode() { - $("p").unbind("mouseenter mouseleave", toggle_reference); -} - -function goto_reference(ref) { - var tokens = ref.split("."); - if (tokens.length != 2) {alert("Invalid reference: "+ref); return;} - var num = parseInt(tokens[1]); - if (isNaN(num)) {alert("Invalid reference: "+ref); return;} - num -= 1; - if (num < 0) {alert("Invalid reference: "+ref); return;} - var p = $("p"); - if (num >= p.length) {alert("Reference not found: "+ref); return;} - $.scrollTo($(p[num]), 1000, - {onAfter:function(){window.py_bridge.animated_scroll_done()}}); -} - -''' - -test = ''' -$(document.body).click(function(e) { - bm = calculate_bookmark(e.pageY); - scroll_to_bookmark(bm); -}); - -$(document).ready(enter_reference_mode); - -''' - -hyphenation = ''' -function init_hyphenate() { - window.py_bridge.init_hyphenate(); -} - -document.addEventListener("DOMContentLoaded", init_hyphenate, false); - -function do_hyphenation(lang) { - Hyphenator.config( - { - 'minwordlength' : 6, - //'hyphenchar' : '|', - 'displaytogglebox' : false, - 'remoteloading' : false, - 'onerrorhandler' : function (e) { - window.py_bridge.debug(e); - } - }); - Hyphenator.hyphenate(document.body, lang); -} -''' From 19d9edb2f7b90ef4acb98ec53faaf60fc9e449b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 13:13:36 -0600 Subject: [PATCH 04/10] Add user specified cover page support to FB2 and RB Output. Fix bug when adding images to RB Output. --- src/calibre/ebooks/fb2/fb2ml.py | 3 +++ src/calibre/ebooks/rb/rbml.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index 16c822d263..78ecc94681 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -107,6 +107,9 @@ class FB2MLizer(object): def get_cover_page(self): output = u'' + if 'cover' in self.oeb_book.guide: + output += '' + self.image_hrefs[self.oeb_book.guide['cover'].href] = 'cover.jpg' if 'titlepage' in self.oeb_book.guide: self.log.debug('Generating cover page...') href = self.oeb_book.guide['titlepage'].href diff --git a/src/calibre/ebooks/rb/rbml.py b/src/calibre/ebooks/rb/rbml.py index c293880343..5574aa94b6 100644 --- a/src/calibre/ebooks/rb/rbml.py +++ b/src/calibre/ebooks/rb/rbml.py @@ -82,13 +82,16 @@ class RBMLizer(object): def get_cover_page(self): output = u'' + if 'cover' in self.oeb_book.guide: + if self.name_map.get(self.oeb_book.guide['cover'].href, None): + output += '' % self.name_map[self.oeb_book.guide['cover'].href] if 'titlepage' in self.oeb_book.guide: self.log.debug('Generating cover page...') href = self.oeb_book.guide['titlepage'].href item = self.oeb_book.manifest.hrefs[href] if item.spine_position is None: stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts.output_profile) - output += self.dump_text(item.data.find(XHTML('body')), stylizer, item) + output += ''.join(self.dump_text(item.data.find(XHTML('body')), stylizer, item)) return output def get_toc(self): @@ -152,7 +155,7 @@ class RBMLizer(object): if tag in IMAGE_TAGS: if elem.attrib.get('src', None): if page.abshref(elem.attrib['src']) not in self.name_map.keys(): - self.name_map[page.abshref(elem.attrib['src'])] = unique_name('%s' % len(self.image_hrefs.keys()), self.image_hrefs.keys(), self.name_map.keys()) + self.name_map[page.abshref(elem.attrib['src'])] = unique_name('%s' % len(self.name_map.keys()), self.name_map.keys()) text.append('' % self.name_map[page.abshref(elem.attrib['src'])]) rb_tag = tag.upper() if tag in TAGS else None From f0a7a8ecd8237b8024c27409bdcc05f4cb9499aa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 14:17:18 -0600 Subject: [PATCH 05/10] E-book viewer: Add partial support for CSS font aliasing --- src/calibre/ebooks/oeb/iterator.py | 74 ++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index 565ceed519..d16cdc9e10 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -19,7 +19,7 @@ from calibre.utils.zipfile import safe_replace, ZipFile from calibre.utils.config import DynamicConfig from calibre.utils.logging import Log from calibre.ebooks.epub.output import EPUBOutput -from calibre import guess_type +from calibre import guess_type, prints TITLEPAGE = EPUBOutput.TITLEPAGE_COVER.decode('utf-8') @@ -99,29 +99,63 @@ class EbookIterator(object): if text in open(path, 'rb').read().decode(path.encoding).lower(): return i + def find_missing_css_files(self): + for x in os.walk(os.path.dirname(self.pathtoopf)): + for f in x[-1]: + if f.endswith('.css'): + yield os.path.join(x[0], f) + + def find_declared_css_files(self): + for item in self.opf.manifest: + if item.mime_type and 'css' in item.mime_type.lower(): + yield item.path + def find_embedded_fonts(self): ''' This will become unnecessary once Qt WebKit supports the @font-face rule. ''' - for item in self.opf.manifest: - if item.mime_type and 'css' in item.mime_type.lower(): - css = open(item.path, 'rb').read().decode('utf-8', 'replace') - for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css): - block = match.group(1) - family = re.compile(r'font-family\s*:\s*([^;]+)').search(block) - url = re.compile(r'url\s*\([\'"]*(.+?)[\'"]*\)', re.DOTALL).search(block) - if url: - path = url.group(1).split('/') - path = os.path.join(os.path.dirname(item.path), *path) - id = QFontDatabase.addApplicationFont(path) - if id != -1: - families = [unicode(f) for f in QFontDatabase.applicationFontFamilies(id)] - if family: - family = family.group(1).strip().replace('"', '') - if family not in families: - print 'WARNING: Family aliasing not supported:', block - else: - print 'Loaded embedded font:', repr(family) + css_files = set(self.find_declared_css_files()) + if not css_files: + css_files = set(self.find_missing_css_files()) + bad_map = {} + font_family_pat = re.compile(r'font-family\s*:\s*([^;]+)') + for csspath in css_files: + css = open(csspath, 'rb').read().decode('utf-8', 'replace') + for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css): + block = match.group(1) + family = font_family_pat.search(block) + url = re.compile(r'url\s*\([\'"]*(.+?)[\'"]*\)', re.DOTALL).search(block) + if url: + path = url.group(1).split('/') + path = os.path.join(os.path.dirname(csspath), *path) + if not os.access(path, os.R_OK): + continue + id = QFontDatabase.addApplicationFont(path) + if id != -1: + families = [unicode(f) for f in QFontDatabase.applicationFontFamilies(id)] + if family: + family = family.group(1).strip().replace('"', '') + bad_map[family] = families[0] + if family not in families: + prints('WARNING: Family aliasing not fully supported.') + prints('\tDeclared family: %s not in actual families: %s' + % (family, families)) + else: + prints('Loaded embedded font:', repr(family)) + if bad_map: + def prepend_embedded_font(match): + for bad, good in bad_map.items(): + if bad in match.group(1): + prints('Substituting font family: %s -> %s'%(bad, good)) + return 'font-family: %s;'%good + + for csspath in css_files: + with open(csspath, 'r+b') as f: + css = f.read() + css = font_family_pat.sub(prepend_embedded_font, css) + f.seek(0) + f.truncate() + f.write(css) def __enter__(self, processed=False): self.delete_on_exit = [] From 8f8676d25ad561127d7781e2291b5a041ce71d4a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Oct 2009 14:41:47 -0600 Subject: [PATCH 06/10] IGN:Fix --develop-from resulting in unusable ebook-viewer on windows --- src/calibre/gui2/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index fa03c3a775..51d88b06e2 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -586,8 +586,11 @@ def build_forms(srcdir, info=None): if form.endswith('viewer%smain.ui'%os.sep): info('\t\tPromoting WebView') dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(') + dat = dat.replace('from PyQt4 import QtWebKit', '') + if iswindows: + dat = dat.replace('self.view = QWebView(', 'self.view = DocumentView(') + dat = dat.replace('from QtWebKit.QWebView import QWebView', '') dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView' - dat += '\nQtWebKit' open(compiled_form, 'wb').write(dat) From 2055b3bdcce3ee997c56d3c7ea8ea8b354b4e7d5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Nov 2009 09:04:07 -0700 Subject: [PATCH 07/10] IGN:... --- resources/recipes/economist.recipe | 6 ++++-- src/calibre/ebooks/oeb/iterator.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/recipes/economist.recipe b/resources/recipes/economist.recipe index a5e43e360a..75cb86863a 100644 --- a/resources/recipes/economist.recipe +++ b/resources/recipes/economist.recipe @@ -16,9 +16,11 @@ class Economist(BasicNewsRecipe): language = 'en' __author__ = "Kovid Goyal" - description = 'Global news and current affairs from a European perspective' - oldest_article = 7.0 INDEX = 'http://www.economist.com/printedition' + description = ('Global news and current affairs from a European perspective.' + ' Needs a subscription from ')+INDEX + + oldest_article = 7.0 cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg' remove_tags = [dict(name=['script', 'noscript', 'title'])] remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body') diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index d16cdc9e10..09ae23ada3 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -147,7 +147,7 @@ class EbookIterator(object): for bad, good in bad_map.items(): if bad in match.group(1): prints('Substituting font family: %s -> %s'%(bad, good)) - return 'font-family: %s;'%good + return match.group().replace(bad, '"%s"'%good) for csspath in css_files: with open(csspath, 'r+b') as f: From 4ab7c54c6776f14e08ad972a9a3eb39b8d159178 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Nov 2009 08:25:21 -0700 Subject: [PATCH 08/10] Improved recipe for Cyberpresse --- resources/recipes/cyberpresse.recipe | 51 +++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/resources/recipes/cyberpresse.recipe b/resources/recipes/cyberpresse.recipe index 9e20c11502..5de953df3a 100644 --- a/resources/recipes/cyberpresse.recipe +++ b/resources/recipes/cyberpresse.recipe @@ -7,18 +7,51 @@ class Cyberpresse(BasicNewsRecipe): __author__ = 'balok' description = 'Canadian news in French' language = 'fr' - + oldest_article = 7 max_articles_per_feed = 100 no_stylesheets = True + remove_javascript = True html2lrf_options = ['--left-margin=0','--right-margin=0','--top-margin=0','--bottom-margin=0'] + encoding = 'utf-8' - preprocess_regexps = [ - (re.compile(r'', re.IGNORECASE | re.DOTALL), lambda match : ''), - (re.compile(r'.*?', re.IGNORECASE | re.DOTALL), lambda match : ''), - (re.compile(r'Agrandir.*?', re.IGNORECASE | re.DOTALL), lambda match : '
'), - ] - - - feeds = [(u'Manchettes', u'http://www.cyberpresse.ca/rss/225.xml'),(u'Capitale nationale', u'http://www.cyberpresse.ca/rss/501.xml'),(u'Opinions', u'http://www.cyberpresse.ca/rss/977.xml'),(u'Insolite', u'http://www.cyberpresse.ca/rss/279.xml')] + + keep_only_tags = [dict(name='div', attrs={'class':'article-page'}), + dict(name='div', attrs={'id':'articlePage'}), + ] + + extra_css = ''' + .photodata{font-family:Arial,Helvetica,Verdana,sans-serif;color: #999999; font-size: 90%; } + h1{font-family:Georgia,Times,serif ; font-size: large; } + .amorce{font-family:Arial,Helvetica,Verdana,sans-serif; font-weight:bold;} + .article-page{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;} + #articlePage{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;} + .auteur{font-family:Georgia,Times,sans-serif; font-size: 90%; color:#006699 ;} + .bodyText{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;} + .byLine{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: 90%;} + .entry{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: x-small;} + .minithumb-auteurs{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: 90%; } + a{color:#003399; font-weight:bold; } + ''' + + remove_tags = [ + dict(name='div', attrs={'class':['centerbar','colspan','share-module']}), + dict(name='p', attrs={'class':['zoom']}), + dict(name='ul', attrs={'class':['stories']}), + dict(name='h4', attrs={'class':['general-cat']}), + ] + + feeds = [(u'Manchettes', u'http://www.cyberpresse.ca/rss/225.xml'), + (u'Capitale nationale', u'http://www.cyberpresse.ca/rss/501.xml'), + (u'Opinions', u'http://www.cyberpresse.ca/rss/977.xml'), + (u'Insolite', u'http://www.cyberpresse.ca/rss/279.xml') + ] + def postprocess_html(self, soup, first): + + for tag in soup.findAll(name=['i','strong']): + tag.name = 'div' + + return soup + + From 3c66674bd845f4939ade44e5831ab97f75915b16 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Nov 2009 08:38:14 -0700 Subject: [PATCH 09/10] Fix #3926 (Get list of books on device fails with Kindle DX with 0.6.20) --- resources/recipes/cyberpresse.recipe | 25 ++++++++++++------------- src/calibre/devices/nuut2/driver.py | 2 +- src/calibre/ebooks/metadata/meta.py | 1 + 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/resources/recipes/cyberpresse.recipe b/resources/recipes/cyberpresse.recipe index 5de953df3a..25a46e3012 100644 --- a/resources/recipes/cyberpresse.recipe +++ b/resources/recipes/cyberpresse.recipe @@ -1,25 +1,24 @@ -import re from calibre.web.feeds.news import BasicNewsRecipe class Cyberpresse(BasicNewsRecipe): title = u'Cyberpresse' - __author__ = 'balok' + __author__ = 'balok and Sujata Raman' description = 'Canadian news in French' language = 'fr' - + oldest_article = 7 max_articles_per_feed = 100 no_stylesheets = True remove_javascript = True html2lrf_options = ['--left-margin=0','--right-margin=0','--top-margin=0','--bottom-margin=0'] encoding = 'utf-8' - - + + keep_only_tags = [dict(name='div', attrs={'class':'article-page'}), dict(name='div', attrs={'id':'articlePage'}), ] - + extra_css = ''' .photodata{font-family:Arial,Helvetica,Verdana,sans-serif;color: #999999; font-size: 90%; } h1{font-family:Georgia,Times,serif ; font-size: large; } @@ -33,14 +32,14 @@ class Cyberpresse(BasicNewsRecipe): .minithumb-auteurs{font-family:Arial,Helvetica,Verdana,sans-serif; font-size: 90%; } a{color:#003399; font-weight:bold; } ''' - + remove_tags = [ dict(name='div', attrs={'class':['centerbar','colspan','share-module']}), dict(name='p', attrs={'class':['zoom']}), dict(name='ul', attrs={'class':['stories']}), dict(name='h4', attrs={'class':['general-cat']}), ] - + feeds = [(u'Manchettes', u'http://www.cyberpresse.ca/rss/225.xml'), (u'Capitale nationale', u'http://www.cyberpresse.ca/rss/501.xml'), (u'Opinions', u'http://www.cyberpresse.ca/rss/977.xml'), @@ -48,10 +47,10 @@ class Cyberpresse(BasicNewsRecipe): ] def postprocess_html(self, soup, first): - + for tag in soup.findAll(name=['i','strong']): - tag.name = 'div' - + tag.name = 'div' + return soup - - + + diff --git a/src/calibre/devices/nuut2/driver.py b/src/calibre/devices/nuut2/driver.py index ff5b09c2b1..0cfa73f276 100644 --- a/src/calibre/devices/nuut2/driver.py +++ b/src/calibre/devices/nuut2/driver.py @@ -19,7 +19,7 @@ class NUUT2(USBMS): supported_platforms = ['windows', 'osx', 'linux'] # Ordered list of supported formats - FORMATS = ['epub', 'pdft', 'txt'] + FORMATS = ['epub', 'pdf', 'txt'] DRM_FORMATS = ['epub'] VENDOR_ID = [0x140e] diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index 2fb70d71b8..857e8e492e 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -34,6 +34,7 @@ def metadata_from_formats(formats): mi = metadata_from_filename(list(iter(formats))[0]) if not mi.authors: mi.authors = [_('Unknown')] + return mi def _metadata_from_formats(formats): mi = MetaInformation(None, None) From d07738ccd24b9498236b1f407b34e31194d3fe95 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Nov 2009 10:09:20 -0700 Subject: [PATCH 10/10] Implement #3519 (A recipe for Guardian print edition) --- resources/recipes/guardian.recipe | 108 ++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/resources/recipes/guardian.recipe b/resources/recipes/guardian.recipe index aad217533b..a493072034 100644 --- a/resources/recipes/guardian.recipe +++ b/resources/recipes/guardian.recipe @@ -15,8 +15,8 @@ class Guardian(BasicNewsRecipe): __author__ = 'Seabound and Sujata Raman' language = 'en_GB' - oldest_article = 7 - max_articles_per_feed = 20 + #oldest_article = 7 + #max_articles_per_feed = 100 remove_javascript = True timefmt = ' [%a, %d %b %Y]' @@ -45,26 +45,94 @@ class Guardian(BasicNewsRecipe): - feeds = [ - ('Front Page', 'http://www.guardian.co.uk/rss'), - ('Business', 'http://www.guardian.co.uk/business/rss'), - ('Sport', 'http://www.guardian.co.uk/sport/rss'), - ('Culture', 'http://www.guardian.co.uk/culture/rss'), - ('Money', 'http://www.guardian.co.uk/money/rss'), - ('Life & Style', 'http://www.guardian.co.uk/lifeandstyle/rss'), - ('Travel', 'http://www.guardian.co.uk/travel/rss'), - ('Environment', 'http://www.guardian.co.uk/environment/rss'), - ('Comment','http://www.guardian.co.uk/commentisfree/rss'), - ] + # feeds = [ + # ('Front Page', 'http://www.guardian.co.uk/rss'), + # ('Business', 'http://www.guardian.co.uk/business/rss'), + # ('Sport', 'http://www.guardian.co.uk/sport/rss'), + # ('Culture', 'http://www.guardian.co.uk/culture/rss'), + # ('Money', 'http://www.guardian.co.uk/money/rss'), + # ('Life & Style', 'http://www.guardian.co.uk/lifeandstyle/rss'), + # ('Travel', 'http://www.guardian.co.uk/travel/rss'), + # ('Environment', 'http://www.guardian.co.uk/environment/rss'), + # ('Comment','http://www.guardian.co.uk/commentisfree/rss'), + # ] - def get_article_url(self, article): - url = article.get('guid', None) - if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \ - '/gallery/' in url or 'ivebeenthere' in url or \ - 'pickthescore' in url or 'audioslideshow' in url : - url = None - return url + # def get_article_url(self, article): + # url = article.get('guid', None) + # if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \ + # '/gallery/' in url or 'ivebeenthere' in url or \ + # 'pickthescore' in url or 'audioslideshow' in url : + # url = None + # return url + def parse_index(self): + + articles = [] + + + soup = self.index_to_soup('http://www.guardian.co.uk/theguardian') + # find cover pic + img = soup.find( 'img',attrs ={'alt':'Guardian digital edition'}) + + if img is not None: + self.cover_url = img['src'] + + # end find cover pic + for li in soup.findAll( 'li'): + + if li.a and li.a.has_key('href'): + url = li.a['href'] + if 'mainsection' in url: + + + #find the articles in the Main Section + + soup = self.index_to_soup(url) + + for tag in soup.findAll('h3'): + for a in tag.findAll('a'): + + if a and a.has_key('href'): + + url2 = a['href'] + + else: + url2 ='' + + title = self.tag_to_string(a) + #eliminate duplicates + if len(articles) == 0: + desc = 'Main Section' + date = '' + articles.append({ + 'title':title, + 'date':date, + 'url':url2, + 'description':desc, + }) + else: + if len(articles) > 0: + if {'title':title,'date':date,'url':url2,'description':desc} in articles: + ulrl2 = '' + #eliminate duplicates + else: + + desc = 'Main Section' + date = '' + articles.append({ + 'title':title, + 'date':date, + 'url':url2, + 'description':desc, + }) + #find the articles in the Main Section + else: + url ='' + + + + + return [('Current Issue', articles)] def preprocess_html(self, soup):