#!/usr/bin/env python # -*- mode: python; coding: koi8-r -*- # from __future__ imports must occur at the beginning of the file from __future__ import generators import string, sys, os, time from cStringIO import StringIO import gobject, gtk, pango from gtk import gdk from detectcharset import detectcharset from textparagraph import TextParagraph from hyphenation import Hyphenation from miscutils import error_dialog, fix_filename from properties import styles_list import config skins_dir = 'skins' PANGO_SCALE = pango.SCALE #PANGO_SCALE = 1024 default_lang = 'ru' def create_rgb_buffer(width, height, color_1, color_2=None): r1, g1, b1 = int(color_1[1:3], 16), int(color_1[3:5], 16), int(color_1[5:7], 16) if color_2: # gradient buff=[] r2, g2, b2 = int(color_2[1:3], 16), int(color_2[3:5], 16), int(color_2[5:7], 16) for i in range(height): r=int(r1-(r1-r2)*i/float(height-1)) g=int(g1-(g1-g2)*i/float(height-1)) b=int(b1-(b1-b2)*i/float(height-1)) buff.append((chr(r)+chr(g)+chr(b))*width) buff = ''.join(buff) else: buff = (chr(r1)+chr(g1)+chr(b1))*(width*height) return buff ## cache_dir = os.path.join(config.config_dir, 'cache') ## import md5 ## import shelve class Content: def __init__(self, filename=None, encoding=None, lang=None): self._content = [] self._content_len = 0 self.filename = filename self.encoding = encoding self.lang = lang self.book_title = 'Unknown' self.authors = 'Unknown' self.titles_list = [] ## self.shelf = None ## m = md5.new(os.path.abspath(filename)).hexdigest() ## self.shelf_filename = os.path.join(cache_dir, m) ## if os.path.exists(self.shelf_filename): ## self.shelf = shelve.open(self.shelf_filename) ## #print self.shelf['0'] ## self._content_len = self.shelf['len'] ## else: ## self.shelf = shelve.open(self.shelf_filename) if filename: self.open_file() self._content_len = len(self._content) ## #self.shelf['len'] = self._content_len ## self.shelf.sync() ## del self._content ## def __del__(self): ## print '__del__' ## #self.shelf.close() def open_file(self): #try: data = open(self.filename).read() #except IOError, err: # error_dialog(self._parent, "Can't open file %s:\n%s" % # (fix_filename(filename), str(err))) # return False #try: while True: if data.startswith('\x1f\x8b'): import gzip data = gzip.GzipFile(fileobj=StringIO(data)).read() continue if data.startswith('BZh'): import bz2 data = bz2.decompress(data) continue if data.startswith('PK\x03\x04'): import zipfile file = zipfile.ZipFile(StringIO(data)) for name in file.namelist(): if name.lower() not in ['file_id.diz', 'comment.txt']: data = file.read(name) break continue break #except Exception, err: # error_dialog(self._parent, "Can't decompress file %s:\n%s" % # (fix_filename(filename), str(err))) ret = False if data.startswith('>>>>', key.start, key.stop ## ret = [] ## for i in xrange(key.start, key.stop): ## try: ## ret.append(self.shelf[str(i)]) ## except KeyError: ## break ## #yield self.shelf[str(i)] return self._content[key.start:key.stop] #return ret else: #yield self.shelf[str(key)] #return self.shelf[str(key)] return self._content[key] ## def __getslice__(self, i, j): ## print '__getslice__', i, j ## return self._content[i:j] def __len__(self): return self._content_len #print '__len__', len(self._content) #return len(self._content) class OrnamentBook(gtk.DrawingArea): keysyms_next = (gtk.keysyms.KP_Page_Down, gtk.keysyms.Page_Down, gtk.keysyms.space, gtk.keysyms.Right) keysyms_prev = (gtk.keysyms.KP_Page_Up, gtk.keysyms.Page_Up, gtk.keysyms.Left) def __init__(self, main_win): default_background = 'golden.jpg' background_file = None for d in sys.path: f = os.path.join(d, 'ornamentbook', skins_dir, default_background) if os.path.exists(f): background_file = f break if not background_file: sys.exit('ERROR: can\'t find default background file %s' % default_background) self.background_file = background_file self.default_background_file = background_file gtk.DrawingArea.__init__(self) # Hyphenation self.hyph = Hyphenation() #self.lang = default_lang self._parent = main_win.window #self.gc = None #self.area = gtk.DrawingArea() ## self.set_size_request(950, 400) ## self._width = 950 ## self._height = 400 gobject.signal_new('position-changed', OrnamentBook, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)) self.set_size_request(600, 400) self._width = 600 self._height = 400 #self.set_events(gdk.ALL_EVENTS_MASK) #self.add_events(gdk.BUTTON_PRESS_MASK|gdk.BUTTON_RELEASE_MASK) #self.add_events(gdk.BUTTON_RELEASE_MASK) self.add_events(gdk.BUTTON_PRESS_MASK) #self.add_events(gdk.BUTTON_PRESS_MASK|gdk.BUTTON_RELEASE_MASK|gdk.KEY_PRESS_MASK|gdk.KEY_RELEASE_MASK) #self.set_events(gdk.ALL_EVENTS_MASK) self.connect('expose-event', self.area_expose_cb) #self.connect('button_release_event', self.button_event_cb) self.connect('button_press_event', self.button_event_cb) #self.realize() self._parent.connect('key_press_event', self.key_event_cb) #self._parent.connect('key-release-event', self.key_event_cb) self.properties = main_win.properties self.content_menu = main_win.content_menu self.current_page_line_index = 0 self.current_page_char_index = 0 self.next_page_line_index = 0 self.next_page_char_index = 0 self.pixmap_area = None #self.prev_pixmap_area = None self.next_pixmap_area = None #self.content = [] # ??? self.content = Content() self.content.text_length = 0 self.footnotes_list = [] self.prev_index_list = [] return def set_background(self, filename): try: open(filename) except IOError, err: sys.stderr.write('ERROR: can\'t open file: %s: %s\n' % (filename, str(err))) return self.background_file = filename self.pixbuf = gdk.pixbuf_new_from_file(self.background_file) self.cur_pixbuf = self.pixbuf.scale_simple(self._width, self._height, gdk.INTERP_BILINEAR) self.properties.use_background = True def initialize(self): for st in styles_list: self.properties.styles[st].set_font_desc(self) style = self.get_style() self.gc = self.window.new_gc() self.gc.set_colormap(self.style.fg_gc[gtk.STATE_NORMAL].get_colormap()) self.gc.set_line_attributes( self.properties.footnote_line_width, gdk.LINE_SOLID, gdk.CAP_NOT_LAST, gdk.JOIN_MITER) self.pixbuf = None if self.properties.use_background: # and self.properties.path_to_background: # FIXME if not self.properties.path_to_background: self.properties.path_to_background = self.default_background_file try: self.pixbuf = gdk.pixbuf_new_from_file( self.properties.path_to_background) except Exception, err: print >> sys.stderr, 'WARNING:', str(err) x, y, widget_width, widget_height, depth = self.window.get_geometry() if self.pixbuf: self.cur_pixbuf = self.pixbuf.scale_simple( self._width, self._height, gdk.INTERP_BILINEAR) else: self.create_background_pixmap(self._width, self._height, depth) pango_font_desc = pango.FontDescription(self.properties.status_font) pangolayout = self.create_pango_layout(' ') pangolayout.set_font_description(pango_font_desc) font_height = pangolayout.get_extents()[1][3]/PANGO_SCALE self.properties.status_font_height = font_height self.properties.status_pango_font_desc = pango_font_desc self.status_pixmap_area = gdk.Pixmap( None, self._width, font_height+self.properties.status_y_position, depth) def create_background_pixmap(self, width, height, depth): if self.properties.use_gradient: buff = create_rgb_buffer(width, height, self.properties.background_color, self.properties.gradient_color) else: buff = create_rgb_buffer(width, height, self.properties.background_color) self.background_pixmap = gdk.Pixmap(None, width, height, depth) self.background_pixmap.draw_rgb_image( self.gc, 0, 0, width, height, gdk.RGB_DITHER_NONE, buff, width*3) def incr_font(self, incr): # if incr > 0 -> increase, else -> decrease for st in styles_list: if self.properties.styles[st].__dict__.has_key('font_size'): self.properties.styles[st].font_size += incr > 0 and 1 or -1 self.properties.styles[st].set_font_desc(self) def open_file(self, filename, encoding=None, line=0, index=0, lang=None): #print 'open_file', filename self.current_page_line_index = line self.current_page_char_index = index self.content = Content(filename, encoding, lang) self.draw_text() menu = gtk.Menu() menu.show() for title, title_index in self.content.titles_list: menuitem = gtk.MenuItem(title) menuitem.connect('activate', self.content_menu_activate_cb, title_index) menu.add(menuitem) menuitem.show() self.content_menu.set_submenu(menu) return True def content_menu_activate_cb(self, w, title_index): self.current_page_line_index = title_index self.current_page_char_index = 0 self.draw_text() def realize(self): gtk.DrawingArea.realize(self) def area_expose_cb(self, area, event): x, y, w, h, d = self.window.get_geometry() if self.pixmap_area and self._width == w and self._height == h: self.window.draw_drawable(self.gc, self.pixmap_area, 0, 0, 0, 0, w, h) #self.draw_status() # запоминаем время? ;) else: self.draw_text() def draw_text(self, go=None): # go => 'fore' or 'back' if go is None: # очищаем списки self.footnotes_list = [] self.prev_index_list = [] elif go == 'back': self.footnotes_list = [] x, y, widget_width, widget_height, depth = self.window.get_geometry() if not self.pixmap_area \ or self._width != widget_width \ or self._height != widget_height: if self.pixbuf: self.cur_pixbuf = self.pixbuf.scale_simple( widget_width, widget_height, gdk.INTERP_BILINEAR) else: self.create_background_pixmap(widget_width, widget_height, depth) self._width = widget_width self._height = widget_height self.pixmap_area = gdk.Pixmap(None, widget_width, widget_height, depth) self.next_pixmap_area = gdk.Pixmap( None, widget_width, widget_height, depth) self.status_pixmap_area = gdk.Pixmap( None, self._width, self.properties.status_font_height+self.properties.status_y_position, depth) # current page if go != 'fore': line_index, index = self._draw(self.pixmap_area, self.current_page_line_index, self.current_page_char_index) self.next_page_line_index = line_index self.next_page_char_index = index else: self.next_page_line_index = self.next_next_page_line_index self.next_page_char_index = self.next_next_page_char_index self.pixmap_area, self.next_pixmap_area = \ self.next_pixmap_area, self.pixmap_area self.draw_status(self.pixmap_area) self.window.draw_drawable(self.gc, self.pixmap_area, 0, 0, 0, 0, widget_width, widget_height) # next page line_index, index = self._draw(self.next_pixmap_area, self.next_page_line_index, self.next_page_char_index) self.next_next_page_line_index = line_index self.next_next_page_char_index = index def draw_status(self, da=None): if not self.properties.show_status: return True if not da: da = self.window width = self._width height = self.properties.status_font_height+self.properties.status_y_position if self.pixbuf: ## self.cur_pixbuf.render_to_drawable( ## self.status_pixmap_area, self.gc, ## 0, self._height-height, 0, 0, width, height, False, 0, 0) self.status_pixmap_area.draw_pixbuf( self.gc, self.cur_pixbuf, 0, self._height-height, 0, 0, width, height, False, 0, 0) else: self.status_pixmap_area.draw_drawable( self.gc, self.background_pixmap, 0, self._height-height, 0, 0, width, height) # position if self.properties.status_show_percent: pos = self.calc_position() pangolayout = self.create_pango_layout(str(int(pos*100))+'%') pangolayout.set_font_description(self.properties.status_pango_font_desc) self.status_pixmap_area.draw_layout( self.gc, self.properties.status_percent_x_position, 0, pangolayout, foreground=gdk.color_parse(self.properties.status_color)) # time if self.properties.status_show_clock: t = time.strftime('%H:%M', time.localtime()) pangolayout = self.create_pango_layout(t) pangolayout.set_font_description(self.properties.status_pango_font_desc) x_pos = width-self.properties.status_clock_x_position-pangolayout.get_extents()[1][2]/PANGO_SCALE self.status_pixmap_area.draw_layout( self.gc, x_pos, 0, pangolayout, foreground=gdk.color_parse(self.properties.status_color)) da.draw_drawable(self.gc, self.status_pixmap_area, 0, 0, 0, self._height-height, width, height) return True def _draw(self, area_window, line_index, index): def draw_1(line_index, index, x_offset, y_offset, text_area_width): #print 'draw_1', line_index, index is_top = True if index: # draw tail of line if need is_top = False y_offset, index = self.draw_paragraph( area_window, line_index, index, x_offset, y_offset, text_area_width, text_area_height) if index == -1: index = 0 line_index += 1 elif len(self.content) > 0: # пропускаем пустые строки в начале страницы while True: if len(self.content) <= line_index or \ self.content[line_index].type == 'image' or \ self.content[line_index].data != '': break line_index += 1 #print 'line_index:', line_index for par in self.content[line_index:]: #print 'par:', par if par.type != 'title' and par.data: is_top = False if par.type == 'image': if not self.content.fb.images.has_key(par.href): # workaround index = -1 line_index += 1 continue yy = self.draw_image(area_window, self.content.fb.images[par.href], x_offset, y_offset, text_area_width, text_area_height) if yy >= 0: index = -1 y_offset = yy line_index += 1 else: # не поместилось break else: if self.properties.new_page and \ not is_top and par.type == 'title': break if y_offset+self.properties.styles[par.type].font_height > self.properties.text_top_margin+text_area_height: break y_offset, index = self.draw_paragraph( area_window, line_index, 0, x_offset, y_offset, text_area_width, text_area_height) if index == -1: index = 0 line_index += 1 return line_index, index widget_width = self._width widget_height = self._height if self.pixbuf: ## self.cur_pixbuf.render_to_drawable( ## area_window, self.gc, 0, 0, 0, 0, -1, -1, False, 0, 0) area_window.draw_pixbuf( self.gc, self.cur_pixbuf, 0, 0, 0, 0, -1, -1, False, 0, 0) else: area_window.draw_drawable(self.gc, self.background_pixmap, 0, 0, 0, 0, widget_width, widget_height) text_area_height = widget_height-self.properties.text_top_margin-self.properties.text_bottom_margin # debug ## area_window.draw_line( ## self.gc, 0, widget_height-self.properties.text_bottom_margin, ## widget_width, widget_height-self.properties.text_bottom_margin) if self.properties.single_column: x_offset = self.properties.text_left_margin y_offset = self.properties.text_top_margin text_area_width = widget_width-self.properties.text_left_margin-self.properties.text_right_margin line_index, index = draw_1(line_index, index, x_offset, y_offset, text_area_width) else: # draw left page x_offset = self.properties.text_left_margin y_offset = self.properties.text_top_margin text_area_width = (widget_width-self.properties.text_left_margin-self.properties.text_right_margin-self.properties.text_spaces_between_column)/2 line_index, index = draw_1(line_index, index, x_offset, y_offset, text_area_width) # draw right page x_offset = text_area_width+self.properties.text_left_margin+self.properties.text_spaces_between_column #y_offset = self.properties.top_margin line_index, index = draw_1(line_index, index, x_offset, y_offset, text_area_width) #self.next_page_line_index = line_index #self.next_page_char_index = index #area_window.thaw_updates() return line_index, index def get_pixbuf(self, data, max_width, max_height): import tempfile, base64 data = base64.decodestring(data) tmp_file = tempfile.mktemp() try: open(tmp_file, 'w').write(data) pb = gdk.pixbuf_new_from_file(tmp_file) #pb = gtk.gdk.pixbuf_new_from_inline(len(data), data, TRUE) finally: os.remove(tmp_file) w = pb.get_width() h = pb.get_height() if w > max_width or h > max_height: scale = min(float(max_width)/w, float(max_height)/h) pb = pb.scale_simple(int(w*scale), int(h*scale), gdk.INTERP_BILINEAR) ## pb = pb.scale_simple(max_width, ## int(h*scale), ## gdk.INTERP_BILINEAR) return pb def draw_image(self, area_window, data, x_offset, y_offset, text_area_width, text_area_height): pb = self.get_pixbuf(data, text_area_width, text_area_height) is_top = y_offset==self.properties.text_top_margin and 1 or 0 width = pb.get_width() height = pb.get_height() if not is_top and y_offset+height > text_area_height: return -1 else: #pb = pb.add_alpha(TRUE, chr(255), chr(255), chr(255)) # фууу ## pb.render_to_drawable(area_window, self.gc, 0, 0, ## x_offset+(text_area_width-width)/2, y_offset, ## width, height, ## False, 0, 0) area_window.draw_pixbuf( self.gc, pb, 0, 0, x_offset+(text_area_width-width)/2, y_offset, width, height, False, 0, 0) return y_offset+height def draw_paragraph(self, area_window, line_index, char_index, x_offset, y_offset, text_area_width, text_area_height): # если параграф нарисован до конца - возвращает -1 new_index = 0 type = self.content[line_index].type style = self.properties.styles[type] footnote_style = self.properties.styles['footnote'] font_height = style.font_height self.gc.set_rgb_fg_color(style.gdk_color) if char_index == 0: y = y_offset+style.pixels_above_line else: y = y_offset while True: footnotes_height = 0 if self.footnotes_list: # calc height of footnote # --- footnotes_height = footnote_style.font_height for ft in self.footnotes_list[:-1]: if ft == []: footnotes_height += footnote_style.pixels_below_line \ -footnote_style.pixels_inside_wrap else: footnotes_height += footnote_style.font_height \ +footnote_style.pixels_inside_wrap #print '>>', footnotes_height, y, text_area_height if footnotes_height \ and y+footnotes_height >= text_area_height: #+self.properties.text_top_margin: self.draw_footnotes(area_window, x_offset, y, text_area_width, text_area_height) y = self._height new_index = char_index break if y+font_height > text_area_height+self.properties.text_top_margin: break if not self.content[line_index].data: # empty line y += font_height new_index = -1 break if char_index == 0: left_indent = style.left_indent+style.first_line_indent else: left_indent = style.left_indent ta_width = text_area_width-left_indent-style.right_indent while True: #print '>%s<' % self.content[line_index].data, \ # len(self.content[line_index].data), char_index if len(self.content[line_index].data) <= char_index \ or not self.content[line_index].data[char_index].isspace(): break char_index += 1 #print '>%s<' % self.content[line_index].data[char_index:].encode('koi8-r', 'replace') pl, new_index = self.get_pangolayout_list( ta_width, self.content[line_index], type, char_index) text_width = reduce(lambda a, b: a+b, map(lambda x: x[1], pl), 0) if style.justify != 'fill': space = style.space_width elif len(pl) <= 1: # single word space = 0 elif new_index == 0: # last line in this paragraph space = style.space_width else: space = float(ta_width-text_width)/(len(pl)-1) if style.justify in ('center', 'right'): p_length = 0 for p in pl: p_length += p[1] if style.justify == 'right': x = x_offset+left_indent+(ta_width-(len(pl)-1)*space-p_length) else: # style.justify == 'center' x = x_offset+left_indent+(ta_width-(len(pl)-1)*space-p_length)/2 else: x = x_offset+left_indent for i in range(len(pl)): if space and i == len(pl)-1 and not new_index == 0 \ and style.justify == 'fill': # last word in this line x = x_offset+text_area_width-pl[i][1]-style.right_indent # из-за надстрочных символов yy = y+font_height+style.pixels_inside_wrap \ -pl[i][0].get_extents()[1][3]/PANGO_SCALE # self.properties.styles['default'].text_height \ #self.gc.set_rgb_fg_color(style.gdk_color) area_window.draw_layout(self.gc, int(x), yy, pl[i][0]) #foreground=style.gdk_color) x += pl[i][1]+space if new_index == 0: # new paragraph y += font_height+style.pixels_below_line else: y += font_height+style.pixels_inside_wrap # -- footnotes -- ## for f in self.content[line_index].footnotes: ## if begin <= f[1] <= end: ## for par in self.content.fb.footnotes[f[2]]: if self.content[line_index].footnotes: ni = new_index == 0 \ and len(self.content[line_index].data) \ or new_index footnotes_list = self.create_footnotes_list(text_area_width, line_index, char_index, ni) if footnotes_list: self.footnotes_list.extend(footnotes_list) if new_index == 0: new_index = -1 break else: char_index = new_index return y, new_index def draw_footnotes(self, area_window, x_offset, y, text_area_width, text_area_height): style = self.properties.styles['footnote'] font_height = style.font_height space_width = style.space_width pixels_inside_wrap = style.pixels_inside_wrap pixels_below_line = style.pixels_below_line if y+font_height > text_area_height: #+self.properties.text_top_margin: print '++' return list_index = 0 gc = self.gc area_window.draw_line(gc, x_offset, y+font_height*2/3, x_offset+text_area_width/3, y+font_height*2/3) y += font_height for pl in self.footnotes_list: if not self.footnotes_list[list_index]: # empty list -> new paragraph list_index += 1 continue #print y, font_height, text_area_height if y+font_height+pixels_inside_wrap > text_area_height+self.properties.text_top_margin: #print '--', y, pixels_inside_wrap break text_width = reduce(lambda a, b: a+b, map(lambda x: x[1], pl), 0) if len(pl) <= 1: # single word space = 0 elif not self.footnotes_list[list_index+1]: # last line in this paragraph space = space_width else: space = (text_area_width-text_width)/(len(pl)-1) x = x_offset for i in range(len(pl)): if i == len(pl)-1 and self.footnotes_list[list_index+1]: # and not index == 0: # and not is_title: # last word in this line x = x_offset+text_area_width-pl[i][1] self.gc.set_rgb_fg_color(style.gdk_color) area_window.draw_layout(self.gc, x, y, pl[i][0]) #foreground=style.gdk_color) x += pl[i][1]+space if not self.footnotes_list[list_index+1]: # last line in this paragraph y += font_height+pixels_below_line else: y += font_height+pixels_inside_wrap list_index += 1 self.footnotes_list = self.footnotes_list[list_index:] def create_footnotes_list(self, text_area_width, line_index, begin, end): footnotes_list = [] for f in self.content[line_index].footnotes: if begin <= f[1] <= end and self.content.fb.footnotes.has_key(f[2]): for par in self.content.fb.footnotes[f[2]]: #print par.data.encode('koi8-r', 'replace') index = 0 #print '>>>', len(par.data) #if par.data: while True: pl, index = self.get_pangolayout_list( text_area_width, par, 'footnote', index) footnotes_list.append(pl) if index == 0: break #else: # print 'par.data is empty' footnotes_list.append([]) return footnotes_list def get_pangolayout_list(self, text_area_width, par, type, char_index=0, end_char_index=0): '''create list: [[pangolayout, width, index] [pangolayout, width, index] [...]] ''' def create_pangolayout(word, text_start, text_end): pangolayout = self.create_pango_layout(word.encode('utf-8')) pangolayout.set_font_description(font) attr_list = None for start, length, st in par.styles: if not start+length <= text_start and not start >= text_end: if not attr_list: attr_list = pango.AttrList() s = start-text_start e = s+length if s < 0: s = 0 s = len(word[:s].encode('utf-8')) e = len(word[:e].encode('utf-8')) if st in ('emphasis', 'strong', 'hyperlink'): if self.properties.styles[st].__dict__.has_key('italic'): attr_list.insert(pango.AttrStyle( pango.STYLE_ITALIC, s, e)) if self.properties.styles[st].__dict__.has_key('bold'): attr_list.insert(pango.AttrWeight( pango.WEIGHT_BOLD, s, e)) if self.properties.styles[st].__dict__.has_key('font_family'): attr_list.insert(pango.AttrFamily( self.properties.styles[st].font_family, s, e)) if self.properties.styles[st].__dict__.has_key('color'): attr_list.insert(pango.AttrForeground( self.properties.styles[st].gdk_color.red, self.properties.styles[st].gdk_color.green, self.properties.styles[st].gdk_color.blue, s, e)) if self.properties.styles[st].__dict__.has_key('font_size'): attr_list.insert(pango.AttrSize( self.properties.styles[st].font_size*PANGO_SCALE, s, e)) elif st == 'footnote': attr_list.insert(pango.AttrRise(PANGO_SCALE*style.font_height/4, s, e)) attr_list.insert(pango.AttrFontDesc(self.properties.styles['footnote'].font, s, e)) if attr_list: pangolayout.set_attributes(attr_list) return pangolayout def split_line(line): # split line, f.e.: # '\taa bbbb cc ddd' -> # [('\taa', 0, 4), ('bbbb', 4, 9), (' cc', 9, 13), # (' ', 13, 15), (' ddd', 15, 20)] word = '' index = 0 prev_index = 0 for c in line: index += 1 if c in string.whitespace: if word: yield (word, prev_index, index) prev_index = index word = '' continue #else: word += c if word: yield (word, prev_index, index+1) return if end_char_index: line = par.data[char_index:end_char_index] else: line = par.data[char_index:] style = self.properties.styles[type] font = style.font #print style.font_family space_width = style.space_width hyphenate = style.hyphenate text_width = 0 total_width = 0 pangolayout_list = [] new_index = 0 prev_text_start = 0 for word, prev_index, index in split_line(line): #print '>>>', word.encode('koi8-r', 'replace') text_start = char_index+prev_index text_end = char_index+index pangolayout = create_pangolayout(word, text_start, text_end) width = pangolayout.get_extents()[1][2]/PANGO_SCALE if total_width+width > text_area_width: if hyphenate: ## hyphenation lang = self.content.lang for start, length, st in par.styles: if not start+length <= text_start \ and not start >= text_end and st[:4] == 'lang': lang = st[5:] wl = self.hyph.hyphenate(word, lang) for w in wl: text_start = char_index+prev_index text_end = char_index+index pl = create_pangolayout(w+u'-', text_start, text_end) wd = pl.get_extents()[1][2]/PANGO_SCALE if total_width+wd < text_area_width: text_width += wd index -= len(word)-len(w)+1 # skip leading '-' if word[len(w)] == '-': index += 1 pangolayout_list.append((pl, wd, index)) prev_index = index break new_index = char_index+prev_index if len(pangolayout_list) == 0: # слово не влазит в строку for i in range(len(word), 0, -1): w = word[:i] pl = create_pangolayout(w, char_index, char_index+i+1) wd = pl.get_extents()[1][2]/PANGO_SCALE if total_width+wd < text_area_width: text_width += wd prev_index = i pangolayout_list.append((pl, wd, char_index+i)) new_index = char_index+i break elif len(pangolayout_list) > 4 \ and prev_index < len(line) \ and line[prev_index] in u'-\u2013\u2014': # строка не должна начинаться с тире #print '+++', line[prev_index:prev_index+40].encode('koi8-r', 'replace') del pangolayout_list[-1] if hyphenate: # TODO lang = self.content.lang for start, length, st in par.styles: if not start+length <= text_start \ and not start >= text_end and st[:4] == 'lang': lang = st[5:] word = line[prev_text_start:prev_index] wl = self.hyph.hyphenate(word, lang) if wl: w = wl [0] text_start = char_index+prev_text_start text_end = char_index+len(w) pl = create_pangolayout(w+u'-', text_start, text_end) wd = pl.get_extents()[1][2]/PANGO_SCALE index = prev_text_start+len(w) # skip leading '-' if word[len(w)] == '-': index += 1 pangolayout_list.append((pl, wd, index)) prev_text_start += len(w) new_index = char_index+prev_text_start break text_width += width total_width += width+space_width pangolayout_list.append((pangolayout, width, index)) prev_text_start = prev_index return pangolayout_list, new_index def calc_back(self): def calc_back_1(line_index, index, text_area_width, text_area_height): new_line_index = line_index #new_index = index new_index = 0 y = 0 if index: # tail of line par = self.content[line_index] type = par.type style = self.properties.styles[type] #font_height = style.font_height index_list = [0] while True: pl, new_index = self.get_pangolayout_list( text_area_width, par, par.type, new_index, index) if new_index == 0: break index_list.append(new_index) index_list.reverse() for new_index in index_list: if new_index == 0: # new paragraph y += style.font_height+style.pixels_below_line else: y += style.font_height+style.pixels_inside_wrap if y+style.font_height+style.pixels_below_line > text_area_height: return new_line_index, new_index paragraphs = self.content[:new_line_index] paragraphs.reverse() for par in paragraphs: type = par.type #line = par.data new_line_index -= 1 if type == 'image': y += style.font_height #print len(self.content.fb.images[par.href]) pb = self.get_pixbuf(self.content.fb.images[par.href], text_area_width, text_area_height) h = pb.get_height() if y+h >= text_area_height: return new_line_index, 0 y += h #+self.properties.styles['default'].text_height continue style = self.properties.styles[type] index_list = [0] new_index = 0 while True: ta_width = text_area_width - (new_index==0 and style.first_line_indent or 0) pl, new_index = self.get_pangolayout_list( ta_width, par, type, new_index) if new_index == 0: break index_list.append(new_index) if not index_list: # empty line y += style.font_height+style.pixels_below_line if y+style.font_height+style.pixels_below_line > text_area_height: return new_line_index, 0 continue index_list.reverse() for new_index in index_list: if new_index == 0: # new paragraph y += style.font_height+style.pixels_below_line else: y += style.font_height+style.pixels_inside_wrap if y+style.font_height+style.pixels_below_line > text_area_height: return new_line_index, new_index return 0, 0 text_area_height = self._height-self.properties.text_top_margin-self.properties.text_bottom_margin line_index = self.current_page_line_index index = self.current_page_char_index if self.properties.single_column: text_area_width = self._width-self.properties.text_left_margin-self.properties.text_right_margin line_index, index = calc_back_1(line_index, index, text_area_width, text_area_height) else: text_area_width = (self._width-self.properties.text_left_margin-self.properties.text_right_margin-self.properties.text_spaces_between_column)/2 # right page line_index, index = calc_back_1(line_index, index, text_area_width, text_area_height) # left page line_index, index = calc_back_1(line_index, index, text_area_width, text_area_height) return line_index, index def go_fore_page(self): if len(self.content) <= self.next_page_line_index: return self.prev_index_list.append((self.current_page_line_index, self.current_page_char_index)) #if self.next_page_line_index == len(self.lines): # return self.current_page_line_index = self.next_page_line_index self.current_page_char_index = self.next_page_char_index self.draw_text(go='fore') def go_back_page(self): if self.current_page_line_index == 0 \ and self.current_page_char_index == 0: return if self.prev_index_list: line_index, index = self.prev_index_list[-1] del self.prev_index_list[-1] else: line_index, index = self.calc_back() self.current_page_line_index = line_index self.current_page_char_index = index self.draw_text(go='back') def calc_position(self): if self.next_page_line_index == len(self.content): return 1.0 if self.content.text_length: cur_pos = reduce(lambda x, y: x+len(y.data), self.content[:self.next_page_line_index], 0) return float(cur_pos)/self.content.text_length else: return 0.0 get_position = calc_position def get_offset(self): offset = reduce(lambda x, y: x+len(y.data), self.content[:self.next_page_line_index], 0) return offset def set_position(self, pos): index = self.content.text_length*pos i = 0 n = 0 for par in self.content: if index <= n: break n += len(par.data) i += 1 self.current_page_line_index = i self.current_page_char_index = 0 self.draw_text() def key_event_cb(self, w, event): if event.keyval in self.keysyms_next: self.go_fore_page() return True if event.keyval in self.keysyms_prev: self.go_back_page() return True return False def button_event_cb(self, w, event): if event.button == 1: self.go_fore_page() elif event.button == 2: #print 'event.button == 2' self.go_back_page() if __name__ == '__main__': #import sys if len(sys.argv) != 2: sys.exit('usage: %s file' % sys.argv[0]) window = gtk.Window() window.connect('destroy', lambda w: gtk.main_quit()) ob = OrnamentBook(window) window.add(ob) #text = '' #for i in xrange(20): # text = text+str(i)*3+' ' #text = (text+' aa bb cc ddddd\n')*7 text = open(sys.argv[1]).read() ob.set_text(unicode(text, 'utf-8')) ob.realize() window.show_all() gtk.main()