mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
A much improved LRF layout engine. Implemented search functionality.
This commit is contained in:
parent
4da344abf5
commit
fbb3be46df
@ -14,7 +14,7 @@
|
|||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
''''''
|
''''''
|
||||||
|
|
||||||
import collections
|
import collections, itertools
|
||||||
|
|
||||||
from PyQt4.QtCore import Qt, QByteArray, SIGNAL
|
from PyQt4.QtCore import Qt, QByteArray, SIGNAL
|
||||||
from PyQt4.QtGui import QGraphicsRectItem, QGraphicsScene, QPen, \
|
from PyQt4.QtGui import QGraphicsRectItem, QGraphicsScene, QPen, \
|
||||||
@ -127,7 +127,7 @@ class _Canvas(QGraphicsRectItem):
|
|||||||
if isinstance(line, QGraphicsItem):
|
if isinstance(line, QGraphicsItem):
|
||||||
line.setParentItem(self)
|
line.setParentItem(self)
|
||||||
line.setPos(x + line.getx(textwidth), y)
|
line.setPos(x + line.getx(textwidth), y)
|
||||||
y += line.height
|
y += line.height + line.line_space
|
||||||
else:
|
else:
|
||||||
y += line.height
|
y += line.height
|
||||||
if not block.has_content:
|
if not block.has_content:
|
||||||
@ -165,6 +165,18 @@ class _Canvas(QGraphicsRectItem):
|
|||||||
self.is_full = y > self.max_y-5
|
self.is_full = y > self.max_y-5
|
||||||
ib.has_content = False
|
ib.has_content = False
|
||||||
|
|
||||||
|
def search(self, phrase):
|
||||||
|
matches = []
|
||||||
|
for child in self.children():
|
||||||
|
if hasattr(child, 'search'):
|
||||||
|
res = child.search(phrase)
|
||||||
|
if res:
|
||||||
|
if isinstance(res, list):
|
||||||
|
matches += res
|
||||||
|
else:
|
||||||
|
matches.append(res)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Canvas(_Canvas, ContentObject):
|
class Canvas(_Canvas, ContentObject):
|
||||||
@ -275,6 +287,7 @@ class Page(_Canvas):
|
|||||||
self.layout_block(block, 0, self.current_y)
|
self.layout_block(block, 0, self.current_y)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Chapter(object):
|
class Chapter(object):
|
||||||
|
|
||||||
num_of_pages = property(fget=lambda self: len(self.pages))
|
num_of_pages = property(fget=lambda self: len(self.pages))
|
||||||
@ -292,6 +305,15 @@ class Chapter(object):
|
|||||||
def screen(self, odd):
|
def screen(self, odd):
|
||||||
return self.oddscreen if odd else self.evenscreen
|
return self.oddscreen if odd else self.evenscreen
|
||||||
|
|
||||||
|
def search(self, phrase):
|
||||||
|
pages = []
|
||||||
|
for i in range(len(self.pages)):
|
||||||
|
matches = self.pages[i].search(phrase)
|
||||||
|
if matches:
|
||||||
|
pages.append([i, matches])
|
||||||
|
return pages
|
||||||
|
|
||||||
|
|
||||||
class History(collections.deque):
|
class History(collections.deque):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -331,6 +353,9 @@ class Document(QGraphicsScene):
|
|||||||
self.link_map = {}
|
self.link_map = {}
|
||||||
self.chapter_map = {}
|
self.chapter_map = {}
|
||||||
self.history = History()
|
self.history = History()
|
||||||
|
self.last_search = iter([])
|
||||||
|
if not opts.white_background:
|
||||||
|
self.setBackgroundBrush(QBrush(QColor(0xee, 0xee, 0xee)))
|
||||||
|
|
||||||
def page_of(self, oid):
|
def page_of(self, oid):
|
||||||
for chapter in self.chapters:
|
for chapter in self.chapters:
|
||||||
@ -492,3 +517,22 @@ class Document(QGraphicsScene):
|
|||||||
def show_page_at_percent(self, p):
|
def show_page_at_percent(self, p):
|
||||||
num = self.num_of_pages*(p/100.)
|
num = self.num_of_pages*(p/100.)
|
||||||
self.show_page(num)
|
self.show_page(num)
|
||||||
|
|
||||||
|
def search(self, phrase):
|
||||||
|
if not phrase:
|
||||||
|
return
|
||||||
|
matches = []
|
||||||
|
for i in range(len(self.chapters)):
|
||||||
|
cmatches = self.chapters[i].search(phrase)
|
||||||
|
for match in cmatches:
|
||||||
|
match[0] += sum(self.chapter_layout[:i])+1
|
||||||
|
matches += cmatches
|
||||||
|
self.last_search = itertools.cycle(matches)
|
||||||
|
self.next_match()
|
||||||
|
|
||||||
|
def next_match(self):
|
||||||
|
page_num = self.last_search.next()[0]
|
||||||
|
if self.current_page == page_num:
|
||||||
|
self.update()
|
||||||
|
self.show_page(page_num)
|
||||||
|
|
@ -22,7 +22,7 @@ from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
|||||||
from libprs500 import __appname__, __version__, __author__, setup_cli_handlers, islinux
|
from libprs500 import __appname__, __version__, __author__, setup_cli_handlers, islinux
|
||||||
from libprs500.ebooks.lrf.parser import LRFDocument
|
from libprs500.ebooks.lrf.parser import LRFDocument
|
||||||
|
|
||||||
from libprs500.gui2 import ORG_NAME, APP_UID
|
from libprs500.gui2 import ORG_NAME, APP_UID, error_dialog
|
||||||
from libprs500.gui2.dialogs.conversion_error import ConversionErrorDialog
|
from libprs500.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from libprs500.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
from libprs500.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
||||||
from libprs500.gui2.main_window import MainWindow
|
from libprs500.gui2.main_window import MainWindow
|
||||||
@ -65,6 +65,8 @@ class Main(QObject, Ui_MainWindow, MainWindow):
|
|||||||
|
|
||||||
self.search.help_text = 'Search'
|
self.search.help_text = 'Search'
|
||||||
self.search.clear_to_help()
|
self.search.clear_to_help()
|
||||||
|
QObject.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find)
|
||||||
|
self.last_search = None
|
||||||
|
|
||||||
self.action_next_page.setShortcuts(QKeySequence.MoveToNextPage)
|
self.action_next_page.setShortcuts(QKeySequence.MoveToNextPage)
|
||||||
self.action_previous_page.setShortcuts(QKeySequence.MoveToPreviousPage)
|
self.action_previous_page.setShortcuts(QKeySequence.MoveToPreviousPage)
|
||||||
@ -91,6 +93,12 @@ class Main(QObject, Ui_MainWindow, MainWindow):
|
|||||||
self.stack.setCurrentIndex(1)
|
self.stack.setCurrentIndex(1)
|
||||||
self.renderer.start()
|
self.renderer.start()
|
||||||
|
|
||||||
|
def find(self, search, refinement):
|
||||||
|
self.last_search = search
|
||||||
|
try:
|
||||||
|
self.document.search(search)
|
||||||
|
except StopIteration:
|
||||||
|
error_dialog(self.window, 'No matches found', '<b>No matches</b> for the search phrase <i>%s</i> were found.'%(search,)).exec_()
|
||||||
|
|
||||||
def parsed(self, *args):
|
def parsed(self, *args):
|
||||||
if self.renderer.lrf is not None:
|
if self.renderer.lrf is not None:
|
||||||
@ -100,13 +108,13 @@ class Main(QObject, Ui_MainWindow, MainWindow):
|
|||||||
self.document_title = self.renderer.lrf.metadata.title
|
self.document_title = self.renderer.lrf.metadata.title
|
||||||
if self.opts.profile:
|
if self.opts.profile:
|
||||||
import cProfile
|
import cProfile
|
||||||
render, lrf = self.document.render, self.renderer.lrf
|
lrf = self.renderer.lrf
|
||||||
cProfile.runctx('render(lrf)', globals(), locals(), lrf.metadata.title+'.stats')
|
cProfile.runctx('self.document.render(lrf)', globals(), locals(), lrf.metadata.title+'.stats')
|
||||||
print 'Stats written to', self.renderer.lrf.metadata.title+'.stats'
|
print 'Stats written to', self.renderer.lrf.metadata.title+'.stats'
|
||||||
else:
|
else:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.document.render(self.renderer.lrf)
|
self.document.render(self.renderer.lrf)
|
||||||
print 'Rendering time:', time.time()-start, 'seconds'
|
print 'Layout time:', time.time()-start, 'seconds'
|
||||||
self.renderer.lrf = None
|
self.renderer.lrf = None
|
||||||
self.graphics_view.setScene(self.document)
|
self.graphics_view.setScene(self.document)
|
||||||
self.graphics_view.show()
|
self.graphics_view.show()
|
||||||
@ -132,7 +140,7 @@ class Main(QObject, Ui_MainWindow, MainWindow):
|
|||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
self.progress_bar.setMaximum(num)
|
self.progress_bar.setMaximum(num)
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_label.setText('Rendering '+ self.document_title)
|
self.progress_label.setText('Laying out '+ self.document_title)
|
||||||
else:
|
else:
|
||||||
self.progress_bar.setValue(self.progress_bar.value()+1)
|
self.progress_bar.setValue(self.progress_bar.value()+1)
|
||||||
QCoreApplication.processEvents()
|
QCoreApplication.processEvents()
|
||||||
@ -171,6 +179,8 @@ def option_parser():
|
|||||||
default=False, action='store_true', dest='visual_debug')
|
default=False, action='store_true', dest='visual_debug')
|
||||||
parser.add_option('--disable-hyphenation', dest='hyphenate', default=True, action='store_false',
|
parser.add_option('--disable-hyphenation', dest='hyphenate', default=True, action='store_false',
|
||||||
help='Disable hyphenation. Should significantly speed up rendering.')
|
help='Disable hyphenation. Should significantly speed up rendering.')
|
||||||
|
parser.add_option('--white-background', dest='white_background', default=False, action='store_true',
|
||||||
|
help='By default the background is off white as I find this easier on the eyes. Use this option to make the background pure white.')
|
||||||
parser.add_option('--profile', dest='profile', default=False, action='store_true',
|
parser.add_option('--profile', dest='profile', default=False, action='store_true',
|
||||||
help='Profile the LRF renderer')
|
help='Profile the LRF renderer')
|
||||||
return parser
|
return parser
|
||||||
|
@ -317,23 +317,25 @@ class TextBlock(object):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
class Link(QGraphicsRectItem):
|
class Link(QGraphicsRectItem):
|
||||||
inactive_brush = QBrush(QColor(0x00, 0x00, 0x00, 0x09))
|
inactive_brush = QBrush(QColor(0xff, 0xff, 0xff, 0xff))
|
||||||
active_brush = QBrush(QColor(0x00, 0x00, 0x00, 0x59))
|
active_brush = QBrush(QColor(0x00, 0x00, 0x00, 0x59))
|
||||||
|
|
||||||
def __init__(self, parent, start, stop, refobj, slot):
|
def __init__(self, parent, start, stop, refobj, slot):
|
||||||
QGraphicsRectItem.__init__(self, start, 0, stop-start, parent.height, parent)
|
QGraphicsRectItem.__init__(self, start, 0, stop-start, parent.height, parent)
|
||||||
self.refobj = refobj
|
self.refobj = refobj
|
||||||
self.slot = slot
|
self.slot = slot
|
||||||
self.setBrush(self.__class__.inactive_brush)
|
self.brush = self.__class__.inactive_brush
|
||||||
self.setPen(QPen(Qt.NoPen))
|
self.setPen(QPen(Qt.NoPen))
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
self.setAcceptsHoverEvents(True)
|
self.setAcceptsHoverEvents(True)
|
||||||
|
|
||||||
def hoverEnterEvent(self, event):
|
def hoverEnterEvent(self, event):
|
||||||
self.setBrush(self.__class__.active_brush)
|
self.brush = self.__class__.active_brush
|
||||||
|
self.parentItem().update()
|
||||||
|
|
||||||
def hoverLeaveEvent(self, event):
|
def hoverLeaveEvent(self, event):
|
||||||
self.setBrush(self.__class__.inactive_brush)
|
self.brush = self.__class__.inactive_brush
|
||||||
|
self.parentItem().update()
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
self.hoverLeaveEvent(None)
|
self.hoverLeaveEvent(None)
|
||||||
@ -371,6 +373,7 @@ class Line(QGraphicsItem):
|
|||||||
self.tokens.append(plot)
|
self.tokens.append(plot)
|
||||||
self.current_width += plot.width
|
self.current_width += plot.width
|
||||||
self.height = max(self.height, plot.height)
|
self.height = max(self.height, plot.height)
|
||||||
|
self.add_space(6)
|
||||||
|
|
||||||
def populate(self, phrase, ts, process_space=True):
|
def populate(self, phrase, ts, process_space=True):
|
||||||
phrase_pos = 0
|
phrase_pos = 0
|
||||||
@ -446,7 +449,6 @@ class Line(QGraphicsItem):
|
|||||||
self.width = float(self.current_width)
|
self.width = float(self.current_width)
|
||||||
if self.height == 0:
|
if self.height == 0:
|
||||||
self.height = baselineskip
|
self.height = baselineskip
|
||||||
self.height += linespace
|
|
||||||
self.height = float(self.height)
|
self.height = float(self.height)
|
||||||
|
|
||||||
self.vdebug = vdebug
|
self.vdebug = vdebug
|
||||||
@ -470,12 +472,24 @@ class Line(QGraphicsItem):
|
|||||||
painter.drawRect(self.boundingRect())
|
painter.drawRect(self.boundingRect())
|
||||||
painter.restore()
|
painter.restore()
|
||||||
painter.save()
|
painter.save()
|
||||||
|
painter.setPen(QPen(Qt.NoPen))
|
||||||
|
for c in self.children():
|
||||||
|
painter.setBrush(c.brush)
|
||||||
|
painter.drawRect(c.boundingRect())
|
||||||
|
painter.restore()
|
||||||
|
painter.save()
|
||||||
for tok in self.tokens:
|
for tok in self.tokens:
|
||||||
if isinstance(tok, (int, float)):
|
if isinstance(tok, (int, float)):
|
||||||
x += tok
|
x += tok
|
||||||
elif isinstance(tok, Word):
|
elif isinstance(tok, Word):
|
||||||
painter.setFont(tok.font)
|
painter.setFont(tok.font)
|
||||||
p = painter.pen()
|
p = painter.pen()
|
||||||
|
if tok.highlight:
|
||||||
|
painter.save()
|
||||||
|
painter.setPen(QPen(Qt.NoPen))
|
||||||
|
painter.setBrush(QBrush(Qt.yellow))
|
||||||
|
painter.drawRect(x, 0, tok.width, tok.height)
|
||||||
|
painter.restore()
|
||||||
painter.setPen(QPen(tok.text_color))
|
painter.setPen(QPen(tok.text_color))
|
||||||
painter.drawText(x, y, tok.string)
|
painter.drawText(x, y, tok.string)
|
||||||
painter.setPen(p)
|
painter.setPen(p)
|
||||||
@ -485,6 +499,30 @@ class Line(QGraphicsItem):
|
|||||||
x += tok.width
|
x += tok.width
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
|
def search(self, phrase):
|
||||||
|
tokens = phrase.lower().split()
|
||||||
|
if len(tokens) < 1: return None
|
||||||
|
for i in range(len(self.tokens)):
|
||||||
|
if not isinstance(self.tokens[i], Word):
|
||||||
|
continue
|
||||||
|
src = qstring_to_unicode(self.tokens[i].string).lower()
|
||||||
|
self.tokens[i].highlight = False
|
||||||
|
if tokens[0] in src:
|
||||||
|
if len(tokens) == 1:
|
||||||
|
self.tokens[i].highlight = True
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
match = True
|
||||||
|
for j in range(len(tokens)):
|
||||||
|
if i+j >= len(self.tokens) or tokens[j].lower() != qstring_to_unicode(self.tokens[i+j].string).lower():
|
||||||
|
match = False
|
||||||
|
break
|
||||||
|
self.tokens[i+j].highlight = True
|
||||||
|
if match:
|
||||||
|
return self
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getx(self, textwidth):
|
def getx(self, textwidth):
|
||||||
if self.align == 'head':
|
if self.align == 'head':
|
||||||
return self.offset
|
return self.offset
|
||||||
@ -497,7 +535,7 @@ class Line(QGraphicsItem):
|
|||||||
s = u''
|
s = u''
|
||||||
for tok in self.tokens:
|
for tok in self.tokens:
|
||||||
if isinstance(tok, (int, float)):
|
if isinstance(tok, (int, float)):
|
||||||
s += ' :%.1f: '%(tok,)
|
s += ' '
|
||||||
elif isinstance(tok, Word):
|
elif isinstance(tok, Word):
|
||||||
s += qstring_to_unicode(tok.string)
|
s += qstring_to_unicode(tok.string)
|
||||||
return s
|
return s
|
||||||
@ -512,6 +550,7 @@ class Word(object):
|
|||||||
self.string, self.width, self.height = QString(string), width, height
|
self.string, self.width, self.height = QString(string), width, height
|
||||||
self.font = font
|
self.font = font
|
||||||
self.text_color = ts.textcolor
|
self.text_color = ts.textcolor
|
||||||
|
self.highlight = False
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
return 0
|
return 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user