#!/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('<?xml'):
            ret = self.open_fb2_file(data)

        if not ret:
            if not self.encoding:
                import locale
                enc = detectcharset(StringIO(data))
                self.encoding = enc or (locale.getdefaultlocale()[1]
                                        or 'iso8859-1')

            ret = self.open_plain_text_file(data)

        if ret:
            self.text_length = reduce(lambda x, y: x+len(y.data),
                                      self._content, 0)

##         n = 0
##         for par in self._content:
##             self.shelf[str(n)] = par
##             n += 1
##         print '+++'
##         self.shelf['len'] = len(self._content)
##         #self.shelf['lang'] = self.lang
##         #self.shelf['encoding'] = self.encoding

        return ret


    def open_plain_text_file(self, data):

        # remove \x00 chars
        data = data.replace('\0', '')
        data = unicode(data, self.encoding)

        self._content = []
        for l in data.splitlines():
            paragraph = TextParagraph('paragraph')
            paragraph.data = l
            self._content.append(paragraph)

        if not self.lang: self.lang = default_lang

        #self.current_page_line_index = line_index
        #self.current_page_char_index = char_index
        #self._content_menu.remove_submenu()

        return True


    def open_fb2_file(self, data):

        from fb2parser import FB2Parser
        from libxml2 import parserError

        self.encoding = 'utf-8'

        fb = FB2Parser()
        #try:
        fb.parseFB2(data)
        #except parserError, err:
        #    error_dialog(self._parent, 'Can\'t parse XML file %s: %s'
        #                 % (fix_filename(filename), str(err)))
        #    return False

        self.fb = fb
        self._content = fb.content
        if not self.lang: self.lang = fb.lang

        #self.current_page_line_index = line_index
        #self.current_page_char_index = char_index

        self.book_title = fb.book_title
        self.authors = ', '.join(fb.authors)
        self.titles_list = fb.titles_list

##         menu = gtk.Menu()
##         menu.show()
##         for title, title_index in fb.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 __getitem__(self, key):
        from types import SliceType
        #print '__getitem__', type(key)
        if type(key) == SliceType:
##             print '>>>>>', 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()
