-
Notifications
You must be signed in to change notification settings - Fork 265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vertically align Text in a Cell or Multicell #210
Comments
Hi @johann-su Interesting question: no, there is currently no way to control vertical alignement in cells. There are currently several methods to render tables with fpdf2: https://pyfpdf.github.io/fpdf2/Tables.html Could you share a code snippet of what you are using, in order to see what can be done? |
I used this as a starting point and added some functionality to it, the most important of them being to compute the height of each row. I've done this similar to how the height of a multicell is calculated within the library. Here is the complete code though it is quite long. from fpdf import FPDF
# https://github.com/bvalgard/create-pdf-with-python-fpdf2/blob/main/create_table_fpdf2.py
class PdfTable(FPDF):
def create_table(self, table_data, title='', data_size = 10, header_size=10, title_size=12, align_data='L', align_header='L', cell_width='even', x_start='x_default',emphasize_data=[], emphasize_style=None,emphasize_color=(0,0,0), border=False, footer=[], align_footer="R", footer_style="B"):
"""
table_data:
list of lists with first element being list of headers
title:
(Optional) title of table (optional)
data_size:
the font size of table data
title_size:
the font size fo the title of the table
align_data:
align table data
L = left align
C = center align
R = right align
align_header:
align table data
L = left align
C = center align
R = right align
cell_width:
even: evenly distribute cell/column width
uneven: base cell size on lenght of cell/column items
int: int value for width of each cell/column
list of ints: list equal to number of columns with the widht of each cell / column
x_start:
where the left edge of table should start
emphasize_data:
which data elements are to be emphasized - pass as list
emphasize_style: the font style you want emphaized data to take
emphasize_color: emphasize color (if other than black)
"""
default_style = self.font_style
if emphasize_style == None:
emphasize_style = default_style
line_height = self.font_size*1.2
header = table_data[0]
data = table_data[1:]
self.set_font(size=title_size)
def _char_width(font, char):
cw = font["cw"]
try:
width = cw[char]
except IndexError:
width = font["desc"].get("MissingWidth") or 500
if width == 65535:
width = 0
return width
# Get Width of Columns
def get_col_widths():
col_width = cell_width
if col_width == 'even':
col_width = self.epw / len(table_data[0]) - 1 # distribute content evenly # epw = effective page width (width of page not including margins)
elif col_width == 'uneven':
col_widths = []
# searching through columns for largest sized cell (not rows but cols)
for col in range(len(table_data[0])): # for every row
longest = 0
for row in range(len(table_data)):
cell_value = str(table_data[row][col])
value_length = self.get_string_width(cell_value)
if value_length > longest:
longest = value_length
col_widths.append(longest + 4) # add 4 for padding
col_width = col_widths
### compare columns
elif isinstance(cell_width, list):
col_width = cell_width # TODO: convert all items in list to int
else:
# TODO: Add try catch
col_width = int(col_width)
return col_width
# get the height of a multi_cell
def get_row_height(txt, index=0):
# Calculate text length
txt = self.normalize_text(txt)
s = txt.replace("\r", "")
normalized_string_length = len(s)
if normalized_string_length > 0 and s[-1] == "\n":
normalized_string_length -= 1
i = 0
line_len = 0
lines_count = 1
while i < normalized_string_length:
# Get next character
c = s[i]
if isinstance(get_col_widths(), list):
max_width = (get_col_widths()[index] - 2 * self.c_margin) * 1000 / self.font_size
else:
max_width = (get_col_widths() - 2 * self.c_margin) * 1000 / self.font_size
if self.unifontsubset:
line_len += self.get_string_width(c, True) / self.font_size * 1000
else:
line_len += _char_width(self.current_font, c)
# Explicit line break
if c == "\n":
line_len = 0
lines_count += 1
# Automatic line break
if line_len > max_width:
line_len = 0
lines_count +=1
i = i + 1
return (lines_count+1) * line_height
def get_title_multicell():
self.multi_cell(0, line_height, title, border=0, align='j', ln=3)
self.ln(line_height) # move cursor back to the left margin
def get_emphazised_datacell(datum, col_width, cell_height, align=align_data, border=border):
self.set_text_color(*emphasize_color)
self.set_font(style=emphasize_style)
if border == True:
self.multi_cell(col_width, cell_height, datum, border=1, align=align, ln=3, max_line_height=line_height)
elif border == False:
self.multi_cell(col_width, cell_height, datum, border=0, align=align, ln=3, max_line_height=line_height)
self.set_text_color(0,0,0)
self.set_font(style=default_style)
def get_regular_datacell(datum, col_width, cell_height, align=align_data, border=border):
if border == True:
self.multi_cell(col_width, cell_height, datum, border=1, align=align, ln=3, max_line_height=line_height)
elif border == False:
self.multi_cell(col_width, cell_height, datum, border=0, align=align, ln=3,max_line_height=line_height)
def get_x_start():
# Get starting position of x
# Determin width of table to get x starting point for centred table
if x_start == 'C':
table_width = 0
if isinstance(col_width, list):
for width in col_width:
table_width += width
else: # need to multiply cell width by number of cells to get table width
table_width = col_width * len(table_data[0])
# Get x start by subtracting table width from pdf width and divide by 2 (margins)
margin_width = self.w - table_width
# TODO: Check if table_width is larger than pdf width
center_table = margin_width / 2 # only want width of left margin not both
x_start_new = center_table
self.set_x(x_start)
elif isinstance(x_start, int):
self.set_x(x_start)
elif x_start == 'x_default':
x_start_new = self.set_x(self.l_margin)
return x_start_new
def get_cell_height(row):
cell_height = line_height
for i, text in enumerate(row):
new_height = get_row_height(text, i)
if new_height > cell_height:
cell_height = new_height
return cell_height
col_width = get_col_widths()
# TABLE CREATION #
x_start = get_x_start()
# add title
if title != '':
get_title_multicell()
# add header
y1 = self.get_y()
if x_start:
x_left = x_start
else:
x_left = self.get_x()
x_right = self.epw + x_left
self.set_font(size=header_size)
if not isinstance(col_width, list):
if x_start:
self.set_x(x_start)
cell_height = get_cell_height(header)
for datum in header:
get_regular_datacell(datum, col_width, cell_height, align=align_header)
x_right = self.get_x()
self.ln(cell_height) # move cursor back to the left margin
# add line beneeth header
y2 = self.get_y()
self.line(x_left,y1,x_right,y1)
self.line(x_left,y2,x_right,y2)
# add data
self.set_font(size=data_size)
for row in data:
if x_start: # not sure if I need this
self.set_x(x_start)
cell_height = get_cell_height(row)
for datum in row:
if datum in emphasize_data:
get_emphazised_datacell(datum, col_width, cell_height)
else:
get_regular_datacell(datum, col_width, cell_height)
self.ln(cell_height) # move cursor back to the left margin
else:
if x_start:
self.set_x(x_start)
cell_height = get_cell_height(header)
for i, datum in enumerate(header):
get_regular_datacell(datum, col_width[i], cell_height, align=align_header)
x_right = self.get_x()
self.ln(cell_height) # move cursor back to the left margin
# add line beneeth header
y2 = self.get_y()
self.line(x_left,y1,x_right,y1)
self.line(x_left,y2,x_right,y2)
# add data
self.set_font(size=data_size)
for i, row in enumerate(data):
if x_start:
self.set_x(x_start)
cell_height = get_cell_height(row)
for i, datum in enumerate(row):
if not isinstance(datum, str):
datum = str(datum)
adjusted_col_width = col_width[i]
if datum in emphasize_data:
get_emphazised_datacell(datum, adjusted_col_width, cell_height)
else:
get_regular_datacell(datum, adjusted_col_width, cell_height)
self.ln(cell_height) # move cursor back to the left margin
y3 = self.get_y()
self.line(x_left,y3,x_right,y3)
# footer
# add data
self.set_font(style=footer_style, size=data_size)
for row in footer:
if isinstance(cell_width, list):
if x_start:
self.set_x(x_start)
cell_height = get_cell_height(row)
for i, datum in enumerate(row):
if not isinstance(datum, str):
datum = str(datum)
adjusted_col_width = col_width[i]
if datum in emphasize_data:
get_emphazised_datacell(datum, adjusted_col_width, cell_height, align=align_footer, border=False)
else:
get_regular_datacell(datum, adjusted_col_width, cell_height, align=align_footer, border=False)
self.ln(cell_height) # move cursor back to the left margin
else:
if x_start: # not sure if I need this
self.set_x(x_start)
cell_height = get_cell_height(row)
for datum in row:
if datum in emphasize_data:
get_emphazised_datacell(datum, col_width, cell_height, align=align_footer)
else:
get_regular_datacell(datum, col_width, cell_height, align=align_footer)
self.ln(cell_height) # move cursor back to the left margin
y4 = self.get_y()
self.line(x_left,y4,x_right,y4) I then use the class like this: pdf = PdfTable()
pdf.create_table(data, border=True, align_data="L", data_size=9, emphasize_style="B", cell_width=[10, 15, 25, 25, 60, 20, 20], footer=self.footer) |
OK I see. Thanks for sharing your code. @bvalgard code is well crafted, but it is not part of fpdf2. You can try reaching him about this "vertical alignement" feature on bvalgard/create-pdf-with-python-fpdf2. Also, I'd be open to introducing a |
@Lucas-C pdf.multi_cell(col_width, cell_height, datum, border=1, align=align, ln=3, max_line_height=line_height) I See what I can do regarding the pr :) |
Yes, this seems a valid use case, and Do you mean that this parameter does not behave as it should? |
@Lucas-C You could add a |
Thanks for your explanation. I think I understand precisely the feature you want and why. |
For reference, @RubendeBruin is working on an implementation to provide vertical alignement for Reviews & comments on this PR are welcome! |
@johann-su |
Is there an option to change the vertical alignment of text inside a multicell from center to top or bottom?
I am using them to create a table and it would be very helpful to have the ability to change the vertical alignment of the text inside the table rows.
The text was updated successfully, but these errors were encountered: