├── fox_face.png ├── background_image.png ├── table_with_class.pdf ├── table_with_function.pdf ├── table.py ├── part_1.py ├── part_2.py ├── table_class.py ├── part_3.py ├── part_4.py ├── create_table_fpdf2.py ├── table_function.py ├── chp2.txt └── chp1.txt /fox_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvalgard/create-pdf-with-python-fpdf2/HEAD/fox_face.png -------------------------------------------------------------------------------- /background_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvalgard/create-pdf-with-python-fpdf2/HEAD/background_image.png -------------------------------------------------------------------------------- /table_with_class.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvalgard/create-pdf-with-python-fpdf2/HEAD/table_with_class.pdf -------------------------------------------------------------------------------- /table_with_function.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bvalgard/create-pdf-with-python-fpdf2/HEAD/table_with_function.pdf -------------------------------------------------------------------------------- /table.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF, HTMLMixin 2 | 3 | class PDF(FPDF, HTMLMixin): 4 | pass 5 | 6 | pdf = PDF() 7 | pdf.add_page() 8 | 9 | 10 | # specify font 11 | pdf.set_font('helvetica', '', 16) 12 | 13 | pdf.cell(0,0, 'A cell with some words') 14 | 15 | pdf.write_html(""" 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
IDName
1Alice
2Bob
34 | 35 | """) 36 | 37 | pdf.output("pdf_table.pdf") 38 | -------------------------------------------------------------------------------- /part_1.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | # create FPDF object 4 | # Layout ('P','L') 5 | # Unit ('mm', 'cm', 'in') 6 | # format ('A3', 'A4' (default), 'A5', 'Letter', 'Legal', (100,150)) 7 | pdf = FPDF('P', 'mm', 'Letter') 8 | 9 | # Add a page 10 | pdf.add_page() 11 | 12 | # specify font 13 | # fonts ('times', 'courier', 'helvetica', 'symbol', 'zpfdingbats') 14 | # 'B' (bold), 'U' (underline), 'I' (italics), '' (regular), combination (i.e., ('BU')) 15 | pdf.set_font('helvetica', 'BIU', 16) 16 | pdf.set_text_color(220,50,50) 17 | # Add text 18 | # w = width 19 | # h = height 20 | # txt = your text 21 | # ln (0 False; 1 True - move cursor down to next line) 22 | # border (0 False; 1 True - add border around cell) 23 | pdf.cell(120, 100, 'Hello World!', ln=True, border=True) 24 | 25 | pdf.set_font('times', '', 12) 26 | pdf.cell(80, 10, 'Good Bye World!') 27 | 28 | pdf.output('pdf_1.pdf') 29 | -------------------------------------------------------------------------------- /part_2.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | class PDF(FPDF): 4 | def header(self): 5 | # Logo 6 | self.image('fox_face.png', 10, 8, 25) 7 | # font 8 | self.set_font('helvetica', 'B', 20) 9 | # Padding 10 | self.cell(80) 11 | # Title 12 | self.cell(30, 10, 'Title', border=True, ln=1, align='C') 13 | # Line break 14 | self.ln(20) 15 | 16 | # Page footer 17 | def footer(self): 18 | # Set position of the footer 19 | self.set_y(-15) 20 | # set font 21 | self.set_font('helvetica', 'I', 8) 22 | # Page number 23 | self.cell(0, 10, f'Page {self.page_no()}/{{nb}}', align='C') 24 | 25 | # Create a PDF object 26 | pdf = PDF('P', 'mm', 'Letter') 27 | 28 | # get total page numbers 29 | pdf.alias_nb_pages() 30 | 31 | # Set auto page break 32 | pdf.set_auto_page_break(auto = True, margin = 15) 33 | 34 | #Add Page 35 | pdf.add_page() 36 | 37 | # specify font 38 | pdf.set_font('helvetica', 'BIU', 16) 39 | 40 | pdf.set_font('times', '', 12) 41 | 42 | for i in range(1, 41): 43 | pdf.cell(0, 10, f'This is line {i} :D', ln=1) 44 | 45 | pdf.output('pdf_2.pdf') 46 | -------------------------------------------------------------------------------- /table_class.py: -------------------------------------------------------------------------------- 1 | from create_table_fpdf2 import PDF 2 | 3 | 4 | data = [ 5 | ["First name", "Last name", "Age", "City",], # 'testing','size'], 6 | ["Jules", "Smith", "34", "San Juan",], # 'testing','size'], 7 | ["Mary", "Ramos", "45", "Orlando",], # 'testing','size'], 8 | ["Carlson", "Banks", "19", "Los Angeles",], # 'testing','size'], 9 | ["Lucas", "Cimon", "31", "Saint-Mahturin-sur-Loire",], # 'testing','size'], 10 | ] 11 | 12 | data_as_dict = {"First name": ["Jules","Mary","Carlson","Lucas"], 13 | "Last name": ["Smith","Ramos","Banks","Cimon"], 14 | "Age": [34,'45','19','31'] 15 | } 16 | 17 | 18 | pdf = PDF() 19 | pdf.add_page() 20 | pdf.set_font("Times", size=10) 21 | 22 | pdf.create_table(table_data = data,title='I\'m the first title', cell_width='even') 23 | pdf.ln() 24 | 25 | pdf.create_table(table_data = data,title='I start at 25',cell_width='uneven',x_start=25) 26 | pdf.ln() 27 | 28 | pdf.create_table(table_data = data,title="I'm in the middle",cell_width=22,x_start='C') 29 | pdf.ln() 30 | 31 | pdf.create_table(table_data = data_as_dict,title='Is my text red',align_header='R', align_data='R', cell_width=[15,15,10,45,], x_start='C', emphasize_data=['45','Jules'], emphasize_style='BIU',emphasize_color=(255,0,0)) 32 | pdf.ln() 33 | 34 | 35 | pdf.output('table_class.pdf') -------------------------------------------------------------------------------- /part_3.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | title = '20,000 Leagues Under the Sea' 4 | 5 | class PDF(FPDF): 6 | def header(self): 7 | # font 8 | self.set_font('helvetica', 'B', 15) 9 | # Calculate width of title and position 10 | title_w = self.get_string_width(title) + 6 11 | doc_w = self.w 12 | self.set_x((doc_w - title_w) / 2) 13 | # colors of frame, background, and text 14 | self.set_draw_color(0, 80, 180) # border = blue 15 | self.set_fill_color(230, 230, 0) # background = yellow 16 | self.set_text_color(220, 50, 50) # text = red 17 | # Thickness of frame (border) 18 | self.set_line_width(1) 19 | # Title 20 | self.cell(title_w, 10, title, border=1, ln=1, align='C', fill=1) 21 | # Line break 22 | self.ln(10) 23 | 24 | # Page footer 25 | def footer(self): 26 | # Set position of the footer 27 | self.set_y(-15) 28 | # set font 29 | self.set_font('helvetica', 'I', 8) 30 | # Set font color grey 31 | self.set_text_color(169,169,169) 32 | # Page number 33 | self.cell(0, 10, f'Page {self.page_no()}', align='C') 34 | 35 | # Adding chapter title to start of each chapter 36 | def chapter_title(self, ch_num, ch_title): 37 | # set font 38 | self.set_font('helvetica', '', 12) 39 | # background color 40 | self.set_fill_color(200, 220, 255) 41 | # Chapter title 42 | chapter_title = f'Chapter {ch_num} : {ch_title}' 43 | self.cell(0, 5, chapter_title, ln=1, fill=1) 44 | # line break 45 | self.ln() 46 | 47 | # Chapter content 48 | def chapter_body(self, name): 49 | # read text file 50 | with open(name, 'rb') as fh: 51 | txt = fh.read().decode('latin-1') 52 | # set font 53 | self.set_font('times', '', 12) 54 | # insert text 55 | self.multi_cell(0, 5, txt) 56 | # line break 57 | self.ln() 58 | # end each chapter 59 | self.set_font('times', 'I', 12) 60 | self.cell(0, 5, 'END OF CHAPTER') 61 | 62 | def print_chapter(self, ch_num, ch_title, name): 63 | self.add_page() 64 | self.chapter_title(ch_num, ch_title) 65 | self.chapter_body(name) 66 | 67 | # Create a PDF object 68 | pdf = PDF('P', 'mm', 'Letter') 69 | 70 | # get total page numbers 71 | pdf.alias_nb_pages() 72 | 73 | # Set auto page break 74 | pdf.set_auto_page_break(auto = True, margin = 15) 75 | 76 | # Add Page 77 | pdf.add_page() 78 | 79 | 80 | pdf.print_chapter(1, 'A RUNAWAY REEF', 'chp1.txt') 81 | pdf.print_chapter(2, 'THE PROS AND CONS', 'chp2.txt') 82 | 83 | pdf.output('pdf_3.pdf') 84 | -------------------------------------------------------------------------------- /part_4.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | title = '20,000 Leagues Under the Sea' 4 | 5 | 6 | class PDF(FPDF): 7 | def header(self): 8 | # font 9 | self.set_font('helvetica', 'B', 15) 10 | # Calculate width of title and position 11 | title_w = self.get_string_width(title) + 6 12 | doc_w = self.w 13 | self.set_x((doc_w - title_w) / 2) 14 | # colors of frame, background, and text 15 | self.set_draw_color(0, 80, 180) # border = blue 16 | self.set_fill_color(230, 230, 0) # background = yellow 17 | self.set_text_color(220, 50, 50) # text = red 18 | # Thickness of frame (border) 19 | self.set_line_width(1) 20 | # Title 21 | self.cell(title_w, 10, title, border=1, ln=1, align='C', fill=1) 22 | # Line break 23 | self.ln(10) 24 | 25 | # Page footer 26 | def footer(self): 27 | # Set position of the footer 28 | self.set_y(-15) 29 | # set font 30 | self.set_font('helvetica', 'I', 8) 31 | # Set font color grey 32 | self.set_text_color(169,169,169) 33 | # Page number 34 | self.cell(0, 10, f'Page {self.page_no()}', align='C') 35 | 36 | # Adding chapter title to start of each chapter 37 | def chapter_title(self, ch_num, ch_title, link): 38 | # Set link location 39 | self.set_link(link) 40 | # set font 41 | self.set_font('helvetica', '', 12) 42 | # background color 43 | self.set_fill_color(200, 220, 255) 44 | # Chapter title 45 | chapter_title = f'Chapter {ch_num} : {ch_title}' 46 | self.cell(0, 5, chapter_title, ln=1, fill=1) 47 | # line break 48 | self.ln() 49 | 50 | # Chapter content 51 | def chapter_body(self, name): 52 | # read text file 53 | with open(name, 'rb') as fh: 54 | txt = fh.read().decode('latin-1') 55 | # set font 56 | self.set_font('times', '', 12) 57 | # insert text 58 | self.multi_cell(0, 5, txt) 59 | # line break 60 | self.ln() 61 | # end each chapter 62 | self.set_font('times', 'I', 12) 63 | self.cell(0, 5, 'END OF CHAPTER') 64 | 65 | def print_chapter(self, ch_num, ch_title, name, link): 66 | self.add_page() 67 | self.chapter_title(ch_num, ch_title, link) 68 | self.chapter_body(name) 69 | 70 | # Create a PDF object 71 | pdf = PDF('P', 'mm', 'Letter') 72 | 73 | # metadata 74 | pdf.set_title(title) 75 | pdf.set_author('Jules Verne') 76 | 77 | # Create Links 78 | website = 'http://www.gutenberg.org/cache/epub/164/pg164.txt' 79 | ch1_link = pdf.add_link() 80 | ch2_link = pdf.add_link() 81 | 82 | 83 | # Set auto page break 84 | pdf.set_auto_page_break(auto = True, margin = 15) 85 | 86 | # Add Page 87 | pdf.add_page() 88 | pdf.image('background_image.png', x = -0.5, w = pdf.w + 1) 89 | 90 | # Attach Links 91 | pdf.cell(0, 10, 'Text Source', ln = 1, link = website) 92 | pdf.cell(0, 10, 'Chapter 1', ln = 1, link = ch1_link) 93 | pdf.cell(0, 10, 'Chapter 2', ln = 1, link = ch2_link) 94 | 95 | 96 | 97 | pdf.print_chapter(1, 'A RUNAWAY REEF', 'chp1.txt', ch1_link) 98 | pdf.print_chapter(2, 'THE PROS AND CONS', 'chp2.txt', ch2_link) 99 | 100 | pdf.output('pdf_4.pdf') 101 | -------------------------------------------------------------------------------- /create_table_fpdf2.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | class PDF(FPDF): 4 | def create_table(self, table_data, title='', data_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)): 5 | """ 6 | table_data: 7 | list of lists with first element being list of headers 8 | title: 9 | (Optional) title of table (optional) 10 | data_size: 11 | the font size of table data 12 | title_size: 13 | the font size fo the title of the table 14 | align_data: 15 | align table data 16 | L = left align 17 | C = center align 18 | R = right align 19 | align_header: 20 | align table data 21 | L = left align 22 | C = center align 23 | R = right align 24 | cell_width: 25 | even: evenly distribute cell/column width 26 | uneven: base cell size on lenght of cell/column items 27 | int: int value for width of each cell/column 28 | list of ints: list equal to number of columns with the widht of each cell / column 29 | x_start: 30 | where the left edge of table should start 31 | emphasize_data: 32 | which data elements are to be emphasized - pass as list 33 | emphasize_style: the font style you want emphaized data to take 34 | emphasize_color: emphasize color (if other than black) 35 | 36 | """ 37 | default_style = self.font_style 38 | if emphasize_style == None: 39 | emphasize_style = default_style 40 | # default_font = self.font_family 41 | # default_size = self.font_size_pt 42 | # default_style = self.font_style 43 | # default_color = self.color # This does not work 44 | 45 | # Get Width of Columns 46 | def get_col_widths(): 47 | col_width = cell_width 48 | if col_width == 'even': 49 | col_width = self.epw / len(data[0]) - 1 # distribute content evenly # epw = effective page width (width of page not including margins) 50 | elif col_width == 'uneven': 51 | col_widths = [] 52 | 53 | # searching through columns for largest sized cell (not rows but cols) 54 | for col in range(len(table_data[0])): # for every row 55 | longest = 0 56 | for row in range(len(table_data)): 57 | cell_value = str(table_data[row][col]) 58 | value_length = self.get_string_width(cell_value) 59 | if value_length > longest: 60 | longest = value_length 61 | col_widths.append(longest + 4) # add 4 for padding 62 | col_width = col_widths 63 | 64 | 65 | 66 | ### compare columns 67 | 68 | elif isinstance(cell_width, list): 69 | col_width = cell_width # TODO: convert all items in list to int 70 | else: 71 | # TODO: Add try catch 72 | col_width = int(col_width) 73 | return col_width 74 | 75 | # Convert dict to lol 76 | # Why? because i built it with lol first and added dict func after 77 | # Is there performance differences? 78 | if isinstance(table_data, dict): 79 | header = [key for key in table_data] 80 | data = [] 81 | for key in table_data: 82 | value = table_data[key] 83 | data.append(value) 84 | # need to zip so data is in correct format (first, second, third --> not first, first, first) 85 | data = [list(a) for a in zip(*data)] 86 | 87 | else: 88 | header = table_data[0] 89 | data = table_data[1:] 90 | 91 | line_height = self.font_size * 2.5 92 | 93 | col_width = get_col_widths() 94 | self.set_font(size=title_size) 95 | 96 | # Get starting position of x 97 | # Determin width of table to get x starting point for centred table 98 | if x_start == 'C': 99 | table_width = 0 100 | if isinstance(col_width, list): 101 | for width in col_width: 102 | table_width += width 103 | else: # need to multiply cell width by number of cells to get table width 104 | table_width = col_width * len(table_data[0]) 105 | # Get x start by subtracting table width from pdf width and divide by 2 (margins) 106 | margin_width = self.w - table_width 107 | # TODO: Check if table_width is larger than pdf width 108 | 109 | center_table = margin_width / 2 # only want width of left margin not both 110 | x_start = center_table 111 | self.set_x(x_start) 112 | elif isinstance(x_start, int): 113 | self.set_x(x_start) 114 | elif x_start == 'x_default': 115 | x_start = self.set_x(self.l_margin) 116 | 117 | 118 | # TABLE CREATION # 119 | 120 | # add title 121 | if title != '': 122 | self.multi_cell(0, line_height, title, border=0, align='j', ln=3, max_line_height=self.font_size) 123 | self.ln(line_height) # move cursor back to the left margin 124 | 125 | self.set_font(size=data_size) 126 | # add header 127 | y1 = self.get_y() 128 | if x_start: 129 | x_left = x_start 130 | else: 131 | x_left = self.get_x() 132 | x_right = self.epw + x_left 133 | if not isinstance(col_width, list): 134 | if x_start: 135 | self.set_x(x_start) 136 | for datum in header: 137 | self.multi_cell(col_width, line_height, datum, border=0, align=align_header, ln=3, max_line_height=self.font_size) 138 | x_right = self.get_x() 139 | self.ln(line_height) # move cursor back to the left margin 140 | y2 = self.get_y() 141 | self.line(x_left,y1,x_right,y1) 142 | self.line(x_left,y2,x_right,y2) 143 | 144 | for row in data: 145 | if x_start: # not sure if I need this 146 | self.set_x(x_start) 147 | for datum in row: 148 | if datum in emphasize_data: 149 | self.set_text_color(*emphasize_color) 150 | self.set_font(style=emphasize_style) 151 | self.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=self.font_size) 152 | self.set_text_color(0,0,0) 153 | self.set_font(style=default_style) 154 | else: 155 | self.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=self.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named self 156 | self.ln(line_height) # move cursor back to the left margin 157 | 158 | else: 159 | if x_start: 160 | self.set_x(x_start) 161 | for i in range(len(header)): 162 | datum = header[i] 163 | self.multi_cell(col_width[i], line_height, datum, border=0, align=align_header, ln=3, max_line_height=self.font_size) 164 | x_right = self.get_x() 165 | self.ln(line_height) # move cursor back to the left margin 166 | y2 = self.get_y() 167 | self.line(x_left,y1,x_right,y1) 168 | self.line(x_left,y2,x_right,y2) 169 | 170 | 171 | for i in range(len(data)): 172 | if x_start: 173 | self.set_x(x_start) 174 | row = data[i] 175 | for i in range(len(row)): 176 | datum = row[i] 177 | if not isinstance(datum, str): 178 | datum = str(datum) 179 | adjusted_col_width = col_width[i] 180 | if datum in emphasize_data: 181 | self.set_text_color(*emphasize_color) 182 | self.set_font(style=emphasize_style) 183 | self.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=self.font_size) 184 | self.set_text_color(0,0,0) 185 | self.set_font(style=default_style) 186 | else: 187 | self.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=self.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named self 188 | self.ln(line_height) # move cursor back to the left margin 189 | y3 = self.get_y() 190 | self.line(x_left,y3,x_right,y3) -------------------------------------------------------------------------------- /table_function.py: -------------------------------------------------------------------------------- 1 | from fpdf import FPDF 2 | 3 | def create_table(table_data, title='', data_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)): 4 | """ 5 | table_data: 6 | list of lists with first element being list of headers 7 | title: 8 | (Optional) title of table (optional) 9 | data_size: 10 | the font size of table data 11 | title_size: 12 | the font size fo the title of the table 13 | align_data: 14 | align table data 15 | L = left align 16 | C = center align 17 | R = right align 18 | align_header: 19 | align table data 20 | L = left align 21 | C = center align 22 | R = right align 23 | cell_width: 24 | even: evenly distribute cell/column width 25 | uneven: base cell size on lenght of cell/column items 26 | int: int value for width of each cell/column 27 | list of ints: list equal to number of columns with the widht of each cell / column 28 | x_start: 29 | where the left edge of table should start 30 | emphasize_data: 31 | which data elements are to be emphasized - pass as list 32 | emphasize_style: the font style you want emphaized data to take 33 | emphasize_color: emphasize color (if other than black) 34 | 35 | """ 36 | default_style = pdf.font_style 37 | if emphasize_style == None: 38 | emphasize_style = default_style 39 | # default_font = pdf.font_family 40 | # default_size = pdf.font_size_pt 41 | # default_style = pdf.font_style 42 | # default_color = pdf.color # This does not work 43 | 44 | # Get Width of Columns 45 | def get_col_widths(): 46 | col_width = cell_width 47 | if col_width == 'even': 48 | col_width = pdf.epw / len(data[0]) - 1 # distribute content evenly # epw = effective page width (width of page not including margins) 49 | elif col_width == 'uneven': 50 | col_widths = [] 51 | 52 | # searching through columns for largest sized cell (not rows but cols) 53 | for col in range(len(table_data[0])): # for every row 54 | longest = 0 55 | for row in range(len(table_data)): 56 | cell_value = str(table_data[row][col]) 57 | value_length = pdf.get_string_width(cell_value) 58 | if value_length > longest: 59 | longest = value_length 60 | col_widths.append(longest + 4) # add 4 for padding 61 | col_width = col_widths 62 | 63 | 64 | 65 | ### compare columns 66 | 67 | elif isinstance(cell_width, list): 68 | col_width = cell_width # TODO: convert all items in list to int 69 | else: 70 | # TODO: Add try catch 71 | col_width = int(col_width) 72 | return col_width 73 | 74 | # Convert dict to lol 75 | # Why? because i built it with lol first and added dict func after 76 | # Is there performance differences? 77 | if isinstance(table_data, dict): 78 | header = [key for key in table_data] 79 | data = [] 80 | for key in table_data: 81 | value = table_data[key] 82 | data.append(value) 83 | # need to zip so data is in correct format (first, second, third --> not first, first, first) 84 | data = [list(a) for a in zip(*data)] 85 | 86 | else: 87 | header = table_data[0] 88 | data = table_data[1:] 89 | 90 | line_height = pdf.font_size * 2.5 91 | 92 | col_width = get_col_widths() 93 | pdf.set_font(size=title_size) 94 | 95 | # Get starting position of x 96 | # Determin width of table to get x starting point for centred table 97 | if x_start == 'C': 98 | table_width = 0 99 | if isinstance(col_width, list): 100 | for width in col_width: 101 | table_width += width 102 | else: # need to multiply cell width by number of cells to get table width 103 | table_width = col_width * len(table_data[0]) 104 | # Get x start by subtracting table width from pdf width and divide by 2 (margins) 105 | margin_width = pdf.w - table_width 106 | # TODO: Check if table_width is larger than pdf width 107 | 108 | center_table = margin_width / 2 # only want width of left margin not both 109 | x_start = center_table 110 | pdf.set_x(x_start) 111 | elif isinstance(x_start, int): 112 | pdf.set_x(x_start) 113 | elif x_start == 'x_default': 114 | x_start = pdf.set_x(pdf.l_margin) 115 | 116 | 117 | # TABLE CREATION # 118 | 119 | # add title 120 | if title != '': 121 | pdf.multi_cell(0, line_height, title, border=0, align='j', ln=3, max_line_height=pdf.font_size) 122 | pdf.ln(line_height) # move cursor back to the left margin 123 | 124 | pdf.set_font(size=data_size) 125 | # add header 126 | y1 = pdf.get_y() 127 | if x_start: 128 | x_left = x_start 129 | else: 130 | x_left = pdf.get_x() 131 | x_right = pdf.epw + x_left 132 | if not isinstance(col_width, list): 133 | if x_start: 134 | pdf.set_x(x_start) 135 | for datum in header: 136 | pdf.multi_cell(col_width, line_height, datum, border=0, align=align_header, ln=3, max_line_height=pdf.font_size) 137 | x_right = pdf.get_x() 138 | pdf.ln(line_height) # move cursor back to the left margin 139 | y2 = pdf.get_y() 140 | pdf.line(x_left,y1,x_right,y1) 141 | pdf.line(x_left,y2,x_right,y2) 142 | 143 | for row in data: 144 | if x_start: # not sure if I need this 145 | pdf.set_x(x_start) 146 | for datum in row: 147 | if datum in emphasize_data: 148 | pdf.set_text_color(*emphasize_color) 149 | pdf.set_font(style=emphasize_style) 150 | pdf.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) 151 | pdf.set_text_color(0,0,0) 152 | pdf.set_font(style=default_style) 153 | else: 154 | pdf.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named pdf 155 | pdf.ln(line_height) # move cursor back to the left margin 156 | 157 | else: 158 | if x_start: 159 | pdf.set_x(x_start) 160 | for i in range(len(header)): 161 | datum = header[i] 162 | pdf.multi_cell(col_width[i], line_height, datum, border=0, align=align_header, ln=3, max_line_height=pdf.font_size) 163 | x_right = pdf.get_x() 164 | pdf.ln(line_height) # move cursor back to the left margin 165 | y2 = pdf.get_y() 166 | pdf.line(x_left,y1,x_right,y1) 167 | pdf.line(x_left,y2,x_right,y2) 168 | 169 | 170 | for i in range(len(data)): 171 | if x_start: 172 | pdf.set_x(x_start) 173 | row = data[i] 174 | for i in range(len(row)): 175 | datum = row[i] 176 | if not isinstance(datum, str): 177 | datum = str(datum) 178 | adjusted_col_width = col_width[i] 179 | if datum in emphasize_data: 180 | pdf.set_text_color(*emphasize_color) 181 | pdf.set_font(style=emphasize_style) 182 | pdf.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) 183 | pdf.set_text_color(0,0,0) 184 | pdf.set_font(style=default_style) 185 | else: 186 | pdf.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named pdf 187 | pdf.ln(line_height) # move cursor back to the left margin 188 | y3 = pdf.get_y() 189 | pdf.line(x_left,y3,x_right,y3) 190 | 191 | 192 | 193 | data = [ 194 | ["First name", "Last name", "Age", "City",], # 'testing','size'], 195 | ["Jules", "Smith", "34", "San Juan",], # 'testing','size'], 196 | ["Mary", "Ramos", "45", "Orlando",], # 'testing','size'], 197 | ["Carlson", "Banks", "19", "Los Angeles",], # 'testing','size'], 198 | ["Lucas", "Cimon", "31", "Saint-Mahturin-sur-Loire",], # 'testing','size'], 199 | ] 200 | 201 | data_as_dict = {"First name": ["Jules","Mary","Carlson","Lucas"], 202 | "Last name": ["Smith","Ramos","Banks","Cimon"], 203 | "Age": [34,'45','19','31'] 204 | } 205 | 206 | 207 | pdf = FPDF() 208 | pdf.add_page() 209 | pdf.set_font("Times", size=10) 210 | 211 | create_table(table_data = data,title='I\'m the first title', cell_width='even') 212 | pdf.ln() 213 | create_table(table_data = data,title='my title is the best title',cell_width='uneven',x_start=25) 214 | pdf.ln() 215 | create_table(table_data = data,title='my title is the best title',cell_width=22,x_start=50) 216 | pdf.ln() 217 | 218 | create_table(table_data = data_as_dict,title='Is my text red',align_header='R', align_data='R', cell_width=[15,15,10,45,], x_start='C', emphasize_data=['45','Jules'], emphasize_style='BIU',emphasize_color=(255,0,0)) 219 | pdf.ln() 220 | 221 | 222 | # create_table(table_data = data_as_dict,align_header='R', align_data='R', cell_width=[15,15,10,45,], x_start='C') 223 | 224 | 225 | pdf.output('table_function.pdf') 226 | 227 | # Need to create obejct as pdf -------------------------------------------------------------------------------- /chp2.txt: -------------------------------------------------------------------------------- 1 | At the period when these events took place, I had just returned from a 2 | scientific research in the disagreeable territory of Nebraska, in the 3 | United States. In virtue of my office as Assistant Professor in the 4 | Museum of Natural History in Paris, the French Government had attached 5 | me to that expedition. After six months in Nebraska, I arrived in New 6 | York towards the end of March, laden with a precious collection. My 7 | departure for France was fixed for the first days in May. Meanwhile I 8 | was occupying myself in classifying my mineralogical, botanical, and 9 | zoological riches, when the accident happened to the Scotia. 10 | 11 | I was perfectly up in the subject which was the question of the day. 12 | How could I be otherwise? I had read and reread all the American and 13 | European papers without being any nearer a conclusion. This mystery 14 | puzzled me. Under the impossibility of forming an opinion, I jumped 15 | from one extreme to the other. That there really was something could 16 | not be doubted, and the incredulous were invited to put their finger on 17 | the wound of the Scotia. 18 | 19 | On my arrival at New York the question was at its height. The theory 20 | of the floating island, and the unapproachable sandbank, supported by 21 | minds little competent to form a judgment, was abandoned. And, indeed, 22 | unless this shoal had a machine in its stomach, how could it change its 23 | position with such astonishing rapidity? 24 | 25 | From the same cause, the idea of a floating hull of an enormous wreck 26 | was given up. 27 | 28 | There remained, then, only two possible solutions of the question, 29 | which created two distinct parties: on one side, those who were for a 30 | monster of colossal strength; on the other, those who were for a 31 | submarine vessel of enormous motive power. 32 | 33 | But this last theory, plausible as it was, could not stand against 34 | inquiries made in both worlds. That a private gentleman should have 35 | such a machine at his command was not likely. Where, when, and how was 36 | it built? and how could its construction have been kept secret? 37 | Certainly a Government might possess such a destructive machine. And 38 | in these disastrous times, when the ingenuity of man has multiplied the 39 | power of weapons of war, it was possible that, without the knowledge of 40 | others, a State might try to work such a formidable engine. 41 | 42 | But the idea of a war machine fell before the declaration of 43 | Governments. As public interest was in question, and transatlantic 44 | communications suffered, their veracity could not be doubted. But how 45 | admit that the construction of this submarine boat had escaped the 46 | public eye? For a private gentleman to keep the secret under such 47 | circumstances would be very difficult, and for a State whose every act 48 | is persistently watched by powerful rivals, certainly impossible. 49 | 50 | Upon my arrival in New York several persons did me the honour of 51 | consulting me on the phenomenon in question. I had published in France 52 | a work in quarto, in two volumes, entitled Mysteries of the Great 53 | Submarine Grounds. This book, highly approved of in the learned world, 54 | gained for me a special reputation in this rather obscure branch of 55 | Natural History. My advice was asked. As long as I could deny the 56 | reality of the fact, I confined myself to a decided negative. But 57 | soon, finding myself driven into a corner, I was obliged to explain 58 | myself point by point. I discussed the question in all its forms, 59 | politically and scientifically; and I give here an extract from a 60 | carefully-studied article which I published in the number of the 30th 61 | of April. It ran as follows: 62 | 63 | "After examining one by one the different theories, rejecting all other 64 | suggestions, it becomes necessary to admit the existence of a marine 65 | animal of enormous power. 66 | 67 | "The great depths of the ocean are entirely unknown to us. Soundings 68 | cannot reach them. What passes in those remote depths--what beings 69 | live, or can live, twelve or fifteen miles beneath the surface of the 70 | waters--what is the organisation of these animals, we can scarcely 71 | conjecture. However, the solution of the problem submitted to me may 72 | modify the form of the dilemma. Either we do know all the varieties of 73 | beings which people our planet, or we do not. If we do NOT know them 74 | all--if Nature has still secrets in the deeps for us, nothing is more 75 | conformable to reason than to admit the existence of fishes, or 76 | cetaceans of other kinds, or even of new species, of an organisation 77 | formed to inhabit the strata inaccessible to soundings, and which an 78 | accident of some sort has brought at long intervals to the upper level 79 | of the ocean. 80 | 81 | "If, on the contrary, we DO know all living kinds, we must necessarily 82 | seek for the animal in question amongst those marine beings already 83 | classed; and, in that case, I should be disposed to admit the existence 84 | of a gigantic narwhal. 85 | 86 | "The common narwhal, or unicorn of the sea, often attains a length of 87 | sixty feet. Increase its size fivefold or tenfold, give it strength 88 | proportionate to its size, lengthen its destructive weapons, and you 89 | obtain the animal required. It will have the proportions determined by 90 | the officers of the Shannon, the instrument required by the perforation 91 | of the Scotia, and the power necessary to pierce the hull of the 92 | steamer. 93 | 94 | "Indeed, the narwhal is armed with a sort of ivory sword, a halberd, 95 | according to the expression of certain naturalists. The principal tusk 96 | has the hardness of steel. Some of these tusks have been found buried 97 | in the bodies of whales, which the unicorn always attacks with success. 98 | Others have been drawn out, not without trouble, from the bottoms of 99 | ships, which they had pierced through and through, as a gimlet pierces 100 | a barrel. The Museum of the Faculty of Medicine of Paris possesses one 101 | of these defensive weapons, two yards and a quarter in length, and 102 | fifteen inches in diameter at the base. 103 | 104 | "Very well! suppose this weapon to be six times stronger and the animal 105 | ten times more powerful; launch it at the rate of twenty miles an hour, 106 | and you obtain a shock capable of producing the catastrophe required. 107 | Until further information, therefore, I shall maintain it to be a 108 | sea-unicorn of colossal dimensions, armed not with a halberd, but with 109 | a real spur, as the armoured frigates, or the `rams' of war, whose 110 | massiveness and motive power it would possess at the same time. Thus 111 | may this puzzling phenomenon be explained, unless there be something 112 | over and above all that one has ever conjectured, seen, perceived, or 113 | experienced; which is just within the bounds of possibility." 114 | 115 | These last words were cowardly on my part; but, up to a certain point, 116 | I wished to shelter my dignity as professor, and not give too much 117 | cause for laughter to the Americans, who laugh well when they do laugh. 118 | I reserved for myself a way of escape. In effect, however, I admitted 119 | the existence of the "monster." My article was warmly discussed, which 120 | procured it a high reputation. It rallied round it a certain number of 121 | partisans. The solution it proposed gave, at least, full liberty to 122 | the imagination. The human mind delights in grand conceptions of 123 | supernatural beings. And the sea is precisely their best vehicle, the 124 | only medium through which these giants (against which terrestrial 125 | animals, such as elephants or rhinoceroses, are as nothing) can be 126 | produced or developed. 127 | 128 | The industrial and commercial papers treated the question chiefly from 129 | this point of view. The Shipping and Mercantile Gazette, the Lloyd's 130 | List, the Packet-Boat, and the Maritime and Colonial Review, all papers 131 | devoted to insurance companies which threatened to raise their rates of 132 | premium, were unanimous on this point. Public opinion had been 133 | pronounced. The United States were the first in the field; and in New 134 | York they made preparations for an expedition destined to pursue this 135 | narwhal. A frigate of great speed, the Abraham Lincoln, was put in 136 | commission as soon as possible. The arsenals were opened to Commander 137 | Farragut, who hastened the arming of his frigate; but, as it always 138 | happens, the moment it was decided to pursue the monster, the monster 139 | did not appear. For two months no one heard it spoken of. No ship met 140 | with it. It seemed as if this unicorn knew of the plots weaving around 141 | it. It had been so much talked of, even through the Atlantic cable, 142 | that jesters pretended that this slender fly had stopped a telegram on 143 | its passage and was making the most of it. 144 | 145 | So when the frigate had been armed for a long campaign, and provided 146 | with formidable fishing apparatus, no one could tell what course to 147 | pursue. Impatience grew apace, when, on the 2nd of July, they learned 148 | that a steamer of the line of San Francisco, from California to 149 | Shanghai, had seen the animal three weeks before in the North Pacific 150 | Ocean. The excitement caused by this news was extreme. The ship was 151 | revictualled and well stocked with coal. 152 | 153 | Three hours before the Abraham Lincoln left Brooklyn pier, I received a 154 | letter worded as follows: 155 | 156 | To M. ARONNAX, Professor in the Museum of Paris, Fifth Avenue Hotel, 157 | New York. 158 | 159 | SIR,--If you will consent to join the Abraham Lincoln in this 160 | expedition, the Government of the United States will with pleasure see 161 | France represented in the enterprise. Commander Farragut has a cabin 162 | at your disposal. 163 | 164 | Very cordially yours, J.B. HOBSON, Secretary of Marine. -------------------------------------------------------------------------------- /chp1.txt: -------------------------------------------------------------------------------- 1 | The year 1866 was signalised by a remarkable incident, a mysterious and 2 | puzzling phenomenon, which doubtless no one has yet forgotten. Not to 3 | mention rumours which agitated the maritime population and excited the 4 | public mind, even in the interior of continents, seafaring men were 5 | particularly excited. Merchants, common sailors, captains of vessels, 6 | skippers, both of Europe and America, naval officers of all countries, 7 | and the Governments of several States on the two continents, were 8 | deeply interested in the matter. 9 | 10 | For some time past vessels had been met by "an enormous thing," a long 11 | object, spindle-shaped, occasionally phosphorescent, and infinitely 12 | larger and more rapid in its movements than a whale. 13 | 14 | The facts relating to this apparition (entered in various log-books) 15 | agreed in most respects as to the shape of the object or creature in 16 | question, the untiring rapidity of its movements, its surprising power 17 | of locomotion, and the peculiar life with which it seemed endowed. If 18 | it was a whale, it surpassed in size all those hitherto classified in 19 | science. Taking into consideration the mean of observations made at 20 | divers times--rejecting the timid estimate of those who assigned to 21 | this object a length of two hundred feet, equally with the exaggerated 22 | opinions which set it down as a mile in width and three in length--we 23 | might fairly conclude that this mysterious being surpassed greatly all 24 | dimensions admitted by the learned ones of the day, if it existed at 25 | all. And that it DID exist was an undeniable fact; and, with that 26 | tendency which disposes the human mind in favour of the marvellous, we 27 | can understand the excitement produced in the entire world by this 28 | supernatural apparition. As to classing it in the list of fables, the 29 | idea was out of the question. 30 | 31 | On the 20th of July, 1866, the steamer Governor Higginson, of the 32 | Calcutta and Burnach Steam Navigation Company, had met this moving mass 33 | five miles off the east coast of Australia. Captain Baker thought at 34 | first that he was in the presence of an unknown sandbank; he even 35 | prepared to determine its exact position when two columns of water, 36 | projected by the mysterious object, shot with a hissing noise a hundred 37 | and fifty feet up into the air. Now, unless the sandbank had been 38 | submitted to the intermittent eruption of a geyser, the Governor 39 | Higginson had to do neither more nor less than with an aquatic mammal, 40 | unknown till then, which threw up from its blow-holes columns of water 41 | mixed with air and vapour. 42 | 43 | Similar facts were observed on the 23rd of July in the same year, in 44 | the Pacific Ocean, by the Columbus, of the West India and Pacific Steam 45 | Navigation Company. But this extraordinary creature could transport 46 | itself from one place to another with surprising velocity; as, in an 47 | interval of three days, the Governor Higginson and the Columbus had 48 | observed it at two different points of the chart, separated by a 49 | distance of more than seven hundred nautical leagues. 50 | 51 | Fifteen days later, two thousand miles farther off, the Helvetia, of 52 | the Compagnie-Nationale, and the Shannon, of the Royal Mail Steamship 53 | Company, sailing to windward in that portion of the Atlantic lying 54 | between the United States and Europe, respectively signalled the 55 | monster to each other in 42° 15' N. lat. and 60° 35' W. long. 56 | In these simultaneous observations they thought themselves justified in 57 | estimating the minimum length of the mammal at more than three hundred 58 | and fifty feet, as the Shannon and Helvetia were of smaller dimensions 59 | than it, though they measured three hundred feet over all. 60 | 61 | Now the largest whales, those which frequent those parts of the sea 62 | round the Aleutian, Kulammak, and Umgullich islands, have never 63 | exceeded the length of sixty yards, if they attain that. 64 | 65 | In every place of great resort the monster was the fashion. They sang 66 | of it in the cafes, ridiculed it in the papers, and represented it on 67 | the stage. All kinds of stories were circulated regarding it. There 68 | appeared in the papers caricatures of every gigantic and imaginary 69 | creature, from the white whale, the terrible "Moby Dick" of sub-arctic 70 | regions, to the immense kraken, whose tentacles could entangle a ship 71 | of five hundred tons and hurry it into the abyss of the ocean. The 72 | legends of ancient times were even revived. 73 | 74 | Then burst forth the unending argument between the believers and the 75 | unbelievers in the societies of the wise and the scientific journals. 76 | "The question of the monster" inflamed all minds. Editors of 77 | scientific journals, quarrelling with believers in the supernatural, 78 | spilled seas of ink during this memorable campaign, some even drawing 79 | blood; for from the sea-serpent they came to direct personalities. 80 | 81 | During the first months of the year 1867 the question seemed buried, 82 | never to revive, when new facts were brought before the public. It was 83 | then no longer a scientific problem to be solved, but a real danger 84 | seriously to be avoided. The question took quite another shape. The 85 | monster became a small island, a rock, a reef, but a reef of indefinite 86 | and shifting proportions. 87 | 88 | On the 5th of March, 1867, the Moravian, of the Montreal Ocean Company, 89 | finding herself during the night in 27° 30' lat. and 72° 15' 90 | long., struck on her starboard quarter a rock, marked in no chart for 91 | that part of the sea. Under the combined efforts of the wind and its 92 | four hundred horse power, it was going at the rate of thirteen knots. 93 | Had it not been for the superior strength of the hull of the Moravian, 94 | she would have been broken by the shock and gone down with the 237 95 | passengers she was bringing home from Canada. 96 | 97 | The accident happened about five o'clock in the morning, as the day was 98 | breaking. The officers of the quarter-deck hurried to the after-part 99 | of the vessel. They examined the sea with the most careful attention. 100 | They saw nothing but a strong eddy about three cables' length distant, 101 | as if the surface had been violently agitated. The bearings of the 102 | place were taken exactly, and the Moravian continued its route without 103 | apparent damage. Had it struck on a submerged rock, or on an enormous 104 | wreck? They could not tell; but, on examination of the ship's bottom 105 | when undergoing repairs, it was found that part of her keel was broken. 106 | 107 | This fact, so grave in itself, might perhaps have been forgotten like 108 | many others if, three weeks after, it had not been re-enacted under 109 | similar circumstances. But, thanks to the nationality of the victim of 110 | the shock, thanks to the reputation of the company to which the vessel 111 | belonged, the circumstance became extensively circulated. 112 | 113 | The 13th of April, 1867, the sea being beautiful, the breeze 114 | favourable, the Scotia, of the Cunard Company's line, found herself in 115 | 15° 12' long. and 45° 37' lat. She was going at the speed of 116 | thirteen knots and a half. 117 | 118 | At seventeen minutes past four in the afternoon, whilst the passengers 119 | were assembled at lunch in the great saloon, a slight shock was felt on 120 | the hull of the Scotia, on her quarter, a little aft of the port-paddle. 121 | 122 | The Scotia had not struck, but she had been struck, and seemingly by 123 | something rather sharp and penetrating than blunt. The shock had been 124 | so slight that no one had been alarmed, had it not been for the shouts 125 | of the carpenter's watch, who rushed on to the bridge, exclaiming, "We 126 | are sinking! we are sinking!" At first the passengers were much 127 | frightened, but Captain Anderson hastened to reassure them. The danger 128 | could not be imminent. The Scotia, divided into seven compartments by 129 | strong partitions, could brave with impunity any leak. Captain 130 | Anderson went down immediately into the hold. He found that the sea 131 | was pouring into the fifth compartment; and the rapidity of the influx 132 | proved that the force of the water was considerable. Fortunately this 133 | compartment did not hold the boilers, or the fires would have been 134 | immediately extinguished. Captain Anderson ordered the engines to be 135 | stopped at once, and one of the men went down to ascertain the extent 136 | of the injury. Some minutes afterwards they discovered the existence 137 | of a large hole, two yards in diameter, in the ship's bottom. Such a 138 | leak could not be stopped; and the Scotia, her paddles half submerged, 139 | was obliged to continue her course. She was then three hundred miles 140 | from Cape Clear, and, after three days' delay, which caused great 141 | uneasiness in Liverpool, she entered the basin of the company. 142 | 143 | The engineers visited the Scotia, which was put in dry dock. They 144 | could scarcely believe it possible; at two yards and a half below 145 | water-mark was a regular rent, in the form of an isosceles triangle. 146 | The broken place in the iron plates was so perfectly defined that it 147 | could not have been more neatly done by a punch. It was clear, then, 148 | that the instrument producing the perforation was not of a common stamp 149 | and, after having been driven with prodigious strength, and piercing an 150 | iron plate 1 3/8 inches thick, had withdrawn itself by a backward 151 | motion. 152 | 153 | Such was the last fact, which resulted in exciting once more the 154 | torrent of public opinion. From this moment all unlucky casualties 155 | which could not be otherwise accounted for were put down to the monster. 156 | 157 | Upon this imaginary creature rested the responsibility of all these 158 | shipwrecks, which unfortunately were considerable; for of three 159 | thousand ships whose loss was annually recorded at Lloyd's, the number 160 | of sailing and steam-ships supposed to be totally lost, from the 161 | absence of all news, amounted to not less than two hundred! 162 | 163 | Now, it was the "monster" who, justly or unjustly, was accused of their 164 | disappearance, and, thanks to it, communication between the different 165 | continents became more and more dangerous. The public demanded sharply 166 | that the seas should at any price be relieved from this formidable 167 | cetacean.[1] 168 | 169 | 170 | [1] Member of the whale family. --------------------------------------------------------------------------------