A much improved LRF layout engine. Implemented search functionality.

This commit is contained in:
Kovid Goyal 2007-09-24 03:16:29 +00:00
parent 4da344abf5
commit fbb3be46df
3 changed files with 106 additions and 13 deletions

View File

@ -14,7 +14,7 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
''''''
import collections
import collections, itertools
from PyQt4.QtCore import Qt, QByteArray, SIGNAL
from PyQt4.QtGui import QGraphicsRectItem, QGraphicsScene, QPen, \
@ -127,7 +127,7 @@ class _Canvas(QGraphicsRectItem):
if isinstance(line, QGraphicsItem):
line.setParentItem(self)
line.setPos(x + line.getx(textwidth), y)
y += line.height
y += line.height + line.line_space
else:
y += line.height
if not block.has_content:
@ -165,6 +165,18 @@ class _Canvas(QGraphicsRectItem):
self.is_full = y > self.max_y-5
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):
@ -275,6 +287,7 @@ class Page(_Canvas):
self.layout_block(block, 0, self.current_y)
class Chapter(object):
num_of_pages = property(fget=lambda self: len(self.pages))
@ -292,6 +305,15 @@ class Chapter(object):
def screen(self, odd):
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):
def __init__(self):
@ -331,6 +353,9 @@ class Document(QGraphicsScene):
self.link_map = {}
self.chapter_map = {}
self.history = History()
self.last_search = iter([])
if not opts.white_background:
self.setBackgroundBrush(QBrush(QColor(0xee, 0xee, 0xee)))
def page_of(self, oid):
for chapter in self.chapters:
@ -492,3 +517,22 @@ class Document(QGraphicsScene):
def show_page_at_percent(self, p):
num = self.num_of_pages*(p/100.)
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)

View File

@ -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.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.lrf_renderer.main_ui import Ui_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.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_previous_page.setShortcuts(QKeySequence.MoveToPreviousPage)
@ -91,6 +93,12 @@ class Main(QObject, Ui_MainWindow, MainWindow):
self.stack.setCurrentIndex(1)
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):
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
if self.opts.profile:
import cProfile
render, lrf = self.document.render, self.renderer.lrf
cProfile.runctx('render(lrf)', globals(), locals(), lrf.metadata.title+'.stats')
lrf = self.renderer.lrf
cProfile.runctx('self.document.render(lrf)', globals(), locals(), lrf.metadata.title+'.stats')
print 'Stats written to', self.renderer.lrf.metadata.title+'.stats'
else:
start = time.time()
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.graphics_view.setScene(self.document)
self.graphics_view.show()
@ -132,7 +140,7 @@ class Main(QObject, Ui_MainWindow, MainWindow):
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(num)
self.progress_bar.setValue(0)
self.progress_label.setText('Rendering '+ self.document_title)
self.progress_label.setText('Laying out '+ self.document_title)
else:
self.progress_bar.setValue(self.progress_bar.value()+1)
QCoreApplication.processEvents()
@ -171,6 +179,8 @@ def option_parser():
default=False, action='store_true', dest='visual_debug')
parser.add_option('--disable-hyphenation', dest='hyphenate', default=True, action='store_false',
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',
help='Profile the LRF renderer')
return parser

View File

@ -317,23 +317,25 @@ class TextBlock(object):
return s
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))
def __init__(self, parent, start, stop, refobj, slot):
QGraphicsRectItem.__init__(self, start, 0, stop-start, parent.height, parent)
self.refobj = refobj
self.slot = slot
self.setBrush(self.__class__.inactive_brush)
self.brush = self.__class__.inactive_brush
self.setPen(QPen(Qt.NoPen))
self.setCursor(Qt.PointingHandCursor)
self.setAcceptsHoverEvents(True)
def hoverEnterEvent(self, event):
self.setBrush(self.__class__.active_brush)
self.brush = self.__class__.active_brush
self.parentItem().update()
def hoverLeaveEvent(self, event):
self.setBrush(self.__class__.inactive_brush)
self.brush = self.__class__.inactive_brush
self.parentItem().update()
def mousePressEvent(self, event):
self.hoverLeaveEvent(None)
@ -371,6 +373,7 @@ class Line(QGraphicsItem):
self.tokens.append(plot)
self.current_width += plot.width
self.height = max(self.height, plot.height)
self.add_space(6)
def populate(self, phrase, ts, process_space=True):
phrase_pos = 0
@ -446,7 +449,6 @@ class Line(QGraphicsItem):
self.width = float(self.current_width)
if self.height == 0:
self.height = baselineskip
self.height += linespace
self.height = float(self.height)
self.vdebug = vdebug
@ -470,12 +472,24 @@ class Line(QGraphicsItem):
painter.drawRect(self.boundingRect())
painter.restore()
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:
if isinstance(tok, (int, float)):
x += tok
elif isinstance(tok, Word):
painter.setFont(tok.font)
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.drawText(x, y, tok.string)
painter.setPen(p)
@ -485,6 +499,30 @@ class Line(QGraphicsItem):
x += tok.width
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):
if self.align == 'head':
return self.offset
@ -497,7 +535,7 @@ class Line(QGraphicsItem):
s = u''
for tok in self.tokens:
if isinstance(tok, (int, float)):
s += ' :%.1f: '%(tok,)
s += ' '
elif isinstance(tok, Word):
s += qstring_to_unicode(tok.string)
return s
@ -512,6 +550,7 @@ class Word(object):
self.string, self.width, self.height = QString(string), width, height
self.font = font
self.text_color = ts.textcolor
self.highlight = False
def main(args=sys.argv):
return 0