2007-08-17 Zsolt Foldvari <zfoldvar@users.sourceforge.net>

* src/docgen/GtkPrint.py: Text report generation is almost ready...



svn: r8827
This commit is contained in:
Zsolt Foldvari 2007-08-17 19:15:05 +00:00
parent 2da3c3931d
commit 679bbcc469
2 changed files with 411 additions and 123 deletions

View File

@ -1,3 +1,6 @@
2007-08-17 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* src/docgen/GtkPrint.py: Text report generation is almost ready...
2007-08-16 Benny Malengier <bm@cage.ugent.be>
* src/Exporter.py: correct crash in export assistent on invalid filenames

View File

@ -25,6 +25,8 @@
__revision__ = "$Revision$"
DEBUG = True
#------------------------------------------------------------------------
#
# Python modules
@ -286,9 +288,11 @@ def fontstyle_to_fontdescription(font_style):
"""
fonts = {
BaseDoc.FONT_SERIF: 'serif',
BaseDoc.FONT_SANS_SERIF: 'sans',
BaseDoc.FONT_MONOSPACE: 'monospace',
#BaseDoc.FONT_SERIF: 'serif',
#BaseDoc.FONT_SANS_SERIF: 'sans',
BaseDoc.FONT_SERIF: 'Times New Roman',
BaseDoc.FONT_SANS_SERIF: 'Arial',
BaseDoc.FONT_MONOSPACE: 'monospace',
}
if font_style.get_bold():
@ -314,9 +318,9 @@ def tabstops_to_tabarray(tab_stops, dpi):
tab_array = pango.TabArray(len(tab_stops), False)
for index in range(len(tab_stops)):
location = tab_stops[index] * dpi * pango.SCALE / 2.54
tab_array.set_tab(index, pango.TAB_LEFT, location)
location = tab_stops[index] * dpi * pango.SCALE / 2.54
tab_array.set_tab(index, pango.TAB_LEFT, location)
return tab_array
##class PrintFacade(gtk.PrintOperation):
@ -426,6 +430,68 @@ def tabstops_to_tabarray(tab_stops, dpi):
##if res == gtk.PRINT_OPERATION_RESULT_APPLY:
##self._settings = self.get_print_settings()
#------------------------------------------------------------------------
#
# Table row style
#
#------------------------------------------------------------------------
class RowStyle(list):
"""Specifies the format of a table row.
RowStyle extents the available styles in BaseDoc.
The RowStyle contains the width of each column as a percentage of the
width of the full row. Note! The width of the row is not know until
divide() or draw() method is called.
"""
def __init__(self):
self.columns = []
def set_columns(self, columns):
"""Set the number of columns.
@param columns: number of columns that should be used.
@param type: int
"""
self.columns = columns
def get_columns(self):
"""Return the number of columns.
"""
return self.columns
def set_column_widths(self, clist):
"""Set the width of all the columns at once.
@param clist: list of width of columns in % of the full row.
@param tyle: list
"""
self.columns = len(clist)
for i in range(self.columns):
self.colwid[i] = clist[i]
def set_column_width(self, index, width):
"""
Sets the width of a specified column to the specified width.
@param index: column being set (index starts at 0)
@param width: percentage of the table width assigned to the column
"""
self.colwid[index] = width
def get_column_width(self, index):
"""
Returns the column width of the specified column as a percentage of
the entire table width.
@param index: column to return (index starts at 0)
"""
return self.colwid[index]
#------------------------------------------------------------------------
#
# Document element classes
@ -438,14 +504,23 @@ class GtkDocBaseElement(object):
Support document element structuring and can render itself onto
a Cairo surface.
There are two cathegories of methods:
1. hierarchy building methods (add_child, get_children, set_parent,
get_parent);
2. rendering methods (divide, draw).
The hierarchy building methods generally don't have to be overriden in
the subclass, while the rendering methods (divide, draw) must be
implemented in the subclasses.
"""
_type = 'BASE'
_allowed_children = None
def __init__(self):
def __init__(self, style=None):
self._parent = None
self._children = []
self._style = None
self._style = style
def get_type(self):
"""Get the type of this element.
@ -481,7 +556,7 @@ class GtkDocBaseElement(object):
return True
def get_children(self):
"""Get the list if children fo this element.
"""Get the list of children of this element.
"""
return self._children
@ -494,10 +569,10 @@ class GtkDocBaseElement(object):
@param type: device points
@param height: height of available space for this element
@param type: device points
@param dpi_x: the horizontal resolution
@param type: dots per inch
@param dpi_y: the vertical resolution
@param type: dots per inch
@param dpi_x: the horizontal resolution
@param type: dots per inch
@param dpi_y: the vertical resolution
@param type: dots per inch
@return: the divided element, and the height of the first part
@rtype: (GtkDocXXX-1, GtkDocXXX-2), device points
@ -509,15 +584,15 @@ class GtkDocBaseElement(object):
"""Draw itself onto a cairo surface.
@param cairo_context: context to draw on
@param type: cairo.Context class
@param type: cairo.Context class
@param pango_layout: pango layout to write on
@param type: pango.Layout class
@param width: width of available space for this element
@param type: device points
@param dpi_x: the horizontal resolution
@param type: dots per inch
@param dpi_y: the vertical resolution
@param type: dots per inch
@param dpi_x: the horizontal resolution
@param type: dots per inch
@param dpi_y: the vertical resolution
@param type: dots per inch
@return: height of the element
@rtype: device points
@ -532,13 +607,13 @@ class GtkDocDocument(GtkDocBaseElement):
_allowed_children = ['PARAGRAPH', 'PAGEBREAK', 'TABLE', 'MEDIA']
def draw(self, cairo_context, pango_layout, width, dpi_x, dpi_y):
x = y = elem_height = 0
for elem in self._children:
cairo_context.translate(x, elem_height)
cairo_context.translate(x, elem_height)
elem_height = elem.draw(cairo_context, pango_layout,
width, dpi_x, dpi_y)
width, dpi_x, dpi_y)
y += elem_height
return y
@ -562,48 +637,49 @@ class GtkDocParagraph(GtkDocBaseElement):
spacing = 2
def __init__(self, style, leader=None):
GtkDocBaseElement.__init__(self)
self._style = style
if leader:
self._text = leader + '\t'
self._style.set_tabs([-1 * self._style.get_first_indent()])
else:
self._text = ''
GtkDocBaseElement.__init__(self, style)
if leader:
self._text = leader + '\t'
# FIXME append new tab to the existing tab list
self._style.set_tabs([-1 * self._style.get_first_indent()])
else:
self._text = ''
def add_text(self, text):
self._text = self._text + text
def divide(self, layout, width, height, dpi_x, dpi_y):
l_margin = self._style.get_left_margin() * dpi_x / 2.54
r_margin = self._style.get_right_margin() * dpi_x / 2.54
t_margin = self._style.get_top_margin() * dpi_y / 2.54
b_margin = self._style.get_bottom_margin() * dpi_y / 2.54
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
f_indent = self._style.get_first_indent() * dpi_x / 2.54
l_margin = self._style.get_left_margin() * dpi_x / 2.54
r_margin = self._style.get_right_margin() * dpi_x / 2.54
t_margin = self._style.get_top_margin() * dpi_y / 2.54
b_margin = self._style.get_bottom_margin() * dpi_y / 2.54
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
f_indent = self._style.get_first_indent() * dpi_x / 2.54
# calculate real width available for text
text_width = width - l_margin - 2 * h_padding - r_margin
if f_indent < 0:
text_width -= f_indent
if f_indent < 0:
text_width -= f_indent
layout.set_width(floor(text_width * pango.SCALE))
# set paragraph properties
layout.set_wrap(pango.WRAP_WORD_CHAR)
layout.set_spacing(self.spacing * pango.SCALE)
layout.set_indent(f_indent * pango.SCALE)
layout.set_tabs(tabstops_to_tabarray(self._style.get_tabs(), dpi_x))
#
align = self._style.get_alignment_text()
if align == 'left':
layout.set_alignment(pango.ALIGN_LEFT)
elif align == 'right':
layout.set_alignment(pango.ALIGN_RIGHT)
elif align == 'center':
layout.set_alignment(pango.ALIGN_CENTER)
elif align == 'justify':
layout.set_justify(True)
#
layout.set_indent(f_indent * pango.SCALE)
layout.set_tabs(tabstops_to_tabarray(self._style.get_tabs(), dpi_x))
#
align = self._style.get_alignment_text()
if align == 'left':
layout.set_alignment(pango.ALIGN_LEFT)
elif align == 'right':
layout.set_alignment(pango.ALIGN_RIGHT)
elif align == 'center':
layout.set_alignment(pango.ALIGN_CENTER)
elif align == 'justify':
layout.set_justify(True)
#
font_style = self._style.get_font()
layout.set_font_description(fontstyle_to_fontdescription(font_style))
@ -612,7 +688,7 @@ class GtkDocParagraph(GtkDocBaseElement):
layout_width, layout_height = layout.get_size()
line_height = layout_height / pango.SCALE + self.spacing
# and the number of lines fit on the available height
text_height = height - t_margin - 2 * v_padding
text_height = height - t_margin - 2 * v_padding
line_per_height = text_height / line_height
# if nothing fits
@ -624,81 +700,82 @@ class GtkDocParagraph(GtkDocBaseElement):
layout_width, layout_height = layout.get_size()
line_count = layout.get_line_count()
# if all paragraph fits we don't need to cut
# if all paragraph fits we don't need to cut
if line_count <= line_per_height:
paragraph_height = ((layout_height / pango.SCALE) +
t_margin +
(2 * v_padding))
if height - paragraph_height > b_margin:
paragraph_height += b_margin
return (self, None), paragraph_height
paragraph_height = ((layout_height / pango.SCALE) +
t_margin +
(2 * v_padding))
if height - paragraph_height > b_margin:
paragraph_height += b_margin
return (self, None), paragraph_height
# get index of first character which doesn't fit on available height
layout_line = layout.get_line(line_per_height)
index = layout_line.start_index
# and divide the text, first create the second part
new_style = BaseDoc.ParagraphStyle(self._style)
new_style.set_top_margin(0)
new_style = BaseDoc.ParagraphStyle(self._style)
new_style.set_top_margin(0)
new_paragraph = GtkDocParagraph(new_style)
new_paragraph.add_text(self._text[index:])
new_paragraph.add_text(self._text.encode('utf-8')[index:])
# then update the first one
self._text = self._text[:index]
self._style.set_bottom_margin(0)
self._text = self._text.encode('utf-8')[:index]
self._style.set_bottom_margin(0)
# FIXME do we need to return the proper height???
#paragraph_height = line_height * line_count + t_margin + 2 * v_padding
paragraph_height = 0
#paragraph_height = line_height * line_count + t_margin + 2 * v_padding
paragraph_height = 0
return (self, new_paragraph), paragraph_height
def draw(self, cr, layout, width, dpi_x, dpi_y):
l_margin = self._style.get_left_margin() * dpi_x / 2.54
r_margin = self._style.get_right_margin() * dpi_x / 2.54
t_margin = self._style.get_top_margin() * dpi_y / 2.54
b_margin = self._style.get_bottom_margin() * dpi_y / 2.54
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
f_indent = self._style.get_first_indent() * dpi_x / 2.54
l_margin = self._style.get_left_margin() * dpi_x / 2.54
r_margin = self._style.get_right_margin() * dpi_x / 2.54
t_margin = self._style.get_top_margin() * dpi_y / 2.54
b_margin = self._style.get_bottom_margin() * dpi_y / 2.54
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
f_indent = self._style.get_first_indent() * dpi_x / 2.54
# calculate real width available for text
text_width = width - l_margin - 2 * h_padding - r_margin
if f_indent < 0:
text_width -= f_indent
if f_indent < 0:
text_width -= f_indent
layout.set_width(floor(text_width * pango.SCALE))
# set paragraph properties
layout.set_wrap(pango.WRAP_WORD_CHAR)
layout.set_spacing(self.spacing * pango.SCALE)
layout.set_indent(f_indent * pango.SCALE)
layout.set_tabs(tabstops_to_tabarray(self._style.get_tabs(), dpi_x))
#
align = self._style.get_alignment_text()
if align == 'left':
layout.set_alignment(pango.ALIGN_LEFT)
elif align == 'right':
layout.set_alignment(pango.ALIGN_RIGHT)
elif align == 'center':
layout.set_alignment(pango.ALIGN_CENTER)
elif align == 'justify':
layout.set_justify(True)
#
font_style = self._style.get_font()
layout.set_indent(f_indent * pango.SCALE)
layout.set_tabs(tabstops_to_tabarray(self._style.get_tabs(), dpi_x))
#
align = self._style.get_alignment_text()
if align == 'left':
layout.set_alignment(pango.ALIGN_LEFT)
elif align == 'right':
layout.set_alignment(pango.ALIGN_RIGHT)
elif align == 'center':
layout.set_alignment(pango.ALIGN_CENTER)
elif align == 'justify':
layout.set_justify(True)
#
font_style = self._style.get_font()
layout.set_font_description(fontstyle_to_fontdescription(font_style))
# layout the text
# layout the text
layout.set_markup(self._text)
layout_width, layout_height = layout.get_size()
# render the layout onto the cairo surface
x = l_margin + h_padding
if f_indent < 0:
x += f_indent
x = l_margin + h_padding
if f_indent < 0:
x += f_indent
cr.move_to(x, t_margin + v_padding)
cr.set_source_rgb(0, 0, 0)
cr.show_layout(layout)
# calculate the full paragraph height
height = layout_height/pango.SCALE + t_margin + 2*v_padding + b_margin
# calculate the full paragraph height
height = layout_height/pango.SCALE + t_margin + 2*v_padding + b_margin
# draw the borders
# draw the borders
if self._style.get_top_border():
cr.move_to(l_margin, t_margin)
cr.rel_line_to(width - l_margin - r_margin, 0)
@ -712,16 +789,203 @@ class GtkDocParagraph(GtkDocBaseElement):
cr.move_to(l_margin, t_margin)
cr.line_to(0, height - t_margin - b_margin)
#cr.move_to(0, 0)
#cr.line_to(layout_width / pango.SCALE, layout_height / pango.SCALE)
cr.set_line_width(1)
cr.set_source_rgb(0, 0, 0)
cr.stroke()
if DEBUG:
cr.set_line_width(0.1)
cr.set_source_rgb(1.0, 0, 0)
cr.rectangle(0, 0, width, height)
cr.stroke()
cr.set_source_rgb(0, 0, 1.0)
cr.rectangle(l_margin, t_margin,
width-l_margin-r_margin, height-t_margin-b_margin)
cr.stroke()
return height
class GtkDocTable(GtkDocBaseElement):
"""Implement a table.
"""
_type = 'TABLE'
_allowed_children = ['ROW']
def divide(self, layout, width, height, dpi_x, dpi_y):
#calculate real table width
table_width = width * self._style.get_width() / 100
# calculate the height of each row
table_height = 0
row_index = 0
while row_index < len(self._children):
row = self._children[row_index]
(r1, r2), row_height = row.divide(layout, table_width, height,
dpi_x, dpi_y)
if table_height + row_height >= height:
break
table_height += row_height
row_index += 1
# divide the table if any row did not fit
new_table = None
if row_index < len(self._children):
new_table = GtkDocTable(self._style)
for row in self._children[row_index:]:
new_table.add_child(row)
del self._children[row_index:]
return (self, new_table), table_height
def draw(self, cr, layout, width, dpi_x, dpi_y):
#calculate real table width
table_width = width * self._style.get_width() / 100
# TODO is a table always left aligned??
table_height = 0
# draw all the rows
for row in self._children:
cr.save()
cr.translate(0, table_height)
row_height = row.draw(cr, layout, table_width, dpi_x, dpi_y)
cr.restore()
table_height += row_height
if DEBUG:
cr.set_line_width(0.1)
cr.set_source_rgb(1.0, 0, 0)
cr.rectangle(0, 0, table_width, table_height)
cr.stroke()
return table_height
class GtkDocTableRow(GtkDocBaseElement):
"""Implement a row in a table.
"""
_type = 'ROW'
_allowed_children = ['CELL']
def divide(self, layout, width, height, dpi_x, dpi_y):
# the highest cell gives the height of the row
cell_heights = []
cell_width_iter = self._style.__iter__()
for cell in self._children:
cell_width = 0
for i in range(cell.get_span()):
cell_width += cell_width_iter.next()
cell_width = cell_width * width / 100
(c1, c2), cell_height = cell.divide(layout, cell_width, height,
dpi_x, dpi_y)
cell_heights.append(cell_height)
return (self, None), max(cell_heights)
def draw(self, cr, layout, width, dpi_x, dpi_y):
cr.save()
# draw all the cells
cell_heights = []
cell_width_iter = self._style.__iter__()
for cell in self._children:
cell_width = 0
for i in range(cell.get_span()):
cell_width += cell_width_iter.next()
cell_width = cell_width * width / 100
cell_height = cell.draw(cr, layout, cell_width, dpi_x, dpi_y)
cell_heights.append(cell_height)
cr.translate(cell_width, 0)
row_height = max(cell_heights)
cr.restore()
if DEBUG:
cr.set_line_width(0.1)
cr.set_source_rgb(0, 0, 1.0)
cr.rectangle(0, 0, width, row_height)
cr.stroke()
return row_height
class GtkDocTableCell(GtkDocBaseElement):
"""Implement a cell in a table row.
"""
_type = 'CELL'
_allowed_children = ['PARAGRAPH', 'MEDIA']
def __init__(self, style, span=1):
GtkDocBaseElement.__init__(self, style)
self._span = span
def get_span(self):
return self._span
def divide(self, layout, width, height, dpi_x, dpi_y):
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
# calculate real available width
width -= 2 * h_padding
# calculate height of each children
cell_height = 0
for child in self._children:
(e1, e2), child_height = child.divide(layout, width, height,
dpi_x, dpi_y)
cell_height += child_height
# calculate real height
cell_height += 2 * v_padding
return (self, None), cell_height
def draw(self, cr, layout, width, dpi_x, dpi_y):
h_padding = self._style.get_padding() * dpi_x / 2.54
v_padding = self._style.get_padding() * dpi_y / 2.54
# calculate real available width
i_width = width - 2 * h_padding
# draw children
cr.save()
cr.translate(h_padding, v_padding)
cell_height = 0
for child in self._children:
child_height = child.draw(cr, layout, i_width, dpi_x, dpi_y)
cell_height += child_height
cr.translate(0, child_height)
cr.restore()
# calculate real height
cell_height += 2 * v_padding
# draw the borders
if self._style.get_top_border():
cr.move_to(0, 0)
cr.rel_line_to(width , 0)
if self._style.get_right_border():
cr.move_to(width, 0)
cr.rel_line_to(0, cell_height)
if self._style.get_bottom_border():
cr.move_to(0, cell_height)
cr.rel_line_to(width, 0)
if self._style.get_left_border():
cr.move_to(0, 0)
cr.line_to(0, cell_height)
cr.set_line_width(1)
cr.set_source_rgb(0, 0, 0)
cr.stroke()
if DEBUG:
cr.set_line_width(0.1)
cr.set_source_rgb(0, 1.0, 0)
cr.rectangle(0, 0, width, cell_height)
cr.stroke()
return cell_height
#------------------------------------------------------------------------
#
# CairoDoc and GtkPrint class
@ -772,33 +1036,48 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc):
new_paragraph = GtkDocParagraph(style, leader)
self._active_element.add_child(new_paragraph)
self._active_element = new_paragraph
def end_paragraph(self):
self._active_element = self._active_element.get_parent()
def start_table(self, name, style_name):
pass
style_sheet = self.get_style_sheet()
style = style_sheet.get_table_style(style_name)
new_table = GtkDocTable(style)
self._active_element.add_child(new_table)
self._active_element = new_table
# we need to remember the column width list from the table style.
# this is an ugly hack, but got no better idea.
self._active_row_style = []
for i in range(style.get_columns()):
self._active_row_style.append(style.get_column_width(i))
def end_table(self):
pass
self._active_element = self._active_element.get_parent()
def start_row(self):
pass
new_row = GtkDocTableRow(self._active_row_style)
self._active_element.add_child(new_row)
self._active_element = new_row
def end_row(self):
pass
self._active_element = self._active_element.get_parent()
def start_cell(self, style_name, span=1):
pass
style_sheet = self.get_style_sheet()
style = style_sheet.get_cell_style(style_name)
new_cell = GtkDocTableCell(style, span)
self._active_element.add_child(new_cell)
self._active_element = new_cell
def end_cell(self):
pass
self._active_element = self._active_element.get_parent()
def write_note(self, text, format, style_name):
log.debug("write_note: %s" % text)
if format == 1:
for line in text.split('\n'):
self.start_paragraph(style_name)
@ -813,10 +1092,9 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc):
self.end_paragraph()
def write_text(self, text, mark=None):
log.debug("write_text: %s" % text)
# FIXME this is ugly, do we really need it?
text = text.replace('<super>', '<small><sup>')
text = text.replace('</super>', '</sup></small>')
# FIXME this is ugly, do we really need it?
text = text.replace('<super>', '<small><sup>')
text = text.replace('</super>', '</sup></small>')
self._active_element.add_text(text)
def add_media_object(self, name, pos, x_cm, y_cm):
@ -855,11 +1133,12 @@ class GtkPrint(CairoDoc):
print_operation = gtk.PrintOperation()
print_operation.set_default_page_setup(page_setup)
print_operation.set_show_progress(True)
print_operation.connect("begin_print", self.on_begin_print)
print_operation.connect("draw_page", self.on_draw_page)
print_operation.connect("paginate", self.on_paginate)
self.print_settings = None
self.print_settings = None
self.do_print(print_operation)
def do_print(self, operation):
@ -914,16 +1193,16 @@ class GtkPrint(CairoDoc):
"""
layout = context.create_pango_layout()
dpi_x = context.get_dpi_x()
dpi_y = context.get_dpi_y()
dpi_x = context.get_dpi_x()
dpi_y = context.get_dpi_y()
# try to fit the next element to current page, divide it if needed
elem = self.elements_to_paginate.pop(0)
(e1, e2), e1_h = elem.divide(layout,
self.page_width,
self.available_height,
dpi_x,
dpi_y)
dpi_x,
dpi_y)
# if (part of) it fits on current page add it
if e1 is not None:
@ -963,12 +1242,18 @@ class GtkPrint(CairoDoc):
"""
cr = context.get_cairo_context()
layout = context.create_pango_layout()
dpi_x = context.get_dpi_x()
dpi_y = context.get_dpi_y()
layout = context.create_pango_layout()
dpi_x = context.get_dpi_x()
dpi_y = context.get_dpi_y()
self._pages[page_nr].draw(cr, layout, self.page_width, dpi_x, dpi_y)
if DEBUG:
cr.set_line_width(0.1)
cr.set_source_rgb(0, 1.0, 0)
cr.rectangle(0, 0, self.page_width, self.page_height)
cr.stroke()
self._pages[page_nr].draw(cr, layout, self.page_width, dpi_x, dpi_y)
#def on_preview(self, operation, preview, context, parent, dummy=None):
#"""