mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Command line PDF conversion and bookmark manager for ebook-viewer
This commit is contained in:
commit
3aa901df7e
@ -195,5 +195,8 @@ class EbookIterator(object):
|
||||
self.bookmarks.append(bm)
|
||||
self.save_bookmarks()
|
||||
|
||||
def set_bookmarks(self, bookmarks):
|
||||
self.bookmarks = bookmarks
|
||||
|
||||
def __exit__(self, *args):
|
||||
self._tdir.__exit__(*args)
|
||||
self._tdir.__exit__(*args)
|
||||
|
@ -1,16 +1,37 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
'''Read meta information from PDF files'''
|
||||
|
||||
import sys, re
|
||||
from __future__ import with_statement
|
||||
|
||||
from calibre.ebooks.metadata import MetaInformation, authors_to_string
|
||||
from pyPdf import PdfFileReader
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
def get_metadata(stream):
|
||||
import sys, os, re, StringIO
|
||||
|
||||
from calibre.ebooks.metadata import MetaInformation, authors_to_string, get_parser
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from pyPdf import PdfFileReader, PdfFileWriter
|
||||
import Image
|
||||
try:
|
||||
from calibre.utils.PythonMagickWand import \
|
||||
NewMagickWand, MagickReadImage, MagickSetImageFormat, MagickWriteImage
|
||||
_imagemagick_loaded = True
|
||||
except:
|
||||
_imagemagick_loaded = False
|
||||
|
||||
def get_metadata(stream, extract_cover=True):
|
||||
""" Return metadata as a L{MetaInfo} object """
|
||||
mi = MetaInformation(_('Unknown'), [_('Unknown')])
|
||||
stream.seek(0)
|
||||
|
||||
if extract_cover and _imagemagick_loaded:
|
||||
try:
|
||||
cdata = get_cover(stream)
|
||||
if cdata is not None:
|
||||
mi.cover_data = ('jpg', cdata)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
try:
|
||||
info = PdfFileReader(stream).getDocumentInfo()
|
||||
if info.title:
|
||||
@ -45,3 +66,68 @@ def set_metadata(stream, mi):
|
||||
stream.write(raw)
|
||||
stream.seek(0)
|
||||
|
||||
def get_cover(stream):
|
||||
try:
|
||||
pdf = PdfFileReader(stream)
|
||||
output = PdfFileWriter()
|
||||
|
||||
if len(pdf.pages) >= 1:
|
||||
output.addPage(pdf.getPage(0))
|
||||
|
||||
with TemporaryDirectory('_pdfmeta') as tdir:
|
||||
cover_path = os.path.join(tdir, 'cover.pdf')
|
||||
|
||||
outputStream = file(cover_path, "wb")
|
||||
output.write(outputStream)
|
||||
outputStream.close()
|
||||
|
||||
wand = NewMagickWand()
|
||||
MagickReadImage(wand, cover_path)
|
||||
MagickSetImageFormat(wand, 'JPEG')
|
||||
MagickWriteImage(wand, '%s.jpg' % cover_path)
|
||||
|
||||
img = Image.open('%s.jpg' % cover_path)
|
||||
|
||||
data = StringIO.StringIO()
|
||||
img.save(data, 'JPEG')
|
||||
return data.getvalue()
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def option_parser():
|
||||
p = get_parser('pdf')
|
||||
p.remove_option('--category')
|
||||
p.remove_option('--comment')
|
||||
p.add_option('--get-cover', default=False, action='store_true',
|
||||
help=_('Extract the cover'))
|
||||
return p
|
||||
|
||||
def main(args=sys.argv):
|
||||
p = option_parser()
|
||||
opts, args = p.parse_args(args)
|
||||
|
||||
with open(os.path.abspath(os.path.expanduser(args[1])), 'r+b') as stream:
|
||||
mi = get_metadata(stream, extract_cover=opts.get_cover)
|
||||
changed = False
|
||||
if opts.title:
|
||||
mi.title = opts.title
|
||||
changed = True
|
||||
if opts.authors:
|
||||
mi.authors = opts.authors.split(',')
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
set_metadata(stream, mi)
|
||||
print unicode(get_metadata(stream, extract_cover=False)).encode('utf-8')
|
||||
|
||||
if mi.cover_data[1] is not None:
|
||||
cpath = os.path.splitext(os.path.basename(args[1]))[0] + '_cover.jpg'
|
||||
with open(cpath, 'wb') as f:
|
||||
f.write(mi.cover_data[1])
|
||||
print 'Cover saved to', f.name
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
69
src/calibre/ebooks/pdf/from_any.py
Normal file
69
src/calibre/ebooks/pdf/from_any.py
Normal file
@ -0,0 +1,69 @@
|
||||
'''
|
||||
Convert any ebook format to PDF.
|
||||
'''
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net ' \
|
||||
'and Marshall T. Vandegrift <llasram@gmail.com>' \
|
||||
'and John Schember <john@nachtimwald.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, os, glob, logging
|
||||
|
||||
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
||||
from calibre.ebooks.epub import config as common_config
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.ebooks.pdf.writer import oeb2pdf, config as pdf_config
|
||||
|
||||
def config(defaults=None):
|
||||
c = common_config(defaults=defaults, name='pdf')
|
||||
c.remove_opt('profile')
|
||||
pdfc = pdf_config(defaults=defaults)
|
||||
c.update(pdfc)
|
||||
return c
|
||||
|
||||
def option_parser(usage=USAGE):
|
||||
usage = usage % ('PDF', formats())
|
||||
parser = config().option_parser(usage=usage)
|
||||
return parser
|
||||
|
||||
def any2pdf(opts, path, notification=None):
|
||||
ext = os.path.splitext(path)[1]
|
||||
if not ext:
|
||||
raise ValueError('Unknown file type: '+path)
|
||||
ext = ext.lower()[1:]
|
||||
|
||||
if opts.output is None:
|
||||
opts.output = os.path.splitext(os.path.basename(path))[0]+'.pdf'
|
||||
|
||||
opts.output = os.path.abspath(opts.output)
|
||||
orig_output = opts.output
|
||||
|
||||
with TemporaryDirectory('_any2pdf') as tdir:
|
||||
oebdir = os.path.join(tdir, 'oeb')
|
||||
os.mkdir(oebdir)
|
||||
opts.output = os.path.join(tdir, 'dummy.epub')
|
||||
opts.profile = 'None'
|
||||
opts.dont_split_on_page_breaks = True
|
||||
orig_bfs = opts.base_font_size2
|
||||
opts.base_font_size2 = 0
|
||||
any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir)
|
||||
opts.base_font_size2 = orig_bfs
|
||||
opf = glob.glob(os.path.join(oebdir, '*.opf'))[0]
|
||||
opts.output = orig_output
|
||||
logging.getLogger('html2epub').info(_('Creating PDF file from EPUB...'))
|
||||
oeb2pdf(opts, opf)
|
||||
|
||||
def main(args=sys.argv):
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
if len(args) < 2:
|
||||
parser.print_help()
|
||||
print 'No input file specified.'
|
||||
return 1
|
||||
any2pdf(opts, args[1])
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
150
src/calibre/ebooks/pdf/writer.py
Normal file
150
src/calibre/ebooks/pdf/writer.py
Normal file
@ -0,0 +1,150 @@
|
||||
'''
|
||||
Write content to PDF.
|
||||
'''
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
|
||||
import os, logging, shutil, sys
|
||||
|
||||
from calibre import LoggingInterface
|
||||
from calibre.ebooks.epub.iterator import SpineItem
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
from calibre.customize.ui import run_plugins_on_postprocess
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, QPrinter, \
|
||||
QMetaObject, Qt
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from pyPdf import PdfFileWriter, PdfFileReader
|
||||
|
||||
class PDFWriter(QObject):
|
||||
def __init__(self):
|
||||
if QApplication.instance() is None:
|
||||
QApplication([])
|
||||
QObject.__init__(self)
|
||||
|
||||
self.logger = logging.getLogger('oeb2pdf')
|
||||
|
||||
self.loop = QEventLoop()
|
||||
self.view = QWebView()
|
||||
self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html)
|
||||
self.render_queue = []
|
||||
self.combine_queue = []
|
||||
self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts')
|
||||
|
||||
def dump(self, oebpath, path):
|
||||
self._delete_tmpdir()
|
||||
|
||||
opf = OPF(oebpath, os.path.dirname(oebpath))
|
||||
self.render_queue = [SpineItem(i.path) for i in opf.spine]
|
||||
self.combine_queue = []
|
||||
self.path = path
|
||||
|
||||
QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection)
|
||||
self.loop.exec_()
|
||||
|
||||
@QtCore.pyqtSignature('_render_book()')
|
||||
def _render_book(self):
|
||||
if len(self.render_queue) == 0:
|
||||
self._write()
|
||||
else:
|
||||
self._render_next()
|
||||
|
||||
def _render_next(self):
|
||||
item = str(self.render_queue.pop(0))
|
||||
self.combine_queue.append(os.path.join(self.tmp_path, '%s_%i.pdf' % (os.path.basename(item), len(self.combine_queue))))
|
||||
|
||||
self.logger.info('Processing %s...' % item)
|
||||
|
||||
self.view.load(QUrl(item))
|
||||
|
||||
def _render_html(self, ok):
|
||||
if ok:
|
||||
item_path = os.path.join(self.tmp_path, '%s_%i.pdf' % (os.path.basename(str(self.view.url().toLocalFile())), len(self.combine_queue) - 1))
|
||||
|
||||
self.logger.debug('\tRendering item as %s' % item_path)
|
||||
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
|
||||
printer.setOutputFormat(QPrinter.PdfFormat)
|
||||
printer.setOutputFileName(item_path)
|
||||
self.view.print_(printer)
|
||||
self._render_book()
|
||||
|
||||
def _delete_tmpdir(self):
|
||||
if os.path.exists(self.tmp_path):
|
||||
shutil.rmtree(self.tmp_path, True)
|
||||
self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts')
|
||||
|
||||
def _write(self):
|
||||
self.logger.info('Combining individual PDF parts...')
|
||||
|
||||
try:
|
||||
outPDF = PdfFileWriter()
|
||||
for item in self.combine_queue:
|
||||
inputPDF = PdfFileReader(file(item, 'rb'))
|
||||
for page in inputPDF.pages:
|
||||
outPDF.addPage(page)
|
||||
outputStream = file(self.path, 'wb')
|
||||
outPDF.write(outputStream)
|
||||
outputStream.close()
|
||||
finally:
|
||||
self._delete_tmpdir()
|
||||
self.loop.exit(0)
|
||||
|
||||
|
||||
def config(defaults=None):
|
||||
desc = _('Options to control the conversion to PDF')
|
||||
if defaults is None:
|
||||
c = Config('pdf', desc)
|
||||
else:
|
||||
c = StringConfig(defaults, desc)
|
||||
|
||||
pdf = c.add_group('PDF', _('PDF options.'))
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def option_parser():
|
||||
c = config()
|
||||
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
|
||||
parser.add_option(
|
||||
'-o', '--output', default=None,
|
||||
help=_('Output file. Default is derived from input filename.'))
|
||||
parser.add_option(
|
||||
'-v', '--verbose', default=0, action='count',
|
||||
help=_('Useful for debugging.'))
|
||||
return parser
|
||||
|
||||
def oeb2pdf(opts, inpath):
|
||||
logger = LoggingInterface(logging.getLogger('oeb2pdf'))
|
||||
logger.setup_cli_handler(opts.verbose)
|
||||
|
||||
outpath = opts.output
|
||||
if outpath is None:
|
||||
outpath = os.path.basename(inpath)
|
||||
outpath = os.path.splitext(outpath)[0] + '.pdf'
|
||||
|
||||
writer = PDFWriter()
|
||||
writer.dump(inpath, outpath)
|
||||
run_plugins_on_postprocess(outpath, 'pdf')
|
||||
logger.log_info(_('Output written to ') + outpath)
|
||||
|
||||
def main(argv=sys.argv):
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(argv[1:])
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
return 1
|
||||
inpath = args[0]
|
||||
retval = oeb2pdf(opts, inpath)
|
||||
return retval
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
14298
src/calibre/gui2/images/print-preview.svg
Normal file
14298
src/calibre/gui2/images/print-preview.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 354 KiB |
14229
src/calibre/gui2/images/print.svg
Normal file
14229
src/calibre/gui2/images/print.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 350 KiB |
93
src/calibre/gui2/viewer/bookmarkmanager.py
Normal file
93
src/calibre/gui2/viewer/bookmarkmanager.py
Normal file
@ -0,0 +1,93 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
|
||||
|
||||
from PyQt4.Qt import Qt, QDialog, QAbstractTableModel, QVariant, SIGNAL, \
|
||||
QModelIndex, QInputDialog, QLineEdit
|
||||
|
||||
from calibre.gui2.viewer.bookmarkmanager_ui import Ui_BookmarkManager
|
||||
from calibre.gui2 import NONE, qstring_to_unicode
|
||||
|
||||
class BookmarkManager(QDialog, Ui_BookmarkManager):
|
||||
def __init__(self, parent, bookmarks):
|
||||
QDialog.__init__(self, parent)
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self.bookmarks = bookmarks[:]
|
||||
self.set_bookmarks()
|
||||
|
||||
self.connect(self.button_revert, SIGNAL('clicked()'), self.set_bookmarks)
|
||||
self.connect(self.button_delete, SIGNAL('clicked()'), self.delete_bookmark)
|
||||
self.connect(self.button_edit, SIGNAL('clicked()'), self.edit_bookmark)
|
||||
|
||||
def set_bookmarks(self):
|
||||
self._model = BookmarkTableModel(self, self.bookmarks)
|
||||
self.bookmarks_table.setModel(self._model)
|
||||
|
||||
def delete_bookmark(self):
|
||||
indexes = self.bookmarks_table.selectionModel().selectedIndexes()
|
||||
if indexes != []:
|
||||
self._model.remove_row(indexes[0].row())
|
||||
|
||||
def edit_bookmark(self):
|
||||
indexes = self.bookmarks_table.selectionModel().selectedIndexes()
|
||||
if indexes != []:
|
||||
title, ok = QInputDialog.getText(self, _('Edit bookmark'), _('New title for bookmark:'), QLineEdit.Normal, self._model.data(indexes[0], Qt.DisplayRole).toString())
|
||||
title = QVariant(unicode(title).strip())
|
||||
if ok and title:
|
||||
self._model.setData(indexes[0], title, Qt.EditRole)
|
||||
|
||||
def get_bookmarks(self):
|
||||
return self._model.bookmarks
|
||||
|
||||
|
||||
class BookmarkTableModel(QAbstractTableModel):
|
||||
headers = [_("Name")]
|
||||
|
||||
def __init__(self, parent, bookmarks):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
|
||||
self.bookmarks = bookmarks[:]
|
||||
|
||||
def rowCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
return 0
|
||||
return len(self.bookmarks)
|
||||
|
||||
def columnCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
return 0
|
||||
return len(self.headers)
|
||||
|
||||
def data(self, index, role):
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
ans = self.bookmarks[index.row()][0]
|
||||
return NONE if ans is None else QVariant(ans)
|
||||
return NONE
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if role == Qt.EditRole:
|
||||
self.bookmarks[index.row()] = (qstring_to_unicode(value.toString()).strip(), self.bookmarks[index.row()][1])
|
||||
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
|
||||
return True
|
||||
return False
|
||||
|
||||
def flags(self, index):
|
||||
flags = QAbstractTableModel.flags(self, index)
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role != Qt.DisplayRole:
|
||||
return NONE
|
||||
if orientation == Qt.Horizontal:
|
||||
return QVariant(self.headers[section])
|
||||
else:
|
||||
return QVariant(section+1)
|
||||
|
||||
def remove_row(self, row):
|
||||
self.beginRemoveRows(QModelIndex(), row, row)
|
||||
del self.bookmarks[row]
|
||||
self.endRemoveRows()
|
||||
|
57
src/calibre/gui2/viewer/bookmarkmanager.ui
Normal file
57
src/calibre/gui2/viewer/bookmarkmanager.ui
Normal file
@ -0,0 +1,57 @@
|
||||
<ui version="4.0" >
|
||||
<class>BookmarkManager</class>
|
||||
<widget class="QDialog" name="BookmarkManager" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="0" column="0" colspan="3" >
|
||||
<widget class="QTableView" name="bookmarks_table" >
|
||||
<property name="showDropIndicator" stdset="0" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QPushButton" name="button_revert" >
|
||||
<property name="text" >
|
||||
<string>Revert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QPushButton" name="button_delete" >
|
||||
<property name="text" >
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" >
|
||||
<widget class="QPushButton" name="button_edit" >
|
||||
<property name="text" >
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -12,6 +12,8 @@ from PyQt4.Qt import QMovie, QApplication, Qt, QIcon, QTimer, QWidget, SIGNAL, \
|
||||
QToolButton, QMenu, QInputDialog, QAction
|
||||
|
||||
from calibre.gui2.viewer.main_ui import Ui_EbookViewer
|
||||
from calibre.gui2.viewer.printing import Printing
|
||||
from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
|
||||
from calibre.gui2.main_window import MainWindow
|
||||
from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
||||
info_dialog, error_dialog
|
||||
@ -262,7 +264,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.connect(self.toc, SIGNAL('clicked(QModelIndex)'), self.toc_clicked)
|
||||
self.connect(self.reference, SIGNAL('goto(PyQt_PyObject)'), self.goto)
|
||||
|
||||
|
||||
self.bookmarks_menu = QMenu()
|
||||
self.action_bookmark.setMenu(self.bookmarks_menu)
|
||||
self.set_bookmarks([])
|
||||
|
||||
if pathtoebook is not None:
|
||||
f = functools.partial(self.load_ebook, pathtoebook)
|
||||
QTimer.singleShot(50, f)
|
||||
@ -273,6 +279,16 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.action_full_screen.setCheckable(True)
|
||||
|
||||
self.print_menu = QMenu()
|
||||
self.print_menu.addAction(QIcon(':/images/print-preview.svg'), _('Print Preview'))
|
||||
self.action_print.setMenu(self.print_menu)
|
||||
self.tool_bar.widgetForAction(self.action_print).setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.connect(self.action_print, SIGNAL("triggered(bool)"), partial(self.print_book, preview=False))
|
||||
self.connect(self.print_menu.actions()[0], SIGNAL("triggered(bool)"), partial(self.print_book, preview=True))
|
||||
|
||||
def print_book(self, preview):
|
||||
Printing(self.iterator.spine, preview)
|
||||
|
||||
def toggle_fullscreen(self, x):
|
||||
if self.isFullScreen():
|
||||
@ -477,17 +493,28 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.setCursor(Qt.BusyCursor)
|
||||
|
||||
def set_bookmarks(self, bookmarks):
|
||||
menu = QMenu()
|
||||
self.bookmarks_menu.clear()
|
||||
self.bookmarks_menu.addAction(_("Manage Bookmarks"), self.manage_bookmarks)
|
||||
self.bookmarks_menu.addSeparator()
|
||||
current_page = None
|
||||
for bm in bookmarks:
|
||||
if bm[0] == 'calibre_current_page_bookmark':
|
||||
current_page = bm
|
||||
else:
|
||||
menu.addAction(bm[0], partial(self.goto_bookmark, bm))
|
||||
self.action_bookmark.setMenu(menu)
|
||||
self._menu = menu
|
||||
self.bookmarks_menu.addAction(bm[0], partial(self.goto_bookmark, bm))
|
||||
return current_page
|
||||
|
||||
def manage_bookmarks(self):
|
||||
bmm = BookmarkManager(self, self.iterator.bookmarks)
|
||||
bmm.exec_()
|
||||
|
||||
bookmarks = bmm.get_bookmarks()
|
||||
|
||||
if bookmarks != self.iterator.bookmarks:
|
||||
self.iterator.set_bookmarks(bookmarks)
|
||||
self.iterator.save_bookmarks()
|
||||
self.set_bookmarks(bookmarks)
|
||||
|
||||
def save_current_position(self):
|
||||
try:
|
||||
pos = self.view.bookmark()
|
||||
|
@ -27,8 +27,8 @@
|
||||
<widget class="QWidget" name="layoutWidget" >
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QWebView" native="1" name="view" >
|
||||
<property name="url" stdset="0" >
|
||||
<widget class="QWebView" name="view" >
|
||||
<property name="url" >
|
||||
<url>
|
||||
<string>about:blank</string>
|
||||
</url>
|
||||
@ -89,6 +89,8 @@
|
||||
<addaction name="separator" />
|
||||
<addaction name="action_preferences" />
|
||||
<addaction name="action_full_screen" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="action_print" />
|
||||
</widget>
|
||||
<widget class="QToolBar" name="tool_bar2" >
|
||||
<attribute name="toolBarArea" >
|
||||
@ -234,6 +236,15 @@
|
||||
<string>Toggle full screen</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_print" >
|
||||
<property name="icon" >
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/print.svg</normaloff>:/images/print.svg</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Print</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
129
src/calibre/gui2/viewer/printing.py
Normal file
129
src/calibre/gui2/viewer/printing.py
Normal file
@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
|
||||
|
||||
import os, sys, traceback, urlparse
|
||||
|
||||
from BeautifulSoup import BeautifulSoup, Tag
|
||||
|
||||
from calibre.ebooks.epub.iterator import EbookIterator
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, Qt, \
|
||||
QPrinter, QPrintPreviewDialog, QPrintDialog, QDialog, QMetaObject, Q_ARG
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
PRINTCSS = 'body{width:100%;margin:0;padding:0;font-family:Arial;color:#000;background:none;font-size:12pt;text-align:left;}h1,h2,h3,h4,h5,h6{font-family:Helvetica;}h1{font-size:19pt;}h2{font-size:17pt;}h3{font-size:15pt;}h4,h5,h6{font-size:12pt;}pre,code,samp{font:10ptCourier,monospace;white-space:pre-wrap;page-break-inside:avoid;}blockquote{margin:1.3em;padding:1em;font-size:10pt;}hr{background-color:#ccc;}aimg{border:none;}a:link,a:visited{background:transparent;font-weight:700;text-decoration:underline;color:#333;}a:link:after,a{color:#000;}table{margin:1px;text-align:left;}th{border-bottom:1pxsolid#333;font-weight:bold;}td{border-bottom:1pxsolid#333;}th,td{padding:4px10px4px0;}tfoot{font-style:italic;}caption{background:#fff;margin-bottom:2em;text-align:left;}thead{display:table-header-group;}tr{page-break-inside:avoid;}#header,.header,#footer,.footer,#navbar,.navbar,#navigation,.navigation,#rightSideBar,.rightSideBar,#leftSideBar,.leftSideBar{display:none;}'
|
||||
|
||||
class Printing(QObject):
|
||||
def __init__(self, spine, preview):
|
||||
if QApplication.instance() is None:
|
||||
QApplication([])
|
||||
QObject.__init__(self)
|
||||
self.loop = QEventLoop()
|
||||
|
||||
self.view = QWebView()
|
||||
if preview:
|
||||
self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_preview)
|
||||
else:
|
||||
self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_book)
|
||||
|
||||
self.process_content(spine)
|
||||
|
||||
def process_content(self, spine):
|
||||
content = ''
|
||||
|
||||
for path in spine:
|
||||
raw = self.raw_content(path)
|
||||
content += self.parsed_content(raw, path)
|
||||
|
||||
refined_content = self.refine_content(content)
|
||||
|
||||
base = os.path.splitdrive(spine[0])[0]
|
||||
base = base if base != '' else '/'
|
||||
|
||||
QMetaObject.invokeMethod(self, "load_content", Qt.QueuedConnection, Q_ARG('QString', refined_content), Q_ARG('QString', base))
|
||||
self.loop.exec_()
|
||||
|
||||
@QtCore.pyqtSignature('load_content(QString, QString)')
|
||||
def load_content(self, content, base):
|
||||
self.view.setHtml(content, QUrl(base))
|
||||
|
||||
def raw_content(self, path):
|
||||
return open(path, 'rb').read().decode(path.encoding)
|
||||
|
||||
def parsed_content(self, raw_content, path):
|
||||
dom_tree = BeautifulSoup(raw_content).body
|
||||
|
||||
# Remove sytle information that is applied to the entire document.
|
||||
# This does not remove styles applied within a tag.
|
||||
styles = dom_tree.findAll('style')
|
||||
for s in styles:
|
||||
s.extract()
|
||||
|
||||
scripts = dom_tree.findAll('script')
|
||||
for s in scripts:
|
||||
s.extract()
|
||||
|
||||
# Convert all relative links to absolute paths.
|
||||
links = dom_tree.findAll(src=True)
|
||||
for s in links:
|
||||
if QUrl(s['src']).isRelative():
|
||||
s['src'] = urlparse.urljoin(path, s['src'])
|
||||
links = dom_tree.findAll(href=True)
|
||||
for s in links:
|
||||
if QUrl(s['href']).isRelative():
|
||||
s['href'] = urlparse.urljoin(path, s['href'])
|
||||
|
||||
return unicode(dom_tree)
|
||||
|
||||
# Adds the begenning and endings tags to the document.
|
||||
# Adds the print css.
|
||||
def refine_content(self, content):
|
||||
dom_tree = BeautifulSoup('<html><head></head><body>%s</body></html>' % content)
|
||||
|
||||
css = dom_tree.findAll('link')
|
||||
for c in css:
|
||||
c.extract()
|
||||
|
||||
print_css = Tag(BeautifulSoup(), 'style', [('type', 'text/css'), ('title', 'override_css')])
|
||||
print_css.insert(0, PRINTCSS)
|
||||
dom_tree.findAll('head')[0].insert(0, print_css)
|
||||
|
||||
return unicode(dom_tree)
|
||||
|
||||
def print_preview(self, ok):
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
|
||||
|
||||
previewDialog = QPrintPreviewDialog(printer)
|
||||
|
||||
self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_)
|
||||
previewDialog.exec_()
|
||||
self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
def print_book(self, ok):
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
|
||||
|
||||
printDialog = QPrintDialog(printer)
|
||||
printDialog.setWindowTitle(_("Print eBook"))
|
||||
|
||||
printDialog.exec_()
|
||||
if printDialog.result() == QDialog.Accepted:
|
||||
self.view.print_(printer)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
def main():
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
@ -60,6 +60,7 @@ entry_points = {
|
||||
'calibre-parallel = calibre.parallel:main',
|
||||
'calibre-customize = calibre.customize.ui:main',
|
||||
'pdftrim = calibre.ebooks.pdf.pdftrim:main' ,
|
||||
'any2pdf = calibre.ebooks.pdf.from_any:main',
|
||||
],
|
||||
'gui_scripts' : [
|
||||
__appname__+' = calibre.gui2.main:main',
|
||||
|
@ -71,6 +71,9 @@ PARALLEL_FUNCS = {
|
||||
'any2mobi' :
|
||||
('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None),
|
||||
|
||||
'any2pdf' :
|
||||
('calibre.ebooks.pdf.from_any', 'any2pdf', {}, None),
|
||||
|
||||
'feeds2mobi' :
|
||||
('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user